package capsule import ( "crypto/sha256" "encoding/binary" "encoding/hex" "errors" "time" "g.activething.com/go/DateKeys/codec/bech32" "g.activething.com/go/DateKeys/internal/ed25519strict" ) // The domain prefixes of what an author signs and a seal seals (spec v0.11, // §29.8, §29.11). Each is followed by a byte 0x00, except in AUTHOR_MESSAGE, // where a line feed follows it. const ( payloadCommitPrefix = "datekeys:dkc3:payload:v1" controlCommitPrefix = "datekeys:dkc3:control:v1" headDigestPrefix = "datekeys:dkc3:head:v1" signersDigestPrefix = "datekeys:dkc3:signers:v1" sigPartPrefix = "datekeys:dkc3:sig-part:v1" sealSubjectPrefix = "datekeys:dkc3:seal-subject:v1" // AuthorMessagePrefix is the first line of AUTHOR_MESSAGE. AuthorMessagePrefix = "datekeys:dkc3:author-signature:v1" // AuthorMessageSize is the length of AUTHOR_MESSAGE: the prefix, a line // feed, the 64 hexadecimal digits of its digest and a line feed. AuthorMessageSize = len(AuthorMessagePrefix) + 1 + 64 + 1 ) // The values of alg that this version defines (spec v0.11, §29.3). const ( // AlgEd25519 is a strict Ed25519 signature with a key of one's own (§29.9). AlgEd25519 = 1 // AlgCMS is a CMS signature with X.509 certificates (§29.10). AlgCMS = 2 // AlgTest and SealTypeTest are reserved for tests: no version defines // them, so they are F1 and S1 in every version (§29.3). AlgTest = 4294967295 SealTypeTest = 4294967295 ) // AuthorKey signs AUTHOR_MESSAGE with alg 1, as *authorkey.Key does. type AuthorKey interface { // Public returns the public key A, 32 bytes. Public() []byte // Sign returns the Ed25519 signature of message, 64 bytes. Sign(message []byte) []byte } // CMSSigner makes the signature of alg 2 (spec v0.11, §29.10). EncryptFiles // calls Sign with AUTHOR_MESSAGE once the capsule is prepared, and waits: the // person signs it outside, with AutoFirma or another application, and Sign // returns the DER of the CMS signature that she got, with its seals. type CMSSigner interface { // Signers returns the SHA-256 of the certificate of each required // signer, from 1 to 16. Signers() [][32]byte // Sign returns the detached CMS signature of message, in DER. Sign(message []byte) ([]byte, error) } // Sealer asks an authority for an RFC 3161 time-stamp token (spec §29.11). type Sealer interface { // Seal returns the DER of the token over SHA-256(subject), where subject // is SEAL_SUBJECT; the imprint SHA-256 of the 32 bytes is the one that // the token must hold. Seal(subject [32]byte) ([]byte, error) } func domainHash(prefix string, parts ...[]byte) [32]byte { h := sha256.New() h.Write([]byte(prefix)) h.Write([]byte{0}) for _, p := range parts { h.Write(p) } var out [32]byte h.Sum(out[:0]) return out } // PayloadCommit is the commitment to I_PAYLOAD that replaces it in what is // signed (spec §29.8). func PayloadCommit(identity [32]byte) [32]byte { return domainHash(payloadCommitPrefix, identity[:]) } // ControlCommit is control_commit: the hash of CONTROL_SIG, the control of a // capsule of format f with payload_commit in place of I_PAYLOAD and the eight // bytes of L at zero (spec §29.8). It does not depend on L, so the area can // grow after signing. func ControlCommit(c *Control, f Format) ([32]byte, error) { sig := *c sig.PayloadIdentity = PayloadCommit(c.PayloadIdentity) sig.PayloadLength = 0 b, err := EncodeControl(&sig, f) if err != nil { return [32]byte{}, err } return domainHash(controlCommitPrefix, b), nil } // HeadDigest is head_digest, the hash of HEAD_CBOR (spec §29.8). The salt of // the head makes it a commitment that hides the files. func HeadDigest(head []byte) [32]byte { return domainHash(headDigestPrefix, head) } // SignersDigest is signers_digest for alg and the exact content of key 1 of a // signature of alg 2, signers; nil with alg 1 (spec §29.8). func SignersDigest(alg uint32, signers []byte) [32]byte { return domainHash(signersDigestPrefix, binary.BigEndian.AppendUint32(nil, alg), signers) } // AuthorMessage is AUTHOR_MESSAGE, the ASCII text that an author signs: // AuthorMessagePrefix, a line feed, the hexadecimal digest D of the three // commitments and a line feed (spec §29.8). func AuthorMessage(controlCommit, headDigest, signersDigest [32]byte) []byte { d := sha256.Sum256(append(append(controlCommit[:], headDigest[:]...), signersDigest[:]...)) m := make([]byte, 0, AuthorMessageSize) m = append(m, AuthorMessagePrefix...) m = append(m, '\n') m = hex.AppendEncode(m, d[:]) return append(m, '\n') } // AuthorCode is the code of AUTHOR_MESSAGE that a person compares before // signing: the first 8 hexadecimal digits of its digest, in two groups of 4. func AuthorCode(message []byte) string { if len(message) != AuthorMessageSize { return "" } d := message[len(AuthorMessagePrefix)+1:] return string(d[:4]) + "-" + string(d[4:8]) } // SigPart is SIG_PART: 0x00 without key 2, and 0x01 and the hash of the exact // content of key 2 otherwise, whatever its alg and its verdict (spec §29.11). func SigPart(signature []byte) []byte { if signature == nil { return []byte{0} } h := domainHash(sigPartPrefix, signature) return append([]byte{1}, h[:]...) } // SealSubject is SEAL_SUBJECT, what a seal of seal_type 2 seals (spec §29.11). func SealSubject(controlCommit, headDigest [32]byte, sigPart []byte) [32]byte { return domainHash(sealSubjectPrefix, controlCommit[:], headDigest[:], sigPart) } // SecurityKey2 returns the exact content of key 2 of SECURITY_CBOR and, when // it is an author-signature that decodes, the signature value it holds: what // a generator of test vectors records about a signature. It fails when // security has no key 2 or its content does not decode. func SecurityKey2(security []byte) (content, value []byte, err error) { w, ok := decodeSecurity(security) if !ok || w.signature == nil { return nil, nil, errors.New("capsule: SECURITY_CBOR holds no author-signature") } a, err := decodeAuthorSignature(w.signature) if err != nil { return nil, nil, err } return w.signature, a.value, nil } // SecurityKey3 returns the seal_type and the token of key 3 of SECURITY_CBOR: // what a generator of test vectors records about a seal. It fails when // security has no key 3 or its content does not decode. func SecurityKey3(security []byte) (sealType uint64, token []byte, err error) { w, ok := decodeSecurity(security) if !ok || w.seal == nil { return 0, nil, errors.New("capsule: SECURITY_CBOR holds no seal") } s, err := decodeSeal(w.seal) if err != nil { return 0, nil, err } return s.sealType, s.token, nil } // DecodeAuthorSignature reads the content of key 2 of SECURITY_CBOR: the alg, // key 1 (the public key of alg 1, SIGNERS of alg 2) and the signature value. func DecodeAuthorSignature(content []byte) (alg uint64, key, value []byte, err error) { a, err := decodeAuthorSignature(content) if err != nil { return 0, nil, nil, err } return a.alg, a.key, a.value, nil } // SecurityContext is what the verdicts of a signature or a seal need besides // SECURITY_CBOR: the commitments of the capsule, the time of its round, and // the author keys that the person saved, by their dkauthor1… string, with the // label she gave them (F3). A reader builds it at step 17.6. type SecurityContext struct { ControlCommit, HeadDigest [32]byte RoundTime time.Time AuthorKeys map[string]string } // EvaluateSecurityIn returns the verdicts of SECURITY_CBOR in the capsule // that c describes (spec §29.7). Without a context, as EvaluateSecurity, it // checks only the structure: any signature is F1, as in v0.10. It never // fails: security never decides the opening. func EvaluateSecurityIn(b []byte, c *SecurityContext) Verdicts { // Security never decides the opening (spec §29.3): whatever a parser does // with hostile input, the capsule opens. A panic is a failure of its own // part only, as a failure of its form would be: X for the outer map, F1 // for the signature and S2 for the seal, each apart from the other. var w *securityWire if !recovered(func() { w, _ = decodeSecurity(b) }) || w == nil { return Verdicts{Signature: VerdictUnreadable, Seal: VerdictUnreadable} } v := Verdicts{Signature: VerdictNoSignature, Seal: VerdictNoSeal} if w.signature != nil { v.Signature = VerdictSignatureUnchecked if c != nil && !recovered(func() { evaluateSignature(&v, w, c) }) { v = Verdicts{Signature: VerdictSignatureUnchecked, Seal: VerdictNoSeal} } } if w.seal != nil { signature := v if !recovered(func() { setSeal(&v, w, c) }) { v = signature v.Seal = VerdictSealUnreadable if v.Detail != nil { d := *v.Detail d.SealHolder, d.SealTime = "", time.Time{} v.Detail = &d } } } return v } // setSeal sets the verdict of the seal of w (spec §29.7): S2 for content that // breaks the schema of seal, S1 for a seal_type this reader does not // implement, and the verdicts of §29.11 for seal_type 2. func setSeal(v *Verdicts, w *securityWire, c *SecurityContext) { s, err := decodeSeal(w.seal) switch { case err != nil: v.Seal = VerdictSealUnreadable case s.sealType == SealTypeRFC3161 && c != nil: evaluateSeal(v, s, w.signature, c) default: v.Seal = VerdictSealUnsupported } } // recovered runs f and reports whether it returned without a panic. func recovered(f func()) (ok bool) { defer func() { if recover() != nil { ok = false } }() f() return true } // evaluateSignature sets the verdict of the content of key 2: F1 for content // that does not decode, an alg this reader does not implement or a key or a // signature of another length; F2 when the signature does not verify; F3 or // F4 when it does (spec §29.7, §29.9). func evaluateSignature(v *Verdicts, w *securityWire, c *SecurityContext) { a, err := decodeAuthorSignature(w.signature) if err != nil { return } if a.alg == AlgCMS { evaluateCMS(v, a, w.seal != nil, c) return } if a.alg != AlgEd25519 || len(a.key) != 32 || len(a.value) != 64 { return } msg := AuthorMessage(c.ControlCommit, c.HeadDigest, SignersDigest(AlgEd25519, nil)) if !ed25519strict.Verify(a.key, msg, a.value) { v.Signature = VerdictSignatureInvalid return } v.Signature = VerdictSignedOther copy(v.AuthorKey[:], a.key) if s, err := bech32.Encode("dkauthor", a.key); err == nil { if label, ok := c.AuthorKeys[s]; ok { v.Signature, v.AuthorLabel = VerdictSignedSaved, label } } }