| 1 | package home |
| 2 | |
| 3 | import ( |
| 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. |
| 19 | type rosterRuntime struct { |
| 20 | mode string |
| 21 | signer *family.Keypair |
| 22 | keyID string |
| 23 | } |
| 24 | |
| 25 | func rosterKeyID(k *family.Keypair) string { |
| 26 | sum := sha256.Sum256(k.SignPub()) |
| 27 | return hex.EncodeToString(sum[:]) |
| 28 | } |
| 29 | func 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 | } |
| 36 | func (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 | |
| 54 | func 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 | |
| 86 | func 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 | |