package capsule import ( "bytes" "encoding/hex" "errors" "time" "unicode/utf8" "g.activething.com/go/DateKeys/codec" "g.activething.com/go/DateKeys/internal/cms" "g.activething.com/go/DateKeys/internal/pathrule" ) // SealTypeRFC3161 is the seal_type of a seal that is an RFC 3161 time-stamp // token (spec v0.11, §29.3, §29.11). const SealTypeRFC3161 = 2 // MaxSigners is the most required signers of an alg 2 signature (§29.10). const MaxSigners = 16 // EncodeSigners returns SIGNERS, the content of key 1 of an author-signature // of alg 2: a CBOR array of 1 to 16 strings of 32 bytes, the SHA-256 of the // certificate of each required signer, in strictly ascending order of bytes // (spec §29.10). It sorts them, and fails when there are none, too many or // two are equal. func EncodeSigners(hashes [][32]byte) ([]byte, error) { if len(hashes) < 1 || len(hashes) > MaxSigners { return nil, errors.New("capsule: SIGNERS holds from 1 to 16 certificates") } sorted := append([][32]byte(nil), hashes...) for i := 1; i < len(sorted); i++ { // insertion sort: at most 16 for j := i; j > 0 && bytes.Compare(sorted[j-1][:], sorted[j][:]) > 0; j-- { sorted[j-1], sorted[j] = sorted[j], sorted[j-1] } } for i := 1; i < len(sorted); i++ { if sorted[i-1] == sorted[i] { return nil, errors.New("capsule: SIGNERS names a certificate twice") } } var e codec.Encoder e.Array(len(sorted)) for _, h := range sorted { e.Bstr(h[:]) } return e.Out() } // decodeSigners reads SIGNERS and checks the profile of spec §29.10. func decodeSigners(b []byte) ([][32]byte, error) { var out [][32]byte decode := func(d *codec.Decoder) error { n, err := d.Array(MaxSigners) if err != nil { return err } if n < 1 { return errors.New("SIGNERS is empty") } for range n { h, err := d.Bstr(32, 32) if err != nil { return err } var x [32]byte copy(x[:], h) if len(out) > 0 && bytes.Compare(out[len(out)-1][:], x[:]) >= 0 { return errors.New("SIGNERS is not in strictly ascending order") } out = append(out, x) } return nil } encode := func(e *codec.Encoder) { e.Array(len(out)) for _, h := range out { e.Bstr(h[:]) } } if err := codec.Unmarshal(b, decode, encode); err != nil { return nil, err } return out, nil } // holderText is how §29.7 shows a name: the name, when it meets the rules of // the declared author, and the SHA-256 of the certificate otherwise. func holderText(name string, hash [32]byte) string { if name != "" && utf8.ValidString(name) && len(name) <= MaxAuthorLen && pathrule.CheckAuthor(name) == nil { return name } return hex.EncodeToString(hash[:]) } // evaluateCMS sets the verdict of a signature of alg 2 (spec §29.10): F1 for // content that breaks its profile, F2 when the signature of a required // signer is invalid, F5 when something the capsule demands is missing, F6 // when every required signer is valid and sealed. hasSeal is whether key 3 // exists, which an alg 2 signature forbids. func evaluateCMS(v *Verdicts, a *authorSignature, hasSeal bool, c *SecurityContext) { required, err := decodeSigners(a.key) if err != nil { return } sd, err := cms.ParseSignature(a.value) if err != nil { return } msg := AuthorMessage(c.ControlCommit, c.HeadDigest, SignersDigest(AlgCMS, a.key)) detail := &Detail{} byHash := map[[32]byte]*cms.SignerInfo{} for _, s := range sd.Signers { byHash[s.Cert.Hash] = s } invalid, incomplete := false, hasSeal for _, h := range required { s := byHash[h] if s == nil { detail.Signers = append(detail.Signers, SignerLine{Holder: hex.EncodeToString(h[:]), Result: "absent"}) incomplete = true continue } line := signerLine(s, msg, c.RoundTime) switch line.Result { case "invalid": invalid = true case "valid": default: incomplete = true } detail.Signers = append(detail.Signers, line) } for _, s := range sd.Signers { if !isRequired(required, s.Cert.Hash) { detail.Foreign = append(detail.Foreign, signerLine(s, msg, c.RoundTime)) } } v.Detail = detail switch { case invalid: v.Signature = VerdictSignatureInvalid case incomplete: v.Signature = VerdictSignedIncomplete default: v.Signature = VerdictSignedComplete } } func isRequired(required [][32]byte, h [32]byte) bool { for _, r := range required { if r == h { return true } } return false } // signerLine checks one SignerInfo as §29.10 orders: not verifiable, invalid, // without seal, with an invalid seal, out of validity, or valid. func signerLine(s *cms.SignerInfo, msg []byte, roundTime time.Time) SignerLine { l := SignerLine{Holder: holderText(s.Cert.Holder(), s.Cert.Hash), Issuer: s.Cert.IssuerName()} switch s.Check(msg) { case cms.NotVerifiable: l.Result = "not verifiable" return l case cms.Invalid: l.Result = "invalid" return l } if s.Token == nil { l.Result = "without seal" return l } tok, err := cms.ParseToken(s.Token) if err != nil || !tok.Check(s.Signature) { l.Result = "invalid seal" return l } if !s.Cert.ValidAt(tok.GenTime) { l.Result = "out of validity" return l } l.Result, l.SealTime = "valid", tok.GenTime l.Before = !roundTime.IsZero() && tok.GenTime.Add(tok.Accuracy).Before(roundTime) return l } // evaluateSeal sets the verdict of a seal of seal_type 2 (spec §29.11): S2 or // S1 for the form and the algorithms, S3 when it does not verify, and S4 or // S5 when it does. signature is the content of key 2, nil without it. func evaluateSeal(v *Verdicts, s *seal, signature []byte, c *SecurityContext) { tok, err := cms.ParseToken(s.token) switch { case errors.Is(err, cms.ErrForm): v.Seal = VerdictSealUnreadable return case err != nil || !tok.ImprintIsSHA256(): v.Seal = VerdictSealUnsupported return } subject := SealSubject(c.ControlCommit, c.HeadDigest, SigPart(signature)) if !tok.Check(subject[:]) { v.Seal = VerdictSealInvalid return } if v.Detail == nil { v.Detail = &Detail{} } v.Detail.SealHolder, v.Detail.SealTime = holderText(tok.TSA.Holder(), tok.TSA.Hash), tok.GenTime v.Seal = VerdictSealedLate if !c.RoundTime.IsZero() && tok.GenTime.Add(tok.Accuracy).Before(c.RoundTime) { v.Seal = VerdictSealed } }