package delivery import ( "crypto/ed25519" "crypto/sha256" "crypto/x509" "crypto/x509/pkix" "encoding/base64" "encoding/hex" "encoding/json" "encoding/pem" "errors" "fmt" "math/big" "slices" "strings" "time" ) // **The vault signs** (novox/hq ADR 0550 §2; design 47's L3 item d). The vault makes an ed25519 signing key and an x509 // authority once, keeps both as its own secrets with their operator copies, and publishes their public halves in its // state `signers`, which only the vault writes and every node principal and composing part reads. Its verb `sign` signs a // node's certificate for that node's own node-engine, from bus state alone (the name from the node's placement, the // serving key from its record), and says `signed` naming the caller, the kind, the node and the digest, never the key. // // A node trusts the vault's key through the bus grants alone (the lead's design limit, 2026-10-28): only the vault may // write `signers`, the same trust the wants and the placement rest on. Until the controller's lease is handed away the // node trusts the controller's key beside it; from then on the vault's alone (LeaseHandedAway). What a node trusts now // it states in its record, NodeRecord.Trusts, so the gate before the lease write reads it from the bus. const ( // VaultModule is the module holding the seat mesh-vault, whose state `signers` is. VaultModule = "mesh-vault" // SignersState is the vault's state of its public signing halves, and SignersBucket its bucket. SignersState = "signers" SignersBucket = VaultModule + "_" + SignersState // SignersRead is the state as a manifest's `reads` names it. SignersRead = VaultModule + "." + SignersState // SignersKeyKey holds the signing key's public half (SignerKey); SignersAuthorityKey the authority (SignerAuthority). SignersKeyKey = "key" SignersAuthorityKey = "authority" // SignVerb is the vault's verb that signs. SignVerb = "sign" // SignedEvent is what the vault says when it signed (Signed), on SignedSubject. SignedEvent = "signed" SignedSubject = "mesh.mod." + VaultModule + ".event." + SignedEvent // KindNodeCertificate is a node's certificate for its internal name, asked by that node's node-engine alone. KindNodeCertificate = "node-certificate" // KindEnrolment is an enrolment reply, signed under its own context; no caller is named until adoption's record // (ADR 0561 §5). KindEnrolment = "enrolment" // KindAuthority is the vault's own authority certificate, which the vault signs for itself. KindAuthority = "authority" // SignerController and SignerVault name the two signers a node may trust. SignerController = "controller" SignerVault = "vault" // CertificateLife is how long a certificate the vault signs for a node lasts: the controller's ten years // (identity.forever), renewed by asking again, never by a clock running out unseen. CertificateLife = 10 * 365 * 24 * time.Hour ) // SignKinds are the kinds `sign` knows. var SignKinds = []string{KindNodeCertificate, KindEnrolment, KindAuthority} // SignContext is the line a message of kind is signed after, so a signature made for one kind never verifies as // another's (as the composition's and the asks' contexts are kept apart). func SignContext(kind string) (string, error) { if !slices.Contains(SignKinds, kind) { return "", fmt.Errorf("delivery: %q is not a kind the vault signs (%s)", kind, strings.Join(SignKinds, ", ")) } return "novox " + kind + " v1\n", nil } // SignerKey is the key `key` of `signers`: the vault's signing key's public half in standard base64, its fingerprint, // and when it was made. type SignerKey struct { Key string `json:"key"` Fingerprint string `json:"fingerprint"` Made time.Time `json:"made"` } // SignerAuthority is the key `authority` of `signers`: the vault's authority certificate as PEM, its fingerprint, and // when it was made. type SignerAuthority struct { Certificate string `json:"certificate"` Fingerprint string `json:"fingerprint"` Made time.Time `json:"made"` } // SignerFingerprint is an ed25519 public key's fingerprint, `sha256:` of its 32 bytes. func SignerFingerprint(key ed25519.PublicKey) string { sum := sha256.Sum256(key) return "sha256:" + hex.EncodeToString(sum[:]) } // AuthorityFingerprint is an authority certificate's fingerprint, `sha256:` of its DER; refused when certificate is // not one PEM certificate. func AuthorityFingerprint(certificate string) (string, error) { c, err := ParseCertificate(certificate) if err != nil { return "", err } sum := sha256.Sum256(c.Raw) return "sha256:" + hex.EncodeToString(sum[:]), nil } // ParseCertificate is the first certificate of a PEM text. func ParseCertificate(text string) (*x509.Certificate, error) { block, _ := pem.Decode([]byte(strings.TrimSpace(text))) if block == nil || block.Type != "CERTIFICATE" { return nil, errors.New("delivery: not a PEM certificate") } return x509.ParseCertificate(block.Bytes) } // NewSignerKey is the value of `key` for pub. func NewSignerKey(pub ed25519.PublicKey, made time.Time) SignerKey { return SignerKey{Key: base64.StdEncoding.EncodeToString(pub), Fingerprint: SignerFingerprint(pub), Made: made.UTC()} } // PublicKey is k's key, checked: an ed25519 public key whose fingerprint is the one k names. func (k SignerKey) PublicKey() (ed25519.PublicKey, error) { raw, err := base64.StdEncoding.DecodeString(k.Key) if err != nil || len(raw) != ed25519.PublicKeySize { return nil, errors.New("delivery: the signers' key is not an ed25519 public key in base64") } if fp := SignerFingerprint(raw); fp != k.Fingerprint { return nil, fmt.Errorf("delivery: the signers' key has the fingerprint %s, and names %s", fp, k.Fingerprint) } return ed25519.PublicKey(raw), nil } // DecodeSignerKey reads the key `key` of `signers`, refused when it does not check. func DecodeSignerKey(raw []byte) (SignerKey, error) { var k SignerKey if err := json.Unmarshal(raw, &k); err != nil { return SignerKey{}, fmt.Errorf("delivery: %s %s: %w", SignersRead, SignersKeyKey, err) } if _, err := k.PublicKey(); err != nil { return SignerKey{}, err } return k, nil } // DecodeSignerAuthority reads the key `authority` of `signers`: one authority certificate (a CA that signs leaves // alone) whose fingerprint is the one named. func DecodeSignerAuthority(raw []byte) (SignerAuthority, error) { var a SignerAuthority if err := json.Unmarshal(raw, &a); err != nil { return SignerAuthority{}, fmt.Errorf("delivery: %s %s: %w", SignersRead, SignersAuthorityKey, err) } c, err := ParseCertificate(a.Certificate) if err != nil { return SignerAuthority{}, err } if !c.IsCA { return SignerAuthority{}, errors.New("delivery: the signers' authority is not an authority certificate") } if fp, _ := AuthorityFingerprint(a.Certificate); fp != a.Fingerprint { return SignerAuthority{}, fmt.Errorf("delivery: the signers' authority has the fingerprint %s, and names %s", fp, a.Fingerprint) } return a, nil } // Signer is one key a node trusts, by who holds it (SignerController, SignerVault, or the operator's). type Signer struct { Name string Key ed25519.PublicKey } // Fingerprint is the signer's key's fingerprint. func (s Signer) Fingerprint() string { return SignerFingerprint(s.Key) } // VerifyBy is the trusted signer whose key verifies sig over context and msg: refused when no trusted key does, saying // so, and when a key is not an ed25519 key. A signature made under another context never verifies. func VerifyBy(trusted []Signer, context string, msg, sig []byte) (Signer, error) { if len(sig) != ed25519.SignatureSize { return Signer{}, errors.New("delivery: not an ed25519 signature") } message := append([]byte(context), msg...) for _, s := range trusted { if len(s.Key) != ed25519.PublicKeySize { continue } if ed25519.Verify(s.Key, message, sig) { return s, nil } } names := make([]string, 0, len(trusted)) for _, s := range trusted { names = append(names, s.Name) } return Signer{}, fmt.Errorf("delivery: the signature verifies against no trusted key (%s)", strings.Join(names, ", ")) } // SignMessage is sig over context and msg by key: what the vault's `sign` does for a kind whose bytes are given. func SignMessage(key ed25519.PrivateKey, context string, msg []byte) []byte { return ed25519.Sign(key, append([]byte(context), msg...)) } // Signed is the vault's `signed`: who asked, the kind, the node, and the sha256 digest of what was signed. Never a key. type Signed struct { Caller string `json:"caller"` Kind string `json:"kind"` Node string `json:"node,omitempty"` Digest string `json:"digest"` Signer string `json:"signer"` At time.Time `json:"at"` } // SignAnswer is what `sign` answers: for a node certificate the certificate as PEM and the authority that signed it, // for an enrolment the signature in base64; always the signer's fingerprint and the digest. Never a key's private half. type SignAnswer struct { Kind string `json:"kind"` Node string `json:"node,omitempty"` Certificate string `json:"certificate,omitempty"` Authority string `json:"authority,omitempty"` Signature string `json:"signature,omitempty"` Signer string `json:"signer"` Digest string `json:"digest"` } // DigestOf is the digest a `signed` names: `sha256:` of the bytes signed. func DigestOf(b []byte) string { sum := sha256.Sum256(b) return "sha256:" + hex.EncodeToString(sum[:]) } // NodeCertificateTemplate is the certificate the vault signs for node's internal name, as the controller's authority // shaped it (identity.Certify): the name in the subject and the subject alternative names, server and client use, the // serial derived from the name and the key so the same ask gives the same certificate, and its life from notBefore. func NodeCertificateTemplate(name string, servingKey ed25519.PublicKey, notBefore time.Time) *x509.Certificate { sum := sha256.Sum256(append([]byte(name+"\n"), servingKey...)) return &x509.Certificate{ SerialNumber: new(big.Int).SetBytes(sum[:16]), Subject: pkix.Name{CommonName: name}, DNSNames: []string{name}, NotBefore: notBefore.UTC().Truncate(time.Second), NotAfter: notBefore.UTC().Truncate(time.Second).Add(CertificateLife), KeyUsage: x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, } } // AuthorityTemplate is the vault's authority certificate for its key, made once: an authority that signs leaves and no // other authority, as the controller's. func AuthorityTemplate(key ed25519.PublicKey, made time.Time) *x509.Certificate { sum := sha256.Sum256(append([]byte("authority\n"), key...)) return &x509.Certificate{ SerialNumber: new(big.Int).SetBytes(sum[:16]), Subject: pkix.Name{CommonName: "the mesh's vault"}, NotBefore: made.UTC().Truncate(time.Second), NotAfter: made.UTC().Truncate(time.Second).Add(2 * CertificateLife), IsCA: true, KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, BasicConstraintsValid: true, MaxPathLen: 0, MaxPathLenZero: true, } } // CheckNodeCertificate says why certificate is not a certificate of name for servingKey by one of the authorities, or // nil: a node uses it to tell whether its record holds a certificate from an authority it trusts, and another node to // check one the vault signed (ADR 0550 §2's live check). func CheckNodeCertificate(certificate string, authorities []string, name string, servingKey ed25519.PublicKey, at time.Time) error { c, err := ParseCertificate(certificate) if err != nil { return err } pool := x509.NewCertPool() for _, a := range authorities { pool.AppendCertsFromPEM([]byte(a)) // a bundle is every certificate it holds } if _, err := c.Verify(x509.VerifyOptions{Roots: pool, DNSName: name, CurrentTime: at, KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}}); err != nil { return fmt.Errorf("delivery: the certificate of %s is not one a trusted authority signed for it: %w", name, err) } held, ok := c.PublicKey.(ed25519.PublicKey) if !ok || !held.Equal(servingKey) { return fmt.Errorf("delivery: the certificate of %s binds another key than the node serves with", name) } return nil } // AuthorityBundle is the authorities given as one PEM text, each certificate once, in the order given, blanks left out: // what a module asking for the mesh's authority is given once the vault signs beside the controller (ADR 0550 §2). An // authority given is split into its certificates, so a bundle given again (the one a node kept from its last // composition) adds nothing twice; a text that holds no PEM block is taken whole. func AuthorityBundle(authorities ...string) string { var out []string for _, a := range authorities { for _, piece := range pemPieces(a) { if !slices.Contains(out, piece) { out = append(out, piece) } } } if len(out) == 0 { return "" } return strings.Join(out, "\n") + "\n" } // pemPieces is each PEM block of text re-encoded, or the trimmed text whole when it holds none. func pemPieces(text string) []string { text = strings.TrimSpace(text) if text == "" { return nil } var out []string rest := []byte(text) for { block, more := pem.Decode(rest) if block == nil { break } out = append(out, strings.TrimSpace(string(pem.EncodeToMemory(block)))) rest = more } if len(out) == 0 { return []string{text} } return out } // Trusts is what a node states it trusts now (NodeRecord.Trusts): ` key ` for each signer and // ` authority ` for each authority certificate, by who holds it, each once, sorted. The gate before the // lease write reads `vault key ` in every node's record. func Trusts(signers []Signer, authorities map[string]string) []string { var out []string for _, s := range signers { if len(s.Key) == ed25519.PublicKeySize { out = append(out, s.Name+" key "+s.Fingerprint()) } } for who, a := range authorities { if fp, err := AuthorityFingerprint(a); err == nil { out = append(out, who+" authority "+fp) } } slices.Sort(out) return slices.Compact(out) } // TrustsKey says whether trusts names the key with fingerprint fp held by who. func TrustsKey(trusts []string, who, fp string) bool { return slices.Contains(trusts, who+" key "+fp) }