oficina / home Public

Clone
git clone https://amadan.net/oficina/home

Plain git — no account needed to clone.

Download

Download this file

1package home
2
3import (
4 "bytes"
5 "crypto/ecdh"
6 "crypto/elliptic"
7 "crypto/sha256"
8 "encoding/base64"
9 "encoding/hex"
10 "math/big"
11 "os"
12
13 family "amadan.net/rastrillo/rastrillo/crypto"
14 "github.com/go-json-experiment/json"
15)
16
17// The directory's roster key signs call-8 pages only. Runtime.signer remains
18// credential-only; this key never signs identity or operator provisioning.
19type rosterRuntime struct {
20 mode string
21 signer *family.Keypair
22 keyID string
23}
24
25func rosterKeyID(k *family.Keypair) string {
26 sum := sha256.Sum256(k.SignPub())
27 return hex.EncodeToString(sum[:])
28}
29func validRosterKey(k *family.Keypair) bool {
30 if k == nil || k.SignPriv == nil || k.SignPriv.D == nil || k.SignPriv.Curve != elliptic.P256() || k.SignPriv.D.Sign() <= 0 || k.SignPriv.D.Cmp(elliptic.P256().Params().N) >= 0 || k.SignPriv.X == nil || k.SignPriv.Y == nil || k.BoxPriv == nil || k.BoxPriv.Curve() != ecdh.P256() {
31 return false
32 }
33 x, y := elliptic.P256().ScalarBaseMult(k.SignPriv.D.Bytes())
34 return x.Cmp(k.SignPriv.X) == 0 && y.Cmp(k.SignPriv.Y) == 0
35}
36func (r rosterRuntime) valid(role string, credentialKeys map[string][]byte) bool {
37 if role != "directory" {
38 return r.mode == "" && r.signer == nil && r.keyID == ""
39 }
40 if r.mode == "" || r.mode == "disabled" {
41 return r.signer == nil && r.keyID == ""
42 }
43 if r.mode != "serve" || !validRosterKey(r.signer) || r.keyID != rosterKeyID(r.signer) {
44 return false
45 }
46 for _, key := range credentialKeys {
47 if bytes.Equal(key, r.signer.SignPub()) {
48 return false
49 }
50 }
51 return true
52}
53
54func loadRosterRuntime(role string, credentialKeys map[string][]byte) (rosterRuntime, error) {
55 mode, modeSet := os.LookupEnv("HOME_ROSTER_MODE")
56 encoded, keySet := os.LookupEnv("HOME_ROSTER_KEY")
57 if role != "directory" {
58 if modeSet || keySet {
59 return rosterRuntime{}, errExchangeConfig
60 }
61 return rosterRuntime{}, nil
62 }
63 if !modeSet {
64 mode = "disabled"
65 }
66 if mode == "disabled" {
67 if keySet {
68 return rosterRuntime{}, errExchangeConfig
69 }
70 return rosterRuntime{mode: mode}, nil
71 }
72 if mode != "serve" || !keySet || len(encoded) > 4096 {
73 return rosterRuntime{}, errExchangeConfig
74 }
75 key, err := decodeHomeKeypair(encoded)
76 if err != nil {
77 return rosterRuntime{}, err
78 }
79 cfg := rosterRuntime{mode: mode, signer: key, keyID: rosterKeyID(key)}
80 if !cfg.valid(role, credentialKeys) {
81 return rosterRuntime{}, errExchangeConfig
82 }
83 return cfg, nil
84}
85
86func decodeHomeKeypair(encoded string) (*family.Keypair, error) {
87 if len(encoded) == 0 || len(encoded) > 4096 {
88 return nil, errExchangeConfig
89 }
90 raw, err := base64.StdEncoding.Strict().DecodeString(encoded)
91 if err != nil || base64.StdEncoding.EncodeToString(raw) != encoded {
92 return nil, errExchangeConfig
93 }
94 var fields struct {
95 Sign string `json:"sign_priv_d"`
96 Box string `json:"box_priv_d"`
97 }
98 if json.Unmarshal(raw, &fields, json.RejectUnknownMembers(true)) != nil {
99 return nil, errExchangeConfig
100 }
101 for _, s := range []string{fields.Sign, fields.Box} {
102 b, e := hex.DecodeString(s)
103 if e != nil || len(b) != 32 || hex.EncodeToString(b) != s {
104 return nil, errExchangeConfig
105 }
106 d := new(big.Int).SetBytes(b)
107 if d.Sign() <= 0 || d.Cmp(elliptic.P256().Params().N) >= 0 {
108 return nil, errExchangeConfig
109 }
110 }
111 key, err := family.UnmarshalKeypair(raw)
112 if err != nil || !validRosterKey(key) {
113 return nil, errExchangeConfig
114 }
115 return key, nil
116}
117