// Writes the vectors of the CMS reader of datekeys-dart, stage 5a of // docs/PLAN_dart.md: the results and the texts of the errors of package // internal/cms of datekeys-go (spec v0.12 draft, §29.10 and §29.11), and of // the ECDSA and RSA of Go that it calls, on // // - cms_ecdsa.json: the curves P-256, P-384 and P-521 as Go's elliptic // gives them; points read by ecdsa.ParseUncompressedPublicKey; and // signatures checked by ecdsa.VerifyASN1, valid, with s above n/2, with // r or s of n or more, zero, negative or not minimal, in another // encoding, over another hash, with keys of d = 1 and d = n - 1; // - cms_rsa.json: keys of 2048, 2049, 3072 and 4096 bits and exponents 3, // 65537 and 2^31 - 1, and signatures checked as internal/cms checks // them, rsa.VerifyPKCS1v15 and rsa.VerifyPSS with a salt of the length // of the hash: valid, with a byte more or less, of n or more, and with // encodings built by hand and signed with the private key, each with one // defect of its padding; // - cms_certs.json: cms.ParseCert, its fields, Holder, IssuerName and // ValidAt around both ends of the validity, on the certificates of the // tests of internal/cms, on identifiers whose arcs wrap around to those // of the profile in 32 or 64 bits, and on certificates edited node by // node; // - cms_signatures.json: cms.ParseSignature and SignerInfo.Check on the // signatures of the tests of internal/cms (cms_test.go, form_test.go, // verify_test.go, hostile_test.go), with identifiers that wrap around // and SET OF with an element repeated; // - cms_tokens.json: cms.ParseToken and Token.Check on the tokens of // those tests, the TSTInfo field by field, the accuracy and the // messageImprint at their limits, and the authority at the ends of its // validity; // - cms_mutations.json: signatures and tokens edited node by node, each // edit written as the node, the operation and the SHA-256 of the result; // - cms_corpus.json: every CMS signature and token of // testdata/vectors/security_cms.json and of the fixtures // format3_signed_cms and format3_sealed of the reference, read as the // verdicts of capsule read them, signer by signer. // // Every expected value is what Go gives; none is written by hand. Binary // values are lower-case hexadecimal; a span [offset, length] places a value // that the reader returns inside the bytes it read; a time is [seconds, // nanoseconds] since 1970. The JSON is ASCII. // // It also writes test/vectors/cms_vectors.g.dart: a part of each file, as // Dart constants for the tests that also run compiled to JavaScript, where // no file can be read. A test on the VM checks that they are those of the // files. // // The keys, the certificates and the signatures come from Go's crypto with // the deterministic randomness of testing/cryptotest.SetGlobalRandom, and the // edits from a fixed seed: every run writes the same bytes with Go 1.26.8. // That needs a test binary, and internal/cms can be imported only from // inside the tree of datekeys-go: this file runs as a test in an export of // it, which it does not change, never in the repository itself. From the // root of datekeys-dart: // // commit=$(git -C ../datekeys-go rev-parse v0.12) // out=$PWD/test/vectors // tmp=$(mktemp -d) // git -C ../datekeys-go archive "$commit" | tar -x -C "$tmp" // mkdir "$tmp/cmsvectors" // cp tool/cms_go_vectors_test.go "$tmp/cmsvectors/" // (cd "$tmp/cmsvectors" && go test -run TestWriteVectors -count=1 \ // -args -source "$commit" -out "$out") // rm -rf "$tmp" package cmsvectors import ( "bytes" "crypto" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/rsa" "crypto/sha1" "crypto/sha256" "crypto/sha512" "encoding/asn1" "encoding/hex" "encoding/json" "errors" "flag" "fmt" "math/big" mrand "math/rand/v2" "os" "path/filepath" "regexp" "runtime" "slices" "strings" "testing" "testing/cryptotest" "time" "unicode/utf16" "g.activething.com/go/DateKeys/capsule" "g.activething.com/go/DateKeys/internal/cms" "g.activething.com/go/DateKeys/internal/cms/cmstest" "g.activething.com/go/DateKeys/internal/der" ) var ( sourceFlag = flag.String("source", "", "the commit of datekeys-go that this tree exports") outFlag = flag.String("out", "", "the directory test/vectors of datekeys-dart") ) // The values of the tests of internal/cms. var ( from = time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC) to = time.Date(2030, 1, 1, 0, 0, 0, 0, time.UTC) now = time.Date(2026, 9, 30, 12, 0, 0, 0, time.UTC) msg = []byte("datekeys:dkc3:author-signature:v1\n00\n") otherMsg = []byte("another message") sealSubject = []byte("seal subject") oidX = asn1.ObjectIdentifier{1, 2, 3, 4, 5} unknownOID = asn1.ObjectIdentifier{1, 2, 3, 4} ) const specLabel = "0.12 draft" // --------------------------------------------------------------------------- // The records // Span is the offset and the length of a value inside the bytes it was read // from. type Span [2]int // Time is a time as its seconds since 1970 and its nanoseconds. type Time [2]int64 func timeOf(t time.Time) *Time { return &Time{t.Unix(), int64(t.Nanosecond())} } // spanIn places sub, a slice of base, in it. func spanIn(base, sub []byte) Span { off := cap(base) - cap(sub) if off < 0 || off+len(sub) > len(base) || !bytes.Equal(base[off:off+len(sub)], sub) { panic("a value that is not a slice of what was read") } return Span{off, len(sub)} } func spanPtr(base, sub []byte) *Span { if sub == nil { return nil } s := spanIn(base, sub) return &s } func hx(b []byte) string { return hex.EncodeToString(b) } func ptr[T any](v T) *T { return &v } // Edit is an edit of a base: the node at Path, as cmstest.Edit finds it, and // the operation; SHA256 is the hash of the result. type Edit struct { Base string `json:"base,omitempty"` Path []int `json:"path,omitempty"` Op string `json:"op,omitempty"` Arg *int `json:"arg,omitempty"` Bit *int `json:"bit,omitempty"` SHA256 string `json:"sha256,omitempty"` } // CertOut is what cms.ParseCert gives. type CertOut struct { Error string `json:"error,omitempty"` Serial *Span `json:"serial,omitempty"` Issuer *Span `json:"issuer,omitempty"` Subject *Span `json:"subject,omitempty"` SKI *Span `json:"ski,omitempty"` NotBefore *Time `json:"not_before,omitempty"` NotAfter *Time `json:"not_after,omitempty"` SPKI *Span `json:"spki,omitempty"` Holder *string `json:"holder,omitempty"` IssuerName *string `json:"issuer_name,omitempty"` ValidAt [][3]any `json:"valid_at,omitempty"` } // AlgOut is an AlgorithmIdentifier: its object identifier and its // parameters, null when absent. type AlgOut struct { OID Span `json:"oid"` Params *Span `json:"params"` } func algOut(base []byte, oid, params []byte) AlgOut { return AlgOut{spanIn(base, oid), spanPtr(base, params)} } // SignerOut is a SignerInfo and the result of its check over the message of // the case and over otherMsg. type SignerOut struct { Cert int `json:"cert"` DigestAlg AlgOut `json:"digest_alg"` SigAlg AlgOut `json:"sig_alg"` SignedAttrs Span `json:"signed_attrs"` MessageDigest Span `json:"message_digest"` Signature Span `json:"signature"` Token *Span `json:"token"` Result string `json:"result"` OtherResult string `json:"other_result"` Holder *string `json:"holder,omitempty"` Issuer *string `json:"issuer,omitempty"` Seal *TokOut `json:"seal,omitempty"` } // SigOut is what cms.ParseSignature gives. type SigOut struct { Error string `json:"error,omitempty"` Certs []Span `json:"certs,omitempty"` OCSP []Span `json:"ocsp,omitempty"` Signers []SignerOut `json:"signers,omitempty"` } // TokOut is what cms.ParseToken gives, and Check over the subject of the // case. type TokOut struct { Error string `json:"error,omitempty"` Kind string `json:"kind,omitempty"` GenTime *Time `json:"gen_time,omitempty"` AccuracyUS *int64 `json:"accuracy_us,omitempty"` ImprintAlg *AlgOut `json:"imprint_alg,omitempty"` Imprint *Span `json:"imprint,omitempty"` TSA *Span `json:"tsa,omitempty"` SHA256 *bool `json:"imprint_sha256,omitempty"` Check *bool `json:"check,omitempty"` TSAHolder *string `json:"tsa_holder,omitempty"` SignerOK *bool `json:"signer_valid_at_gen_time,omitempty"` Latest *Time `json:"gen_time_plus_accuracy,omitempty"` } func resultText(r cms.Result) string { switch r { case cms.Valid: return "valid" case cms.Invalid: return "invalid" case cms.NotVerifiable: return "not verifiable" } panic("a result outside the three") } func kindOf(err error) string { switch { case errors.Is(err, cms.ErrForm): return "form" case errors.Is(err, cms.ErrAlgorithm): return "algorithm" } panic("an error of another kind: " + err.Error()) } // certOutcome is cms.ParseCert on b, with ValidAt around both ends when // validAt. func certOutcome(b []byte, validAt bool) CertOut { c, err := cms.ParseCert(b) if err != nil { if !strings.HasPrefix(err.Error(), "certificate: ") { panic(err) } return CertOut{Error: err.Error()} } if c.Hash != sha256.Sum256(b) || !bytes.Equal(c.Raw, b) { panic("the raw certificate") } out := CertOut{ Serial: spanPtr(b, c.Serial), Issuer: spanPtr(b, c.RawIssuer), Subject: spanPtr(b, c.RawSubject), SKI: spanPtr(b, c.SKI), NotBefore: timeOf(c.NotBefore), NotAfter: timeOf(c.NotAfter), SPKI: spanPtr(b, c.SPKI), Holder: ptr(c.Holder()), IssuerName: ptr(c.IssuerName()), } if !validAt { return out } for _, at := range []time.Time{ c.NotBefore.Add(-time.Second), c.NotBefore.Add(-time.Nanosecond), c.NotBefore, c.NotBefore.Add(time.Nanosecond), c.NotAfter.Add(-time.Nanosecond), c.NotAfter, c.NotAfter.Add(time.Nanosecond), c.NotAfter.Add(time.Second), } { out.ValidAt = append(out.ValidAt, [3]any{at.Unix(), at.Nanosecond(), c.ValidAt(at)}) } return out } // sigOutcome is cms.ParseSignature on b and the check of each signer over // message and over otherMsg; with corpus, the names of each certificate and // the token of each signer over its signature value. func sigOutcome(b, message []byte, corpus bool) SigOut { sd, err := cms.ParseSignature(b) if err != nil { if kindOf(err) != "form" || sd != nil { panic(err) } return SigOut{Error: err.Error()} } var out SigOut for _, c := range sd.Certs { out.Certs = append(out.Certs, spanIn(b, c.Raw)) } for _, o := range sd.OCSP { out.OCSP = append(out.OCSP, spanIn(b, o)) } for _, s := range sd.Signers { so := SignerOut{ Cert: slices.Index(sd.Certs, s.Cert), DigestAlg: algOut(b, s.DigestAlg.OID, s.DigestAlg.Params), SigAlg: algOut(b, s.SigAlg.OID, s.SigAlg.Params), SignedAttrs: spanIn(b, s.SignedAttrs), MessageDigest: spanIn(b, s.MessageDigest), Signature: spanIn(b, s.Signature), Token: spanPtr(b, s.Token), Result: resultText(s.Check(message)), OtherResult: resultText(s.Check(otherMsg)), } if so.Cert < 0 { panic("a signer whose certificate is not among the certificates") } if corpus { so.Holder, so.Issuer = ptr(s.Cert.Holder()), ptr(s.Cert.IssuerName()) if s.Token != nil { t := tokOutcome(s.Token, s.Signature, true) if tok, err := cms.ParseToken(s.Token); err == nil { t.SignerOK = ptr(s.Cert.ValidAt(tok.GenTime)) } so.Seal = &t } } out.Signers = append(out.Signers, so) } return out } // tokOutcome is cms.ParseToken on b and Check over subject. func tokOutcome(b, subject []byte, corpus bool) TokOut { tok, err := cms.ParseToken(b) if err != nil { if tok != nil { panic(err) } return TokOut{Error: err.Error(), Kind: kindOf(err)} } us := int64(tok.Accuracy / time.Microsecond) if time.Duration(us)*time.Microsecond != tok.Accuracy { panic("an accuracy that is not whole microseconds") } a := algOut(b, tok.ImprintAlg.OID, tok.ImprintAlg.Params) out := TokOut{ GenTime: timeOf(tok.GenTime), AccuracyUS: &us, ImprintAlg: &a, Imprint: spanPtr(b, tok.Imprint), TSA: spanPtr(b, tok.TSA.Raw), SHA256: ptr(tok.ImprintIsSHA256()), Check: ptr(tok.Check(subject)), Latest: timeOf(tok.GenTime.Add(tok.Accuracy)), } if corpus { out.TSAHolder = ptr(tok.TSA.Holder()) } return out } // --------------------------------------------------------------------------- // The edits // apply returns base with the edit e done, as cmstest.Edit does it. func apply(base []byte, e Edit) []byte { if e.Op == "raw" { out := bytes.Clone(base) out[*e.Arg] ^= 1 << *e.Bit return out } return cmstest.Edit(base, func(old []byte) []byte { switch e.Op { case "retag": return append([]byte{byte(*e.Arg)}, old[1:]...) case "remove": return nil case "dup": return append(bytes.Clone(old), old...) case "null": return cmstest.Null() case "append": return cmstest.TLV(old[0], append(slices.Clone(cmstest.Children(old)), cmstest.Null())...) case "reverse": k := slices.Clone(cmstest.Children(old)) slices.Reverse(k) return cmstest.TLV(old[0], k...) } c := slices.Clone(contentOf(old)) switch e.Op { case "flip": c[*e.Arg] ^= 1 << *e.Bit case "trunc": c = c[:len(c)-1] case "extend": c = append(c, byte(*e.Arg)) case "empty": c = nil default: panic("an edit " + e.Op) } return cmstest.TLV(old[0], c) }, e.Path...) } var retagTags = []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x0c, 0x13, 0x14, 0x16, 0x17, 0x18, 0x1a, 0x1e, 0x30, 0x31, 0x80, 0x81, 0x82, 0xa0, 0xa1, 0xa2, 0xa3} // edits returns about n edits of b, drawn with r from every node and every // operation that applies to it, in the order of the nodes, and then n/6 // flips of a bit anywhere in b, its tags and lengths included. func edits(r *mrand.Rand, name string, b []byte, n int) []Edit { var all []Edit var walk func(node []byte, path []int) walk = func(node []byte, path []int) { p := slices.Clone(path) other := retagTags[r.IntN(len(retagTags))] for other == node[0] { other = retagTags[r.IntN(len(retagTags))] } all = append(all, Edit{Base: name, Path: p, Op: "retag", Arg: ptr(int(other))}) if len(path) > 0 { for _, op := range []string{"remove", "dup", "null"} { all = append(all, Edit{Base: name, Path: p, Op: op}) } } if node[0]&0x20 != 0 { kids := cmstest.Children(node) all = append(all, Edit{Base: name, Path: p, Op: "append"}) if len(kids) > 1 { all = append(all, Edit{Base: name, Path: p, Op: "reverse"}) } for i, k := range kids { walk(k, append(slices.Clone(path), i)) } return } c := contentOf(node) all = append(all, Edit{Base: name, Path: p, Op: "extend", Arg: ptr(r.IntN(256))}) if len(c) > 0 { all = append(all, Edit{Base: name, Path: p, Op: "flip", Arg: ptr(r.IntN(len(c))), Bit: ptr(r.IntN(8))}, Edit{Base: name, Path: p, Op: "trunc"}, Edit{Base: name, Path: p, Op: "empty"}) } } walk(b, nil) if n < len(all) { keep := r.Perm(len(all))[:n] slices.Sort(keep) var out []Edit for _, i := range keep { out = append(out, all[i]) } all = out } for range n / 6 { all = append(all, Edit{Base: name, Op: "raw", Arg: ptr(r.IntN(len(b))), Bit: ptr(r.IntN(8))}) } for i := range all { s := sha256.Sum256(apply(b, all[i])) all[i].SHA256 = hx(s[:]) } return all } // --------------------------------------------------------------------------- // Helpers of the tests of internal/cms func path(parts ...any) []int { var out []int for _, p := range parts { switch x := p.(type) { case int: out = append(out, x) case []int: out = append(out, x...) } } return out } func firstSignerInfo(b []byte) []int { return path(cmstest.SignerInfosPath(b), 0) } func contentOf(b []byte) []byte { c, err := der.Content(b) if err != nil { panic(err) } return c } func sha1Sum(b []byte) []byte { s := sha1.Sum(b) return s[:] } func sha256Of(b []byte) []byte { s := sha256.Sum256(b) return s[:] } func cn(v []byte) []byte { return cmstest.ATV(cmstest.OIDCommonName, v) } func given(v []byte) []byte { return cmstest.ATV(cmstest.OIDGivenName, v) } func surname(v []byte) []byte { return cmstest.ATV(cmstest.OIDSurname, v) } func org(v []byte) []byte { return cmstest.ATV(cmstest.OIDOrganization, v) } // base128 appends the arc v in base 128, as the content of an object // identifier writes it. func base128(out []byte, v *big.Int) []byte { var digits []byte x := new(big.Int).Set(v) seven := big.NewInt(127) for { digits = append(digits, byte(new(big.Int).And(x, seven).Int64())) x.Rsh(x, 7) if x.Sign() == 0 { break } } for i := len(digits) - 1; i >= 0; i-- { d := digits[i] if i > 0 { d |= 0x80 } out = append(out, d) } return out } // wrapOID is the object identifier of oid with its last arc increased by // 2^bits: an identifier that a reader that turns arcs into integers of that // many bits would take for oid. func wrapOID(oid asn1.ObjectIdentifier, bits uint) []byte { c := base128(nil, big.NewInt(int64(oid[0]*40+oid[1]))) for _, a := range oid[2 : len(oid)-1] { c = base128(c, big.NewInt(int64(a))) } last := new(big.Int).Lsh(big.NewInt(1), bits) last.Add(last, big.NewInt(int64(oid[len(oid)-1]))) return cmstest.OIDBytes(base128(c, last)) } // --------------------------------------------------------------------------- // The generator type gen struct { r *mrand.Rand ecKey, ecKey2 *ecdsa.PrivateKey rsaKey *rsa.PrivateKey p384Key, p521Key *ecdsa.PrivateKey ana, luis, tsa cmstest.Signer } func TestWriteVectors(t *testing.T) { if *sourceFlag == "" || *outFlag == "" { t.Skip("run with -args -source COMMIT -out DIR") } cryptotest.SetGlobalRandom(t, 20261005) g := &gen{r: mrand.New(mrand.NewPCG(2026, 1005))} g.ecKey = cmstest.ECKey(elliptic.P256()) g.ecKey2 = cmstest.ECKey(elliptic.P256()) g.rsaKey = cmstest.RSAKey(2048) g.p384Key = cmstest.ECKey(elliptic.P384()) g.p521Key = cmstest.ECKey(elliptic.P521()) g.ana = cmstest.NewCert(cmstest.CertSpec{CN: "Ana López"}, g.ecKey) g.luis = cmstest.NewCert(cmstest.CertSpec{CN: "Luis Gómez"}, g.rsaKey) g.tsa = cmstest.NewCert(cmstest.CertSpec{CN: "TSA de prueba"}, g.ecKey2) sigs, algs := g.signatureFiles() files := []*vfile{ g.ecdsaFile(), g.rsaFile(), g.certFile(), sigs, algs, g.tokenFile(), g.mutationFile(), g.corpusFile(), } var dart bytes.Buffer dart.WriteString("// Generated by tool/cms_go_vectors_test.go: a part of each file\n" + "// test/vectors/cms_*.json, for the tests that also run compiled to\n" + "// JavaScript, where no file can be read: the cases of each section whose\n" + "// index is a multiple of the number that \"every\" gives for it, none when\n" + "// it gives none. Do not edit.\n") for _, f := range files { full := f.json(nil) path := filepath.Join(*outFlag, "cms_"+f.name+".json") if err := os.WriteFile(path, full, 0o644); err != nil { t.Fatal(err) } t.Logf("wrote %s, %d bytes", path, len(full)) part := f.json(f.every) fmt.Fprintf(&dart, "\n/// Part of test/vectors/cms_%s.json.\nconst cms%sJson = r'''\n%s''';\n", f.name, camel(f.name), part) } dpath := filepath.Join(*outFlag, "cms_vectors.g.dart") if err := os.WriteFile(dpath, dart.Bytes(), 0o644); err != nil { t.Fatal(err) } t.Logf("wrote %s, %d bytes", dpath, dart.Len()) } func camel(s string) string { parts := strings.Split(s, "_") for i, p := range parts { parts[i] = strings.ToUpper(p[:1]) + p[1:] } return strings.Join(parts, "") } // vfile is a file of vectors: its header and its sections, each a list of // cases, written one per line. type vfile struct { name, description string head [][2]any sections []section // every gives, for the sections that the tests compiled to JavaScript // run, the step of the cases they take. every map[string]int } // derCase is a case whose DER the dictionary of its file can write. type derCase interface { rawDER() []byte setDER(der any, sum string) } func (c *SigCase) rawDER() []byte { return c.raw } func (c *TokCase) rawDER() []byte { return c.raw } func (c *corpusSig) rawDER() []byte { return c.raw } func (c *corpusSeal) rawDER() []byte { return c.raw } func (c *SigCase) setDER(der any, sum string) { c.DER, c.Edit.SHA256 = der, sum } func (c *TokCase) setDER(der any, sum string) { c.DER, c.Edit.SHA256 = der, sum } func (c *corpusSig) setDER(der any, sum string) { c.DER, c.SHA256 = der, sum } func (c *corpusSeal) setDER(der any, sum string) { c.DER, c.SHA256 = der, sum } // certsIn returns the certificates that a signature or a token holds, as // the reader finds them: those of the profile, and the authority of each // token that reads. func certsIn(b []byte) [][]byte { var out [][]byte if sd, err := cms.ParseSignature(b); err == nil { for _, c := range sd.Certs { out = append(out, c.Raw) } for _, s := range sd.Signers { if s.Token != nil { out = append(out, certsIn(s.Token)...) } } } if tok, err := cms.ParseToken(b); err == nil { out = append(out, tok.TSA.Raw) } return out } // compress writes the DER of each case of f as a list of pieces, the hex of // bytes or the index of a certificate of the table certificates of the file, // with the SHA-256 of the DER: the certificates that two cases or more hold, // each once. func (f *vfile) compress() { var cands [][]byte seen := map[string]bool{} var cases []derCase for _, sec := range f.sections { for _, c := range sec.cases { if dc, ok := c.(derCase); ok && dc.rawDER() != nil { cases = append(cases, dc) for _, cert := range certsIn(dc.rawDER()) { if !seen[string(cert)] { seen[string(cert)] = true cands = append(cands, cert) } } } } } var table [][]byte for _, cert := range cands { n := 0 for _, c := range cases { if bytes.Contains(c.rawDER(), cert) { n++ } } if n >= 2 { table = append(table, cert) } } var hexes []string for _, cert := range table { hexes = append(hexes, hx(cert)) } f.head = append(f.head, [2]any{"certificates", hexes}) for _, c := range cases { b := c.rawDER() var pieces []any pos := 0 for { best, at := -1, len(b) for i, cert := range table { if j := bytes.Index(b[pos:], cert); j >= 0 && (pos+j < at || pos+j == at && len(cert) > len(table[best])) { best, at = i, pos+j } } if best < 0 { break } if at > pos { pieces = append(pieces, hx(b[pos:at])) } pieces = append(pieces, best) pos = at + len(table[best]) } if pos < len(b) { pieces = append(pieces, hx(b[pos:])) } if len(pieces) == 0 || len(pieces) == 1 && pieces[0] == hx(b) { continue } sum := sha256.Sum256(b) c.setDER(pieces, hx(sum[:])) } } type section struct { name string cases []any } func (f *vfile) json(every map[string]int) []byte { var w bytes.Buffer w.WriteString("{\n") head := [][2]any{ {"spec", specLabel}, {"generator", "tool/cms_go_vectors_test.go, " + runtime.Version()}, {"source", "datekeys-go " + *sourceFlag}, {"description", f.description}, } if every != nil { // Every section, 0 for those that the part leaves out. steps := map[string]int{} for _, s := range f.sections { steps[s.name] = every[s.name] } head = append(head, [2]any{"every", steps}) } for _, kv := range append(head, f.head...) { fmt.Fprintf(&w, " %q: %s,\n", kv[0], marshal(kv[1])) } for i, s := range f.sections { step := 1 if every != nil { step = every[s.name] } fmt.Fprintf(&w, " %q: [", s.name) first := true for j, c := range s.cases { if step == 0 || j%step != 0 { continue } if !first { w.WriteByte(',') } first = false w.WriteString("\n ") w.Write(marshal(c)) } if !first { w.WriteString("\n ") } w.WriteByte(']') if i < len(f.sections)-1 { w.WriteByte(',') } w.WriteByte('\n') } w.WriteString("}\n") var check any if err := json.Unmarshal(w.Bytes(), &check); err != nil { panic(fmt.Sprintf("the JSON of %s does not parse: %v", f.name, err)) } if bytes.Contains(w.Bytes(), []byte("'''")) { panic("the JSON holds three quotes") } return w.Bytes() } // marshal is json.Marshal with every rune outside ASCII, and the quote ', // escaped: the JSON is ASCII, and a raw string of Dart holds it. func marshal(v any) []byte { b, err := json.Marshal(v) if err != nil { panic(err) } var out bytes.Buffer for _, r := range string(b) { switch { case r == '\'': out.WriteString(`'`) case r < 0x80: out.WriteRune(r) case r < 0x10000: fmt.Fprintf(&out, `\u%04x`, r) default: r1, r2 := utf16.EncodeRune(r) fmt.Fprintf(&out, `\u%04x\u%04x`, r1, r2) } } return out.Bytes() } // sourceOIDs reads the object identifiers that the source of internal/cms // declares, as name = oid("dotted"), and returns the content of the DER of // each, by its name. func sourceOIDs() map[string]string { out := map[string]string{} re := regexp.MustCompile(`(?m)^\s*(oid\w+)\s*=\s*oid\("([0-9.]+)"\)`) for _, name := range []string{"cms.go", "cert.go", "verify.go"} { src, err := os.ReadFile(filepath.Join("..", "internal", "cms", name)) if err != nil { panic(err) } for _, m := range re.FindAllStringSubmatch(string(src), -1) { var arcs asn1.ObjectIdentifier for _, a := range strings.Split(m[2], ".") { var v int if _, err := fmt.Sscan(a, &v); err != nil { panic(err) } arcs = append(arcs, v) } out[m[1]] = hx(contentOf(cmstest.OID(arcs))) } } if len(out) != 30 { panic(fmt.Sprintf("%d object identifiers in the source of internal/cms", len(out))) } return out } // --------------------------------------------------------------------------- // cms_ecdsa.json type curveOut struct { Name string `json:"name"` Bits int `json:"bits"` P string `json:"p"` N string `json:"n"` B string `json:"b"` Gx string `json:"gx"` Gy string `json:"gy"` } type pointCase struct { Name string `json:"name"` Curve string `json:"curve"` Point string `json:"point"` OK bool `json:"ok"` } type ecKeyOut struct { Name string `json:"name"` Curve string `json:"curve"` Point string `json:"point"` } type ecdsaCase struct { Name string `json:"name"` Key int `json:"key"` Hash string `json:"hash"` Sig string `json:"sig"` Valid bool `json:"valid"` } func uncompressed(k *ecdsa.PublicKey) []byte { return cmstest.Uncompressed(k) } // sigRS is the ECDSA-Sig-Value of r and s, written with Go's asn1, which // writes negative values in two's complement. func sigRS(r, s *big.Int) []byte { b, err := asn1.Marshal(struct{ R, S *big.Int }{r, s}) if err != nil { panic(err) } return b } func parseRS(sig []byte) (*big.Int, *big.Int) { var v struct{ R, S *big.Int } if _, err := asn1.Unmarshal(sig, &v); err != nil { panic(err) } return v.R, v.S } func (g *gen) ecdsaFile() *vfile { f := &vfile{name: "ecdsa", description: "The curves P-256, P-384 and P-521 of Go's crypto/elliptic; points read by crypto/ecdsa.ParseUncompressedPublicKey (ok when it reads them); and the keys and signatures of verify, each checked by crypto/ecdsa.VerifyASN1 on the hash given, as internal/cms checks them.", every: map[string]int{"curves": 1, "points": 1, "keys": 1, "verify": 2}} curves := []elliptic.Curve{elliptic.P256(), elliptic.P384(), elliptic.P521()} var cs, points, keys, verify []any for _, c := range curves { p := c.Params() cs = append(cs, curveOut{p.Name, p.BitSize, hx(p.P.Bytes()), hx(p.N.Bytes()), hx(p.B.Bytes()), hx(p.Gx.Bytes()), hx(p.Gy.Bytes())}) } addPoint := func(name string, c elliptic.Curve, point []byte) { _, err := ecdsa.ParseUncompressedPublicKey(c, point) points = append(points, pointCase{name, c.Params().Name, hx(point), err == nil}) } hashes := []crypto.Hash{crypto.SHA256, crypto.SHA384, crypto.SHA512} for _, c := range curves { p := c.Params() size := (p.BitSize + 7) / 8 fill := func(v *big.Int) []byte { return v.FillBytes(make([]byte, size)) } raw := func(d *big.Int) *ecdsa.PrivateKey { k, err := ecdsa.ParseRawPrivateKey(c, fill(d)) if err != nil { panic(err) } return k } random := cmstest.ECKey(c) one := raw(big.NewInt(1)) minus := raw(new(big.Int).Sub(p.N, big.NewInt(1))) two := raw(big.NewInt(2)) pt := uncompressed(&random.PublicKey) x, y := pt[1:1+size], pt[1+size:] // The points. addPoint("a key", c, pt) addPoint("G", c, uncompressed(&one.PublicKey)) addPoint("-G", c, uncompressed(&minus.PublicKey)) addPoint("y + 1, off the curve", c, append(append([]byte{4}, x...), fill(new(big.Int).Add(new(big.Int).SetBytes(y), big.NewInt(1)))...)) addPoint("x = p", c, append(append([]byte{4}, fill(p.P)...), y...)) addPoint("y = p", c, append(append([]byte{4}, x...), fill(p.P)...)) addPoint("y + p, which fits", c, func() []byte { v := new(big.Int).Add(new(big.Int).SetBytes(y), p.P) if v.BitLen() > size*8 { return append(append([]byte{4}, x...), fill(p.P)...) } return append(append([]byte{4}, x...), fill(v)...) }()) addPoint("all ones", c, append([]byte{4}, bytes.Repeat([]byte{0xff}, 2*size)...)) addPoint("zeros", c, append([]byte{4}, make([]byte, 2*size)...)) addPoint("compressed", c, cmstest.Compressed(&random.PublicKey)) addPoint("hybrid, 0x06 or 0x07", c, append([]byte{6 | y[len(y)-1]&1}, pt[1:]...)) addPoint("the infinity", c, []byte{0}) addPoint("empty", c, nil) addPoint("a byte short", c, pt[:len(pt)-1]) addPoint("a byte more", c, append(slices.Clone(pt), 0)) addPoint("the prefix 0x05", c, append([]byte{5}, pt[1:]...)) // The keys and the signatures. base := len(keys) for _, k := range []struct { name string key *ecdsa.PrivateKey }{{"a key", random}, {"d = 1, Q = G", one}, {"d = n - 1, Q = -G", minus}, {"d = 2", two}} { keys = append(keys, ecKeyOut{p.Name + ", " + k.name, p.Name, hx(uncompressed(&k.key.PublicKey))}) } add := func(name string, key int, hash, sig []byte) { pub, err := ecdsa.ParseUncompressedPublicKey(c, mustHex(keys[key].(ecKeyOut).Point)) if err != nil { panic(err) } verify = append(verify, ecdsaCase{p.Name + ", " + name, key, hx(hash), hx(sig), ecdsa.VerifyASN1(pub, hash, sig)}) } sign := func(k *ecdsa.PrivateKey, hash []byte) []byte { s, err := ecdsa.SignASN1(rand.Reader, k, hash) if err != nil { panic(err) } return s } for ki, k := range []*ecdsa.PrivateKey{random, one, minus, two} { for _, h := range hashes { hash := sum(h, []byte(fmt.Sprintf("message %d of %s", ki, p.Name))) sig := sign(k, hash) add(fmt.Sprintf("%s over %s, key %d", "valid", h, ki), base+ki, hash, sig) if ki == 1 && h == crypto.SHA256 { // Q = G and s = 1: R = (e + r) G, the infinity when e = n - r. k := big.NewInt(12345) hl := min(size, 64) add("Q = G, e + r = n: u1 G + u2 Q is the infinity", base+ki, k.FillBytes(make([]byte, hl)), sigRS(new(big.Int).Sub(p.N, k), big.NewInt(1))) add("Q = G, e + r = n + 1", base+ki, big.NewInt(12346).FillBytes(make([]byte, hl)), sigRS(new(big.Int).Sub(p.N, k), big.NewInt(1))) } if ki == 2 && h == crypto.SHA256 { // Q = -G: R = (e - r) s^-1 G, the infinity when e = r. k := big.NewInt(98765) hl := min(size, 64) add("Q = -G, e = r: u1 G + u2 Q is the infinity", base+ki, k.FillBytes(make([]byte, hl)), sigRS(k, big.NewInt(7))) add("Q = -G, e = r + 1", base+ki, big.NewInt(98766).FillBytes(make([]byte, hl)), sigRS(k, big.NewInt(7))) } if ki != 0 { continue } r, s := parseRS(sig) n := p.N add(h.String()+": s above n/2, n - s", base, hash, sigRS(r, new(big.Int).Sub(n, s))) add(h.String()+": s + n", base, hash, sigRS(r, new(big.Int).Add(s, n))) add(h.String()+": r + n", base, hash, sigRS(new(big.Int).Add(r, n), s)) add(h.String()+": r and s swapped", base, hash, sigRS(s, r)) other := slices.Clone(hash) other[0] ^= 1 add(h.String()+": the first byte of the hash flipped", base, other, sig) last := slices.Clone(hash) last[len(last)-1] ^= 1 add(h.String()+": the last byte of the hash flipped", base, last, sig) if h == crypto.SHA256 { add("r = 0", base, hash, sigRS(big.NewInt(0), s)) add("s = 0", base, hash, sigRS(r, big.NewInt(0))) add("r = n", base, hash, sigRS(n, s)) add("s = n", base, hash, sigRS(r, n)) add("r = n - 1", base, hash, sigRS(new(big.Int).Sub(n, big.NewInt(1)), s)) add("s = 1", base, hash, sigRS(r, big.NewInt(1))) add("r negative", base, hash, sigRS(new(big.Int).Neg(r), s)) add("s negative", base, hash, sigRS(r, new(big.Int).Neg(s))) add("r - n, negative", base, hash, sigRS(new(big.Int).Sub(r, n), s)) rb, sb := r.Bytes(), s.Bytes() integer := func(c []byte) []byte { return cmstest.TLV(0x02, c) } add("r with a zero byte in front", base, hash, cmstest.Seq(integer(append([]byte{0, 0}, rb...)), cmstest.BigInt(s))) add("s with a zero byte in front", base, hash, cmstest.Seq(cmstest.BigInt(r), integer(append([]byte{0, 0}, sb...)))) add("r without the zero byte of its sign", base, hash, cmstest.Seq(integer(rb), cmstest.BigInt(s))) add("r with 0xff in front", base, hash, cmstest.Seq(integer(append([]byte{0xff}, rb...)), cmstest.BigInt(s))) add("r empty", base, hash, cmstest.Seq(integer(nil), cmstest.BigInt(s))) add("a byte after the SEQUENCE", base, hash, append(slices.Clone(sig), 0)) add("a byte after s, inside", base, hash, cmstest.Seq(cmstest.BigInt(r), cmstest.BigInt(s), []byte{0})) add("a third INTEGER", base, hash, cmstest.Seq(cmstest.BigInt(r), cmstest.BigInt(s), cmstest.Int(1))) add("only r", base, hash, cmstest.Seq(cmstest.BigInt(r))) add("empty", base, hash, nil) add("an empty SEQUENCE", base, hash, cmstest.Seq()) add("truncated", base, hash, sig[:len(sig)-1]) add("a SET", base, hash, append([]byte{0x31}, sig[1:]...)) add("the SEQUENCE primitive", base, hash, append([]byte{0x10}, sig[1:]...)) add("r as an OCTET STRING", base, hash, cmstest.Seq(cmstest.Octets(rb), cmstest.BigInt(s))) add("r as an ENUMERATED", base, hash, cmstest.Seq(cmstest.TLV(0x0a, cmstest.BigInt(r)[2:]), cmstest.BigInt(s))) add("r with a tag of two bytes", base, hash, cmstest.Seq(append([]byte{0x1f, 0x02}, cmstest.BigInt(r)[1:]...), cmstest.BigInt(s))) inner := append(cmstest.BigInt(r), cmstest.BigInt(s)...) add("a length in long form under 128", base, hash, append([]byte{0x30, 0x81, byte(len(inner))}, inner...)) add("a length with a leading zero", base, hash, append([]byte{0x30, 0x82, 0, byte(len(inner))}, inner...)) add("an indefinite length", base, hash, append(append([]byte{0x30, 0x80}, inner...), 0, 0)) add("a length of five bytes", base, hash, append([]byte{0x30, 0x85, 0, 0, 0, 0, byte(len(inner))}, inner...)) add("a length one too long", base, hash, append([]byte{0x30, byte(len(inner) + 1)}, inner...)) add("a length one too short", base, hash, append([]byte{0x30, byte(len(inner) - 1)}, inner...)) long := sum(crypto.SHA512, []byte("a long hash")) sl := sign(random, long) add("a hash of 64 bytes", base, long, sl) tail := slices.Clone(long) tail[len(tail)-1] ^= 0x80 add("a hash of 64 bytes, its last byte flipped", base, tail, sl) short := sum(crypto.SHA256, []byte("a short hash"))[:20] add("a hash of 20 bytes", base, short, sign(random, short)) add("an empty hash", base, nil, sign(random, nil)) } } } } f.sections = []section{{"curves", cs}, {"points", points}, {"keys", keys}, {"verify", verify}} return f } func mustHex(s string) []byte { b, err := hex.DecodeString(s) if err != nil { panic(err) } return b } func sum(h crypto.Hash, b []byte) []byte { switch h { case crypto.SHA1: s := sha1.Sum(b) return s[:] case crypto.SHA384: s := sha512.Sum384(b) return s[:] case crypto.SHA512: s := sha512.Sum512(b) return s[:] } s := sha256.Sum256(b) return s[:] } // --------------------------------------------------------------------------- // cms_rsa.json type rsaKeyOut struct { Name string `json:"name"` N string `json:"n"` E int `json:"e"` Bits int `json:"bits"` } type rsaCase struct { Name string `json:"name"` Key int `json:"key"` Scheme string `json:"scheme"` Hash string `json:"hash"` Digest string `json:"digest"` Sig string `json:"sig"` Valid bool `json:"valid"` } // rsaKeyWithE is a key of bits bits with the exponent e, its primes from // crypto/rand.Prime. func rsaKeyWithE(bits, e int) *rsa.PrivateKey { for { p, err := rand.Prime(rand.Reader, (bits+1)/2) if err != nil { panic(err) } q, err := rand.Prime(rand.Reader, bits/2) if err != nil { panic(err) } n := new(big.Int).Mul(p, q) one := big.NewInt(1) pm, qm := new(big.Int).Sub(p, one), new(big.Int).Sub(q, one) be := big.NewInt(int64(e)) if p.Cmp(q) == 0 || n.BitLen() != bits || new(big.Int).GCD(nil, nil, be, pm).Cmp(one) != 0 || new(big.Int).GCD(nil, nil, be, qm).Cmp(one) != 0 { continue } d := new(big.Int).ModInverse(be, new(big.Int).Mul(pm, qm)) return &rsa.PrivateKey{PublicKey: rsa.PublicKey{N: n, E: e}, D: d, Primes: []*big.Int{p, q}} } } // rawSign is em^d mod n in k bytes: a signature of any encoding em. func rawSign(k *rsa.PrivateKey, em []byte) []byte { m := new(big.Int).SetBytes(em) if m.Cmp(k.N) >= 0 { panic("an encoding of n or more") } return new(big.Int).Exp(m, k.D, k.N).FillBytes(make([]byte, k.Size())) } var digestInfo = map[crypto.Hash][]byte{ crypto.SHA256: mustHex("3031300d060960864801650304020105000420"), crypto.SHA384: mustHex("3041300d060960864801650304020205000430"), crypto.SHA512: mustHex("3051300d060960864801650304020305000440"), } // pkcs1EM is EMSA-PKCS1-v1_5 of hashed in k bytes, built here; the cases // change it. func pkcs1EM(k int, h crypto.Hash, hashed []byte) []byte { t := append(slices.Clone(digestInfo[h]), hashed...) em := make([]byte, k) em[1] = 1 for i := 2; i < k-len(t)-1; i++ { em[i] = 0xff } copy(em[k-len(t):], t) return em } func mgf1(h crypto.Hash, seed []byte, n int) []byte { var out []byte for c := uint32(0); len(out) < n; c++ { out = append(out, sum(h, append(slices.Clone(seed), byte(c>>24), byte(c>>16), byte(c>>8), byte(c)))...) } return out[:n] } // pssEM is EMSA-PSS-ENCODE of mHash with salt, for a modulus of modBits // bits, built here with the hash h and the hash of the mask mgfHash; the // cases change it. It returns the encoding in emLen bytes. func pssEM(modBits int, h, mgfHash crypto.Hash, mHash, salt []byte) []byte { emBits := modBits - 1 emLen := (emBits + 7) / 8 hLen := h.Size() m := append(append(make([]byte, 8), mHash...), salt...) H := sum(h, m) db := make([]byte, emLen-hLen-1) db[len(db)-len(salt)-1] = 1 copy(db[len(db)-len(salt):], salt) mask := mgf1(mgfHash, H, len(db)) for i := range db { db[i] ^= mask[i] } db[0] &= 0xff >> (8*emLen - emBits) return append(append(db, H...), 0xbc) } func (g *gen) rsaFile() *vfile { f := &vfile{name: "rsa", description: "RSA keys and signatures, each checked as internal/cms checks it (valid: the first signature of each scheme with each key, over SHA-256; verify: the others): rsa.VerifyPKCS1v15 for pkcs1, rsa.VerifyPSS with SaltLength the size of the hash for pss, valid when it returns no error. digest is the hash that is signed. digest_info is the DigestInfo of RSASSA-PKCS1-v1_5 up to the hash, as Go writes it: the bytes before the hash in a signature of pkcs1 opened with the public key.", every: map[string]int{"keys": 1, "valid": 1, "verify": 6}} type key struct { name string k *rsa.PrivateKey } keys := []key{ {"2048 bits, e = 65537", g.rsaKey}, {"2049 bits, e = 65537", cmstest.RSAKey(2049)}, {"3072 bits, e = 65537", cmstest.RSAKey(3072)}, {"4096 bits, e = 65537", cmstest.RSAKey(4096)}, {"2048 bits, e = 3", rsaKeyWithE(2048, 3)}, {"2048 bits, e = 2^31 - 1", rsaKeyWithE(2048, 1<<31-1)}, {"4095 bits, e = 3", rsaKeyWithE(4095, 3)}, } var ks, cases []any for _, k := range keys { ks = append(ks, rsaKeyOut{k.name, hx(k.k.N.Bytes()), k.k.E, k.k.N.BitLen()}) } verify := func(pub *rsa.PublicKey, scheme string, h crypto.Hash, digest, sig []byte) bool { if scheme == "pss" { return rsa.VerifyPSS(pub, h, digest, sig, &rsa.PSSOptions{SaltLength: h.Size(), Hash: h}) == nil } return rsa.VerifyPKCS1v15(pub, h, digest, sig) == nil } var valid []any add := func(name string, ki int, scheme string, h crypto.Hash, digest, sig []byte) bool { k := keys[ki].k v := verify(&k.PublicKey, scheme, h, digest, sig) c := rsaCase{keys[ki].name + ", " + name, ki, scheme, h.String(), hx(digest), hx(sig), v} // The first signature of each scheme with each key, over SHA-256. if v && h == crypto.SHA256 && (name == "PKCS #1 v1.5, SHA-256" || name == "PSS, SHA-256") { valid = append(valid, c) } else { cases = append(cases, c) } return v } mustValid := func(v bool, what string) { if !v { panic("not valid: " + what) } } hashes := []crypto.Hash{crypto.SHA256, crypto.SHA384, crypto.SHA512} digestInfoOut := map[string]string{} for ki, kk := range keys { k := kk.k size := k.Size() // Every defect on the keys of 2048 bits with e = 65537 and e = 3, and // on the key of 2049 bits, whose encoding of PSS is a byte shorter // than the modulus; the others are checked valid and with a bit // flipped. full := ki == 0 || ki == 1 || ki == 4 for _, h := range hashes { if !full && h == crypto.SHA384 { continue } digest := sum(h, []byte(fmt.Sprintf("message of key %d", ki))) // PKCS #1 v1.5, signed by Go when e is 65537, by hand otherwise. var sig []byte if k.E == 65537 { s, err := rsa.SignPKCS1v15(nil, k, h, digest) if err != nil { panic(err) } sig = s } else { sig = rawSign(k, pkcs1EM(size, h, digest)) } mustValid(add("PKCS #1 v1.5, "+h.String(), ki, "pkcs1", h, digest, sig), "pkcs1") if ki == 0 { em := new(big.Int).Exp(new(big.Int).SetBytes(sig), big.NewInt(int64(k.E)), k.N).FillBytes(make([]byte, size)) digestInfoOut[h.String()] = hx(em[size-h.Size()-19 : size-h.Size()]) } // PSS, with a salt of the length of the hash. var pss []byte if k.E == 65537 { s, err := rsa.SignPSS(rand.Reader, k, h, digest, &rsa.PSSOptions{SaltLength: h.Size()}) if err != nil { panic(err) } pss = s } else { salt := sum(h, []byte("salt"))[:h.Size()] em := pssEM(k.N.BitLen(), h, h, digest, salt) pss = rawSign(k, append(make([]byte, size-len(em)), em...)) } mustValid(add("PSS, "+h.String(), ki, "pss", h, digest, pss), "pss") add("PKCS #1 v1.5 checked as PSS, "+h.String(), ki, "pss", h, digest, sig) add("PSS checked as PKCS #1 v1.5, "+h.String(), ki, "pkcs1", h, digest, pss) flipped := slices.Clone(sig) flipped[size/2] ^= 1 add("PKCS #1 v1.5, a bit flipped, "+h.String(), ki, "pkcs1", h, digest, flipped) pflipped := slices.Clone(pss) pflipped[size-1] ^= 1 add("PSS, a bit flipped, "+h.String(), ki, "pss", h, digest, pflipped) other := slices.Clone(digest) other[0] ^= 1 add("PKCS #1 v1.5, another digest, "+h.String(), ki, "pkcs1", h, other, sig) add("PSS, another digest, "+h.String(), ki, "pss", h, other, pss) add("PKCS #1 v1.5, a zero byte in front, "+h.String(), ki, "pkcs1", h, digest, append([]byte{0}, sig...)) if h != crypto.SHA256 || !full { continue } // The length of the signature, and its integer. add("PSS, a zero byte in front", ki, "pss", h, digest, append([]byte{0}, pss...)) add("PKCS #1 v1.5, the last byte cut", ki, "pkcs1", h, digest, sig[:size-1]) add("PKCS #1 v1.5, empty", ki, "pkcs1", h, digest, nil) nb := k.N.FillBytes(make([]byte, size)) add("PKCS #1 v1.5, the signature n", ki, "pkcs1", h, digest, nb) add("PSS, the signature n", ki, "pss", h, digest, nb) plus := new(big.Int).Add(k.N, new(big.Int).SetBytes(sig)) if plus.BitLen() <= size*8 { add("PKCS #1 v1.5, the signature plus n", ki, "pkcs1", h, digest, plus.FillBytes(make([]byte, size))) } add("PKCS #1 v1.5, n - 1", ki, "pkcs1", h, digest, new(big.Int).Sub(k.N, big.NewInt(1)).FillBytes(make([]byte, size))) add("PKCS #1 v1.5, zero", ki, "pkcs1", h, digest, make([]byte, size)) add("PKCS #1 v1.5, one", ki, "pkcs1", h, digest, big.NewInt(1).FillBytes(make([]byte, size))) add("PSS, zero", ki, "pss", h, digest, make([]byte, size)) // A signature that starts with a zero byte, written without it. for i := 0; ; i++ { d := sum(h, []byte(fmt.Sprintf("message %d of key %d", i, ki))) s := rawSign(k, pkcs1EM(size, h, d)) if s[0] != 0 { continue } mustValid(add("PKCS #1 v1.5 that starts with zero", ki, "pkcs1", h, d, s), "zero") add("PKCS #1 v1.5 that starts with zero, without it", ki, "pkcs1", h, d, s[1:]) break } // Encodings of PKCS #1 v1.5 built by hand, each with one defect. em := pkcs1EM(size, h, digest) edit := func(name string, f func(em []byte) []byte) { e := f(slices.Clone(em)) if new(big.Int).SetBytes(e).Cmp(k.N) >= 0 { return } add("PKCS #1 v1.5 encoding, "+name, ki, "pkcs1", h, digest, rawSign(k, e)) } edit("as Go writes it", func(e []byte) []byte { return e }) edit("a zero inside the padding", func(e []byte) []byte { e[20] = 0; return e }) edit("0xfe in the padding", func(e []byte) []byte { e[3] = 0xfe; return e }) edit("block type 2", func(e []byte) []byte { e[1] = 2; return e }) edit("block type 0", func(e []byte) []byte { e[1] = 0; return e }) edit("01 01 in front", func(e []byte) []byte { e[0] = 1; return e }) edit("no zero after the padding", func(e []byte) []byte { e[size-len(digestInfo[h])-h.Size()-1] = 0xff; return e }) edit("the padding two bytes short, two zeros at the end", func(e []byte) []byte { t := e[size-len(digestInfo[h])-h.Size()-1:] copy(e[size-len(t)-2:], t) e[size-2], e[size-1] = 0, 0 return e }) edit("the padding eight bytes, zeros before the DigestInfo", func(e []byte) []byte { for i := 10; i < size-len(digestInfo[h])-h.Size(); i++ { e[i] = 0 } return e }) edit("the DigestInfo without NULL", func(e []byte) []byte { t := append(mustHex("302f300b0609608648016503040201"+"0420"), digest...) out := make([]byte, size) out[1] = 1 for i := 2; i < size-len(t)-1; i++ { out[i] = 0xff } copy(out[size-len(t):], t) return out }) edit("the DigestInfo of SHA-384 with this hash", func(e []byte) []byte { e[size-h.Size()-19+14] = 2 return e }) edit("the hash a byte short", func(e []byte) []byte { t := slices.Clone(e[size-h.Size()-19:]) t[18]-- t = t[:len(t)-1] out := make([]byte, size) out[1] = 1 for i := 2; i < size-len(t)-1; i++ { out[i] = 0xff } copy(out[size-len(t):], t) return out }) // Encodings of PSS built by hand, each with one defect. salt := sum(h, []byte("salt")) pssEdit := func(name string, e []byte) { full := append(make([]byte, size-len(e)), e...) if new(big.Int).SetBytes(full).Cmp(k.N) >= 0 { return } add("PSS encoding, "+name, ki, "pss", h, digest, rawSign(k, full)) } bits := k.N.BitLen() pssEdit("built here", pssEM(bits, h, h, digest, salt)) for _, n := range []int{0, 20, 31, 33, 48} { pssEdit(fmt.Sprintf("a salt of %d bytes", n), pssEM(bits, h, h, digest, bytes.Repeat([]byte{7}, n))) } for _, sl := range []int{0, 20, 31, 33, rsa.PSSSaltLengthAuto} { s, err := rsa.SignPSS(rand.Reader, k, h, digest, &rsa.PSSOptions{SaltLength: sl}) if err == nil { add(fmt.Sprintf("PSS of Go with the salt length %d", sl), ki, "pss", h, digest, s) } } pssEdit("MGF1 with SHA-1", pssEM(bits, h, crypto.SHA1, digest, salt)) pssEdit("MGF1 with SHA-512", pssEM(bits, h, crypto.SHA512, digest, salt)) bad := pssEM(bits, h, h, digest, salt) bad[len(bad)-1] = 0xbb pssEdit("the trailer 0xbb", bad) top := pssEM(bits, h, h, digest, salt) top[0] |= 0x80 pssEdit("the top bit set", top) hb := pssEM(bits, h, h, digest, salt) hb[len(hb)-2] ^= 1 pssEdit("H flipped", hb) // DB with its separator moved: the encoding of a salt of another // length whose mask is that of this H. sep := pssEM(bits, h, h, digest, salt) emLen := len(sep) mask := mgf1(h, sep[emLen-h.Size()-1:emLen-1], emLen-h.Size()-1) for _, flip := range []int{emLen - h.Size() - 1 - h.Size() - 1, 0, 5} { e := slices.Clone(sep) db := e[:emLen-h.Size()-1] plain := make([]byte, len(db)) for i := range db { plain[i] = db[i] ^ mask[i] } plain[0] &= 0xff >> (8*emLen - (bits - 1)) plain[flip] ^= 3 for i := range db { db[i] = plain[i] ^ mask[i] } db[0] &= 0xff >> (8*emLen - (bits - 1)) pssEdit(fmt.Sprintf("DB changed at %d", flip), e) } if bits%8 == 1 { // The encryption of k bytes holds emLen = k - 1 bytes and a // zero byte in front of them, which must be zero. e := pssEM(bits, h, h, digest, salt) full := append([]byte{1}, e...) if new(big.Int).SetBytes(full).Cmp(k.N) < 0 { add("PSS encoding with 0x01 in front", ki, "pss", h, digest, rawSign(k, full)) } } } } f.head = [][2]any{{"digest_info", digestInfoOut}} f.sections = []section{{"keys", ks}, {"valid", valid}, {"verify", cases}} return f } // --------------------------------------------------------------------------- // cms_certs.json // CertCase is a certificate, given or as an edit of a base, and what // cms.ParseCert gives. type CertCase struct { Name string `json:"name,omitempty"` DER string `json:"der,omitempty"` Edit CertOut } type named struct { name string b []byte } func (g *gen) cert(spec cmstest.CertSpec) []byte { return cmstest.NewCert(spec, g.ecKey).Cert.Raw } func (g *gen) certFile() *vfile { f := &vfile{name: "certs", description: "cms.ParseCert: the error, or the fields (spans of serialNumber, issuer, subject, the subjectKeyIdentifier when there is one, and the SPKI), notBefore and notAfter, Holder, IssuerName, and ValidAt at instants around both ends. named: the certificates of the tests of internal/cms (cert_test.go), and others; bases and edits: the bases edited node by node, as cmstest.Edit does it, with the operations of cms_mutations.json.", every: map[string]int{"named": 4, "bases": 1, "edits": 9}} var cases []named add := func(name string, b []byte) { cases = append(cases, named{name, b}) } cert := g.cert spec := cmstest.CertSpec{CN: "Ana López", From: time.Date(2021, 2, 3, 4, 5, 6, 0, time.UTC), To: time.Date(2051, 2, 3, 4, 5, 6, 0, time.UTC), Serial: cmstest.Int(0x1234)} raw := cmstest.NewCert(spec, g.ecKey).Cert.Raw add("a certificate field by field", raw) tbs := func(i int) []int { return []int{0, i} } time1 := cmstest.UTCTime("200101000000Z") for _, c := range []named{ {"a SET", cmstest.Edit(raw, cmstest.Retag(0x31))}, {"a fourth element", cmstest.Edit(raw, cmstest.Append(cmstest.Null()))}, {"tbsCertificate as a SET", cmstest.Edit(raw, cmstest.Retag(0x31), 0)}, {"signatureAlgorithm as a SET", cmstest.Edit(raw, cmstest.Retag(0x31), 1)}, {"the signature as an OCTET STRING", cmstest.Edit(raw, cmstest.Retag(0x04), 2)}, {"no version: a certificate of v1", cert(cmstest.CertSpec{NoVersion: true})}, {"version 1 with extensions", cert(cmstest.CertSpec{NoVersion: true, After: [][]byte{cmstest.Null()}})}, {"the version written as 1", cert(cmstest.CertSpec{Version: cmstest.TLV(0xa0, cmstest.Int(0))})}, {"the version written as 2", cert(cmstest.CertSpec{Version: cmstest.TLV(0xa0, cmstest.Int(1))})}, {"the version as [1]", cert(cmstest.CertSpec{Version: cmstest.TLV(0xa1, cmstest.Int(2))})}, {"the version of two INTEGERs", cert(cmstest.CertSpec{Version: cmstest.TLV(0xa0, cmstest.Int(2), cmstest.Int(2))})}, {"the version 3 as a negative INTEGER", cert(cmstest.CertSpec{Version: cmstest.TLV(0xa0, cmstest.Int(-2))})}, {"the version 2 written in two bytes", cert(cmstest.CertSpec{Version: cmstest.TLV(0xa0, cmstest.IntBytes([]byte{0, 2}))})}, {"a tbsCertificate without its SPKI", cmstest.Edit(raw, func(b []byte) []byte { return cmstest.Seq(cmstest.Children(b)[:6]...) }, 0)}, {"the serial as an OCTET STRING", cert(cmstest.CertSpec{Serial: cmstest.Octets([]byte{1})})}, {"the signature of tbs as a SET", cmstest.Edit(raw, cmstest.Retag(0x31), tbs(2)...)}, {"the issuer as a SET", cmstest.Edit(raw, cmstest.Retag(0x31), tbs(3)...)}, {"the validity as a SET", cmstest.Edit(raw, cmstest.Retag(0x31), tbs(4)...)}, {"the subject as a SET", cmstest.Edit(raw, cmstest.Retag(0x31), tbs(5)...)}, {"the SPKI as a SET", cmstest.Edit(raw, cmstest.Retag(0x31), tbs(6)...)}, {"a validity of three times", cmstest.Edit(raw, cmstest.Append(time1), tbs(4)...)}, {"a validity of one time", cmstest.Edit(raw, cmstest.Replace(cmstest.Seq(time1)), tbs(4)...)}, {"notBefore with a fraction", cert(cmstest.CertSpec{NotBefore: cmstest.GeneralizedTime("20200101000000.5Z")})}, {"notAfter with a fraction", cert(cmstest.CertSpec{NotAfter: cmstest.GeneralizedTime("20391231235959.5Z")})}, {"notBefore an INTEGER", cert(cmstest.CertSpec{NotBefore: cmstest.Int(1)})}, {"notAfter that is not a time", cert(cmstest.CertSpec{NotAfter: cmstest.UTCTime("not a time!!Z")})}, {"subjectUniqueID before issuerUniqueID", cert(cmstest.CertSpec{UniqueIDs: [][]byte{cmstest.TLV(0x82, []byte{0, 2}), cmstest.TLV(0x81, []byte{0, 1})}})}, {"the extensions as [4]", cert(cmstest.CertSpec{NoExtensions: true, After: [][]byte{cmstest.TLV(0xa4, cmstest.Seq(cmstest.ExtKeyUsage()))}})}, {"an element after the extensions", cert(cmstest.CertSpec{After: [][]byte{cmstest.Null()}})}, {"[3] of two SEQUENCEs", cert(cmstest.CertSpec{NoExtensions: true, After: [][]byte{cmstest.TLV(0xa3, cmstest.Seq(cmstest.ExtKeyUsage()), cmstest.Seq(cmstest.ExtKeyUsage()))}})}, {"[3] holding a SET", cert(cmstest.CertSpec{NoExtensions: true, After: [][]byte{cmstest.TLV(0xa3, cmstest.Set(0x31, cmstest.ExtKeyUsage()))}})}, {"an empty [3]", cert(cmstest.CertSpec{NoExtensions: true, After: [][]byte{cmstest.TLV(0xa3)}})}, {"no Extension", cert(cmstest.CertSpec{Extensions: [][]byte{}})}, {"an Extension as a SET", cert(cmstest.CertSpec{Extensions: [][]byte{cmstest.TLV(0x31, cmstest.OID(oidX), cmstest.Octets(nil))}})}, {"an Extension that is an INTEGER", cert(cmstest.CertSpec{Extensions: [][]byte{cmstest.Int(1)}})}, {"an Extension of only its type", cert(cmstest.CertSpec{Extensions: [][]byte{cmstest.Seq(cmstest.OID(oidX))}})}, {"an empty Extension", cert(cmstest.CertSpec{Extensions: [][]byte{cmstest.Seq()}})}, {"an Extension of four elements", cert(cmstest.CertSpec{Extensions: [][]byte{cmstest.Seq(cmstest.OID(oidX), cmstest.Bool(true), cmstest.Octets(nil), cmstest.Octets(nil))}})}, {"an Extension whose type is an INTEGER", cert(cmstest.CertSpec{Extensions: [][]byte{cmstest.Seq(cmstest.Int(1), cmstest.Octets(nil))}})}, {"an Extension whose value is not OCTETS", cert(cmstest.CertSpec{Extensions: [][]byte{cmstest.Seq(cmstest.OID(oidX), cmstest.BitString(nil))}})}, {"an Extension critical by an INTEGER", cert(cmstest.CertSpec{Extensions: [][]byte{cmstest.Seq(cmstest.OID(oidX), cmstest.Int(1), cmstest.Octets(nil))}})}, {"an extension twice", cert(cmstest.CertSpec{Extensions: [][]byte{cmstest.ExtKeyUsage(), cmstest.ExtKeyUsage()}})}, {"subjectKeyIdentifier twice", cert(cmstest.CertSpec{Extensions: [][]byte{cmstest.ExtSKI([]byte{1}), cmstest.ExtSKI([]byte{2})}})}, {"a subjectKeyIdentifier not in DER", cert(cmstest.CertSpec{Extensions: [][]byte{cmstest.Extension(cmstest.OIDSKI, false, []byte{0x04, 0x01, 0x07, 0x00})}})}, {"a subjectKeyIdentifier an INTEGER", cert(cmstest.CertSpec{Extensions: [][]byte{cmstest.Extension(cmstest.OIDSKI, false, cmstest.Int(7))}})}, {"an empty subjectKeyIdentifier", cert(cmstest.CertSpec{Extensions: [][]byte{cmstest.ExtSKI(nil)}})}, {"a subjectKeyIdentifier of no bytes", cert(cmstest.CertSpec{Extensions: [][]byte{cmstest.Extension(cmstest.OIDSKI, false, nil)}})}, {"a subjectKeyIdentifier with a long form length", cert(cmstest.CertSpec{Extensions: [][]byte{cmstest.Extension(cmstest.OIDSKI, false, []byte{0x04, 0x81, 0x01, 0x07})}})}, {"a subjectKeyIdentifier constructed", cert(cmstest.CertSpec{Extensions: [][]byte{cmstest.Extension(cmstest.OIDSKI, false, cmstest.TLV(0x24, cmstest.Octets([]byte{7})))}})}, {"a subjectKeyIdentifier of 64 bytes", cert(cmstest.CertSpec{Extensions: [][]byte{cmstest.ExtSKI(bytes.Repeat([]byte{9}, 64))}})}, } { add(c.name, c.b) } good := cmstest.Name(cmstest.ATV(cmstest.OIDCommonName, cmstest.UTF8("A"))) for _, c := range []named{ {"an RDN as a SEQUENCE", cmstest.NameOf(cmstest.Seq(cmstest.ATV(cmstest.OIDCommonName, cmstest.UTF8("A"))))}, {"an empty RDN", cmstest.NameOf(cmstest.RDN())}, {"an RDN that is an INTEGER", cmstest.NameOf(cmstest.Int(1))}, {"an AttributeTypeAndValue as a SET", cmstest.NameOf(cmstest.RDN(cmstest.TLV(0x31, cmstest.OID(cmstest.OIDCommonName), cmstest.UTF8("A"))))}, {"an AttributeTypeAndValue of three", cmstest.NameOf(cmstest.RDN(cmstest.Seq(cmstest.OID(cmstest.OIDCommonName), cmstest.UTF8("A"), cmstest.Null())))}, {"an AttributeTypeAndValue of one", cmstest.NameOf(cmstest.RDN(cmstest.Seq(cmstest.OID(cmstest.OIDCommonName))))}, {"an AttributeTypeAndValue primitive", cmstest.NameOf(cmstest.RDN(cmstest.Int(3)))}, {"an attribute type that is an INTEGER", cmstest.NameOf(cmstest.RDN(cmstest.Seq(cmstest.Int(3), cmstest.UTF8("A"))))}, } { add("the subject, "+c.name, cert(cmstest.CertSpec{Subject: c.b, Issuer: good})) add("the issuer, "+c.name, cert(cmstest.CertSpec{Subject: good, Issuer: c.b})) } for _, c := range []struct { name string spec cmstest.CertSpec }{ {"both unique identifiers", cmstest.CertSpec{UniqueIDs: [][]byte{cmstest.TLV(0x81, []byte{0, 1}), cmstest.TLV(0x82, []byte{0, 2})}}}, {"only subjectUniqueID", cmstest.CertSpec{UniqueIDs: [][]byte{cmstest.TLV(0x82, []byte{0, 2})}, NoExtensions: true}}, {"no extensions", cmstest.CertSpec{NoExtensions: true}}, {"a critical subjectKeyIdentifier", cmstest.CertSpec{Extensions: [][]byte{cmstest.Extension(cmstest.OIDSKI, true, cmstest.Octets([]byte{9}))}}}, {"an unknown extension", cmstest.CertSpec{Extensions: [][]byte{cmstest.Extension(oidX, false, nil), cmstest.ExtSKI([]byte{9})}}}, {"a GeneralizedTime in 2030", cmstest.CertSpec{NotAfter: cmstest.GeneralizedTime("20300101000000Z")}}, {"a signature that is no one's", cmstest.CertSpec{Signature: cmstest.BitString([]byte("not a signature"))}}, {"another signature algorithm", cmstest.CertSpec{SigAlg: cmstest.AlgID(asn1.ObjectIdentifier{1, 2, 3}, cmstest.Int(7))}}, {"an empty name", cmstest.CertSpec{Subject: cmstest.NameOf(), Issuer: cmstest.NameOf()}}, // The times of validity at their limits. {"UTCTime of 1950", cmstest.CertSpec{NotBefore: cmstest.UTCTime("500101000000Z")}}, {"UTCTime of 2049", cmstest.CertSpec{NotAfter: cmstest.UTCTime("491231235959Z")}}, {"GeneralizedTime of 1950", cmstest.CertSpec{NotBefore: cmstest.GeneralizedTime("19500101000000Z")}}, {"GeneralizedTime of the year 0", cmstest.CertSpec{NotBefore: cmstest.GeneralizedTime("00000101000000Z")}}, {"GeneralizedTime of 9999", cmstest.CertSpec{NotAfter: cmstest.GeneralizedTime("99991231235959Z")}}, {"UTCTime of 29 February 2000", cmstest.CertSpec{NotBefore: cmstest.UTCTime("000229000000Z")}}, {"UTCTime of 29 February 2001", cmstest.CertSpec{NotBefore: cmstest.UTCTime("010229000000Z")}}, {"GeneralizedTime of 29 February 2100", cmstest.CertSpec{NotAfter: cmstest.GeneralizedTime("21000229000000Z")}}, {"a second 60", cmstest.CertSpec{NotBefore: cmstest.UTCTime("200101000060Z")}}, {"UTCTime with an offset", cmstest.CertSpec{NotBefore: cmstest.UTCTime("2001010000+0100")}}, {"UTCTime without seconds", cmstest.CertSpec{NotBefore: cmstest.UTCTime("2001010000Z")}}, {"GeneralizedTime in lower case z", cmstest.CertSpec{NotBefore: cmstest.GeneralizedTime("20200101000000z")}}, {"notBefore after notAfter", cmstest.CertSpec{From: to, To: from}}, {"notBefore equal to notAfter", cmstest.CertSpec{From: now, To: now}}, {"a validity of one second", cmstest.CertSpec{From: now, To: now.Add(time.Second)}}, {"notBefore as a BIT STRING", cmstest.CertSpec{NotBefore: cmstest.BitString([]byte("200101000000Z"))}}, } { add(c.name, cert(c.spec)) } // A NumericString, and the names (cert_test.go). inn := cmstest.ATV(asn1.ObjectIdentifier{1, 2, 643, 3, 131, 1, 1}, cmstest.Numeric("123456789012")) add("a NumericString beside the commonName", cert(cmstest.CertSpec{Subject: cmstest.Name(cn(cmstest.UTF8("Ivan Petrov")), inn)})) subject := func(atvs ...[]byte) []byte { return cert(cmstest.CertSpec{Subject: cmstest.Name(atvs...)}) } utf8 := cmstest.UTF8 for _, c := range []named{ {"a commonName in a NumericString", subject(cn(cmstest.Numeric("12345")))}, {"a UTF8String", subject(cn(utf8("Ana López")))}, {"a UTF8String that is not UTF-8", subject(cn(utf8("Ana \xff")))}, {"a UTF8String with a BOM, kept", subject(cn(utf8("\xef\xbb\xbfAna")))}, {"a UTF8String with a surrogate in UTF-8", subject(cn(utf8("Ana \xed\xa0\x80")))}, {"a UTF8String with a NUL", subject(cn(utf8("Ana\x00")))}, {"a UTF8String of four bytes", subject(cn(utf8("Ana \U0001F600")))}, {"a PrintableString of its whole alphabet", subject(cn(cmstest.Printable("Ana O'Neil (1+2), x-y./:=? Z9")))}, {"a PrintableString with an underscore", subject(cn(cmstest.Printable("Ana_Lopez")))}, {"a PrintableString with an at sign", subject(cn(cmstest.Printable("ana@example.com")))}, {"a PrintableString with a tilde", subject(cn(cmstest.Printable("Ana~")))}, {"a PrintableString with an ampersand", subject(cn(cmstest.Printable("A&B")))}, {"a PrintableString with an asterisk", subject(cn(cmstest.Printable("A*B")))}, {"a PrintableString with a byte of 0xe9", subject(cn(cmstest.Printable("L\xe9a")))}, {"a PrintableString with a quotation mark", subject(cn(cmstest.Printable("A\"B")))}, {"a PrintableString with a semicolon", subject(cn(cmstest.Printable("A;B")))}, {"a PrintableString with a TAB", subject(cn(cmstest.Printable("A\tB")))}, {"an empty PrintableString", subject(cn(cmstest.Printable("")))}, {"an IA5String", subject(cn(cmstest.IA5("ana@example.com")))}, {"an IA5String with a byte of 0x80", subject(cn(cmstest.IA5("Ana\x80")))}, {"an IA5String with a DEL and a NUL", subject(cn(cmstest.IA5("Ana\x7f\x00")))}, {"a TeletexString in ASCII", subject(cn(cmstest.Teletex("Ana Lopez")))}, {"a TeletexString with a byte of 0xe9", subject(cn(cmstest.Teletex("L\xe9a")))}, {"a BMPString", subject(cn(cmstest.BMPText("Ana López")))}, {"a BMPString above the surrogates", subject(cn(cmstest.BMPText("Ana ¥")))}, {"a BMPString of U+FFFF and U+0000", subject(cn(cmstest.BMP([]byte{0xff, 0xff, 0, 0})))}, {"a BMPString of odd length", subject(cn(cmstest.BMP([]byte{0, 'A', 0})))}, {"a BMPString with a surrogate pair", subject(cn(cmstest.BMPText("Ana \U0001F600")))}, {"a BMPString with a low surrogate", subject(cn(cmstest.BMP([]byte{0, 'A', 0xdc, 0})))}, {"a BMPString with a high surrogate", subject(cn(cmstest.BMP([]byte{0xd8, 0, 0, 'A'})))}, {"a BMPString of U+DFFF", subject(cn(cmstest.BMP([]byte{0xdf, 0xff})))}, {"a BMPString of U+E000", subject(cn(cmstest.BMP([]byte{0xe0, 0})))}, {"a VisibleString", subject(cn(cmstest.Visible("Ana")))}, {"a UniversalString", subject(cn(cmstest.TLV(0x1c, []byte{0, 0, 0, 'A'})))}, {"a NumericString", subject(cn(cmstest.Numeric("12345")))}, {"a GeneralString", subject(cn(cmstest.TLV(0x1b, []byte("Ana"))))}, {"a UTF8String constructed", subject(cn(cmstest.TLV(0x2c, utf8("Ana"))))}, {"two commonNames", subject(cn(utf8("Ana")), cn(utf8("Luis")))}, {"two commonNames in one RDN", cert(cmstest.CertSpec{Subject: cmstest.NameOf(cmstest.RDN(cn(utf8("Ana")), cn(utf8("Luis"))))})}, {"a commonName in an RDN of two attributes", cert(cmstest.CertSpec{Subject: cmstest.NameOf(cmstest.RDN(org(utf8("Banco")), cn(utf8("Ana"))))})}, {"no commonName", subject(org(utf8("Banco")))}, {"givenName, surname and the NIF in the CN", subject(cn(utf8("ESPAÑOL ESPAÑOL JUAN - 12345678Z")), given(utf8("JUAN")), surname(utf8("ESPAÑOL ESPAÑOL")))}, {"surname, givenName and the CN, in another order", subject(surname(utf8("López")), cn(utf8("LÓPEZ ANA - 1Z")), given(utf8("Ana")))}, {"givenName and surname without a CN", subject(given(utf8("Ana")), surname(utf8("López")))}, {"givenName and surname in BMPString and PrintableString", subject(cn(utf8("X")), given(cmstest.BMPText("Ana")), surname(cmstest.Printable("Lopez")))}, {"only a givenName", subject(cn(utf8("Ana López")), given(utf8("Ana")))}, {"only a surname", subject(cn(utf8("Ana López")), surname(utf8("López")))}, {"an empty givenName", subject(cn(utf8("Ana López")), given(utf8("")), surname(utf8("López")))}, {"an empty surname", subject(cn(utf8("Ana López")), given(utf8("Ana")), surname(utf8("")))}, {"an empty givenName without a CN", subject(given(utf8("")), surname(utf8("López")))}, {"a givenName that is not UTF-8", subject(cn(utf8("Ana López")), given(utf8("An\xff")), surname(utf8("López")))}, {"a surname that is not UTF-8", subject(cn(utf8("Ana López")), given(utf8("Ana")), surname(utf8("L\xff")))}, {"a givenName that is no text", subject(cn(utf8("Ana López")), given(cmstest.Visible("Ana")), surname(utf8("López")))}, {"two givenNames", subject(cn(utf8("Ana López")), given(utf8("Ana")), given(utf8("Eva")), surname(utf8("López")))}, {"two surnames", subject(cn(utf8("Ana López")), given(utf8("Ana")), surname(utf8("López")), surname(utf8("Pérez")))}, {"a givenName with an escape, not the CN", subject(cn(utf8("Ana López")), given(utf8("Ana\x1b")), surname(utf8("López")))}, {"a commonName with an escape, kept as text", subject(cn(utf8("Ana\x1b[31m")))}, {"a givenName and a surname of spaces", subject(cn(utf8("Ana")), given(utf8(" ")), surname(utf8(" ")))}, // Identifiers whose last arc wraps around to that of the profile in // 32 or 64 bits: other types, which give no text. {"a commonName whose arc wraps in 64 bits, beside a commonName", subject(cn(utf8("Ana")), cmstest.Seq(wrapOID(cmstest.OIDCommonName, 64), utf8("Luis")))}, {"only a commonName whose arc wraps in 64 bits", subject(cmstest.Seq(wrapOID(cmstest.OIDCommonName, 64), utf8("Eva")))}, {"only a commonName whose arc wraps in 32 bits", subject(cmstest.Seq(wrapOID(cmstest.OIDCommonName, 32), utf8("Eva")))}, {"a givenName whose arc wraps in 64 bits, a surname and a CN", subject(cn(utf8("Ana López")), cmstest.Seq(wrapOID(cmstest.OIDGivenName, 64), utf8("Eva")), surname(utf8("López")))}, {"a surname whose arc wraps in 64 bits, a givenName and a CN", subject(cn(utf8("Ana López")), given(utf8("Ana")), cmstest.Seq(wrapOID(cmstest.OIDSurname, 64), utf8("Pérez")))}, } { add(c.name, c.b) } issuer := func(atvs ...[]byte) []byte { return cert(cmstest.CertSpec{Issuer: cmstest.Name(atvs...)}) } for _, c := range []named{ {"a commonName", issuer(org(utf8("Banco")), cn(utf8("CA de prueba")))}, {"an organizationName without a CN", issuer(org(utf8("Banco S.A.")))}, {"a commonName with an escape, kept", issuer(org(utf8("Banco")), cn(utf8("CA\x1b")))}, {"a commonName not UTF-8, then the O", issuer(org(utf8("Banco")), cn(utf8("C\xff")))}, {"two commonNames, then the O", issuer(org(utf8("Banco")), cn(utf8("A")), cn(utf8("B")))}, {"an empty commonName, not the O", issuer(org(utf8("Banco")), cn(utf8("")))}, {"an organizationName that is not UTF-8", issuer(org(utf8("B\xff")))}, {"two organizationNames", issuer(org(utf8("A")), org(utf8("B")))}, {"neither", issuer(cmstest.ATV(cmstest.OIDCountry, cmstest.Printable("ES")))}, {"an organizationName that is not text", issuer(org(cmstest.Visible("Banco")))}, {"an organizationName whose arc wraps in 64 bits", issuer(cmstest.Seq(wrapOID(cmstest.OIDOrganization, 64), utf8("Banco")))}, {"a commonName whose arc wraps in 64 bits, then the O", issuer(org(utf8("Banco")), cmstest.Seq(wrapOID(cmstest.OIDCommonName, 64), utf8("CA")))}, } { add("the issuer, "+c.name, c.b) } // Extensions whose identifier wraps around to subjectKeyIdentifier. skiWrap := func(bits uint, value []byte) []byte { return cmstest.Seq(wrapOID(cmstest.OIDSKI, bits), cmstest.Octets(value)) } for _, c := range []named{ {"an extension whose arc wraps to subjectKeyIdentifier in 64 bits, its value not DER", cert(cmstest.CertSpec{Extensions: [][]byte{skiWrap(64, []byte{1, 2, 3})}})}, {"an extension whose arc wraps to subjectKeyIdentifier in 32 bits, its value empty", cert(cmstest.CertSpec{Extensions: [][]byte{skiWrap(32, nil)}})}, {"subjectKeyIdentifier and an extension whose arc wraps to it", cert(cmstest.CertSpec{Extensions: [][]byte{cmstest.ExtSKI([]byte{9}), skiWrap(64, cmstest.Octets([]byte{8}))}})}, {"an extension whose arc wraps to keyUsage, and keyUsage", cert(cmstest.CertSpec{Extensions: [][]byte{cmstest.ExtKeyUsage(), cmstest.Seq(wrapOID(cmstest.OIDKeyUsage, 64), cmstest.Octets(nil))}})}, } { add(c.name, c.b) } var namedCases []any for _, c := range cases { namedCases = append(namedCases, CertCase{Name: c.name, DER: hx(c.b), CertOut: certOutcome(c.b, true)}) } // The bases, edited node by node. x509Cert := cmstest.NewECDSA("Luis Gómez", elliptic.P256(), from, to).Cert.Raw names := cert(cmstest.CertSpec{ Subject: cmstest.Name(cn(cmstest.BMPText("Ana")), given(cmstest.Printable("Ana")), surname(cmstest.Teletex("Lopez")), org(cmstest.IA5("Banco"))), Issuer: cmstest.Name(cmstest.ATV(cmstest.OIDCountry, cmstest.Printable("ES")), org(utf8("Banco")), cn(utf8("CA de prueba"))), UniqueIDs: [][]byte{cmstest.TLV(0x81, []byte{0, 1}), cmstest.TLV(0x82, []byte{0, 2})}, Extensions: [][]byte{cmstest.Extension(cmstest.OIDSKI, true, cmstest.Octets([]byte{1, 2, 3})), cmstest.Extension(oidX, false, nil)}, }) rsaCert := g.luis.Cert.Raw bases := []named{{"field by field", raw}, {"crypto/x509", x509Cert}, {"names", names}, {"rsa", rsaCert}} var baseOut, editOut []any for _, b := range bases { baseOut = append(baseOut, CertCase{Name: b.name, DER: hx(b.b), CertOut: certOutcome(b.b, true)}) for _, e := range edits(g.r, b.name, b.b, 200) { editOut = append(editOut, CertCase{Edit: e, CertOut: certOutcome(apply(b.b, e), false)}) } } f.sections = []section{{"named", namedCases}, {"bases", baseOut}, {"edits", editOut}} return f } // --------------------------------------------------------------------------- // cms_signatures.json // SigCase is a signature, given or as an edit of a base, the message it is // checked over when it is not that of the file, and what // cms.ParseSignature gives. type SigCase struct { Name string `json:"name,omitempty"` DER any `json:"der,omitempty"` Message string `json:"message,omitempty"` Edit SigOut raw []byte } func (g *gen) sigCase(name string, b []byte) *SigCase { return &SigCase{Name: name, DER: hx(b), SigOut: sigOutcome(b, msg, false), raw: b} } func pss(fields ...[]byte) []byte { return cmstest.AlgID(cmstest.OIDPSS, cmstest.Seq(fields...)) } func evenPointKey() *ecdsa.PrivateKey { for { k := cmstest.ECKey(elliptic.P256()) if p := cmstest.Uncompressed(&k.PublicKey); p[len(p)-1]&1 == 0 { return k } } } func (g *gen) signatureFiles() (*vfile, *vfile) { const what = "cms.ParseSignature: the error, or the spans of its certificates and OCSP responses and, for each SignerInfo, the index of its certificate, the spans of its fields, and SignerInfo.Check over the message of the file (result) and over other_message (other_result). der is the hex of the signature, or a list of pieces, each the hex of bytes or the index of one of the certificates of the file, and then sha256 is the SHA-256 of the signature. " f := &vfile{name: "signatures", description: what + "The signatures of the tests of the form in internal/cms (form_test.go, cms_test.go, hostile_test.go, cert_test.go), and others: identifiers whose arcs wrap around to those of the profile in 32 or 64 bits, and SET OF with an element repeated. oids are the object identifiers of internal/cms, by the names of its source, as the content of their DER.", every: map[string]int{"form": 4}, head: [][2]any{{"message", hx(msg)}, {"other_message", hx(otherMsg)}, {"oids", sourceOIDs()}}} fa := &vfile{name: "algorithms", description: what + "The signatures of the tests of the algorithms and the keys in internal/cms (cms_test.go, verify_test.go), and others.", every: map[string]int{"algorithms": 7, "keys": 6}, head: [][2]any{{"message", hx(msg)}, {"other_message", hx(otherMsg)}}} ana, luis, tsa := g.ana, g.luis, g.tsa ecKey, ecKey2, rsaKey := g.ecKey, g.ecKey2, g.rsaKey tok := func(sig []byte) []byte { return cmstest.Token(sig, now, cmstest.TokenOptions{}, tsa) } // The form (form_test.go, cms_test.go, hostile_test.go). var form []named addF := func(name string, b []byte) { form = append(form, named{name, b}) } good := cmstest.Signature(msg, cmstest.Options{}, ana) both := cmstest.Signature(msg, cmstest.Options{}, ana, luis) sealed := cmstest.Signature(msg, cmstest.Options{Token: tok}, ana) sd := cmstest.SignedDataPath si := firstSignerInfo(good) attrsOf := func(mutate func(attrs [][]byte) [][]byte) []byte { return cmstest.Signature(msg, cmstest.Options{Mutate: mutate}, ana) } extra := func(attrs ...[]byte) []byte { return cmstest.Signature(msg, cmstest.Options{ExtraAttrs: attrs}, ana) } h := sha256.Sum256(ana.Cert.Raw) v2 := func(value []byte) func([][]byte) [][]byte { return func(a [][]byte) [][]byte { a[2] = cmstest.Seq(cmstest.OID(cmstest.OIDSigCertV2), cmstest.Set(0x31, value)) return a } } reversed := func(b []byte) []byte { k := slices.Clone(cmstest.Children(b)) slices.Reverse(k) return cmstest.TLV(b[0], k...) } same := func(c1, c2 cmstest.CertSpec) []byte { a, b := cmstest.NewCert(c1, ecKey), cmstest.NewCert(c2, ecKey2) return cmstest.Signature(msg, cmstest.Options{ExtraCerts: [][]byte{b.Cert.Raw}}, a) } ocsp := func(n int64) []byte { return cmstest.TLV(0xa1, cmstest.OID(cmstest.OIDOCSP), cmstest.Seq(cmstest.Int(n))) } twoOCSP := cmstest.Signature(msg, cmstest.Options{CRLs: [][]byte{ocsp(1), ocsp(2)}}, ana) addF("a signature of ECDSA P-256", good) for _, c := range []named{ {"a byte after it", append(bytes.Clone(good), 0)}, {"truncated", good[:len(good)-1]}, {"not a SignedData", []byte{0x30, 0x03, 0x02, 0x01, 0x00}}, {"empty", nil}, {"BER", cmstest.Signature(msg, cmstest.Options{BER: true}, ana)}, {"a ContentInfo with a third element", cmstest.Edit(good, cmstest.Append(cmstest.Null()))}, {"a ContentInfo of only its type", cmstest.Seq(cmstest.OID(cmstest.OIDSignedData))}, {"the content as [1]", cmstest.Edit(good, cmstest.Retag(0xa1), 1)}, {"the content type id-data", cmstest.Edit(good, cmstest.Replace(cmstest.OID(cmstest.OIDData)), 0)}, {"the content type an INTEGER", cmstest.Edit(good, cmstest.Replace(cmstest.Int(1)), 0)}, {"[0] with an element more", cmstest.Edit(good, cmstest.Append(cmstest.Null()), 1)}, {"[0] empty", cmstest.Edit(good, cmstest.Replace(cmstest.TLV(0xa0)), 1)}, {"[0] holding a SET", cmstest.Edit(good, cmstest.Retag(0x31), sd...)}, {"a SignedData of two fields", cmstest.Edit(good, func(b []byte) []byte { return cmstest.Seq(cmstest.Children(b)[:2]...) }, sd...)}, {"a SignedData without signerInfos", cmstest.Edit(good, func(b []byte) []byte { k := cmstest.Children(b); return cmstest.Seq(k[:len(k)-1]...) }, sd...)}, {"a field after signerInfos", cmstest.Edit(good, cmstest.Append(cmstest.Set(0x31)), sd...)}, {"the version as an OCTET STRING", cmstest.Edit(good, cmstest.Retag(0x04), path(sd, 0)...)}, {"the version of the SignedData 5", cmstest.Edit(good, cmstest.Replace(cmstest.Int(5)), path(sd, 0)...)}, {"digestAlgorithms as a SEQUENCE", cmstest.Edit(good, cmstest.Retag(0x30), path(sd, 1)...)}, {"digestAlgorithms out of order", cmstest.Edit(good, cmstest.Replace(cmstest.TLV(0x31, cmstest.HashAlg(crypto.SHA512), cmstest.HashAlg(crypto.SHA256))), path(sd, 1)...)}, {"a digestAlgorithm that is an INTEGER", cmstest.Edit(good, cmstest.Replace(cmstest.Set(0x31, cmstest.Int(1))), path(sd, 1)...)}, {"a digestAlgorithm that is a SET", cmstest.Edit(good, cmstest.Replace(cmstest.Set(0x31, cmstest.TLV(0x31, cmstest.OID(cmstest.OIDSHA256)))), path(sd, 1)...)}, {"a digestAlgorithm without an OID", cmstest.Edit(good, cmstest.Replace(cmstest.Set(0x31, cmstest.Seq(cmstest.Int(1)))), path(sd, 1)...)}, {"an empty digestAlgorithm", cmstest.Edit(good, cmstest.Replace(cmstest.Set(0x31, cmstest.Seq())), path(sd, 1)...)}, {"a digestAlgorithm of three fields", cmstest.Edit(good, cmstest.Replace(cmstest.Set(0x31, cmstest.AlgID(cmstest.OIDSHA256, cmstest.Null(), cmstest.Null()))), path(sd, 1)...)}, {"no digestAlgorithm", cmstest.Edit(good, cmstest.Replace(cmstest.Set(0x31)), path(sd, 1)...)}, {"digestAlgorithms of SHA-256 twice", cmstest.Edit(good, cmstest.Replace(cmstest.TLV(0x31, cmstest.HashAlg(crypto.SHA256), cmstest.HashAlg(crypto.SHA256))), path(sd, 1)...)}, {"digestAlgorithms of SHA-1 and MD5", cmstest.Edit(good, cmstest.Replace(cmstest.Set(0x31, cmstest.HashAlg(crypto.SHA1), cmstest.AlgID(asn1.ObjectIdentifier{1, 2, 840, 113549, 2, 5}, cmstest.Null()))), path(sd, 1)...)}, {"encapContentInfo as a SET", cmstest.Edit(good, cmstest.Retag(0x31), path(sd, 2)...)}, {"an empty encapContentInfo", cmstest.Edit(good, cmstest.Replace(cmstest.Seq()), path(sd, 2)...)}, {"encapContentInfo of three fields", cmstest.Edit(good, cmstest.Append(cmstest.TLV(0xa0, cmstest.Octets(msg)), cmstest.Null()), path(sd, 2)...)}, {"eContentType an INTEGER", cmstest.Edit(good, cmstest.Replace(cmstest.Seq(cmstest.Int(1))), path(sd, 2)...)}, {"eContentType id-ct-TSTInfo", cmstest.Edit(good, cmstest.Replace(cmstest.Seq(cmstest.OID(cmstest.OIDTSTInfo))), path(sd, 2)...)}, {"rule 2: an eContent in the signature", cmstest.Edit(good, cmstest.Append(cmstest.TLV(0xa0, cmstest.Octets(msg))), path(sd, 2)...)}, {"certificates out of order", cmstest.Edit(both, reversed, path(sd, 3)...)}, {"a CertificateChoice [4]", cmstest.Signature(msg, cmstest.Options{ExtraCerts: [][]byte{cmstest.TLV(0xa4, cmstest.Null())}}, ana)}, {"a CertificateChoice that is a SET", cmstest.Signature(msg, cmstest.Options{ExtraCerts: [][]byte{cmstest.Set(0x31, cmstest.Null())}}, ana)}, {"a CertificateChoice [0] primitive", cmstest.Signature(msg, cmstest.Options{ExtraCerts: [][]byte{cmstest.TLV(0x80, []byte{1})}}, ana)}, {"rule 3: a CRL in crls", cmstest.Signature(msg, cmstest.Options{CRLs: [][]byte{cmstest.Seq(cmstest.Int(1))}}, ana)}, {"rule 3: a CRL of the shape of an OCSP", cmstest.Signature(msg, cmstest.Options{CRLs: [][]byte{cmstest.Seq(cmstest.OID(cmstest.OIDOCSP), cmstest.Seq(cmstest.Int(0)))}}, ana)}, {"rule 3: crls out of order", cmstest.Edit(twoOCSP, reversed, path(sd, 4)...)}, {"rule 3: another revocation format", cmstest.Signature(msg, cmstest.Options{CRLs: [][]byte{cmstest.TLV(0xa1, cmstest.OID(unknownOID), cmstest.Null())}}, ana)}, {"rule 3: a revocation format of one", cmstest.Signature(msg, cmstest.Options{CRLs: [][]byte{cmstest.TLV(0xa1, cmstest.OID(cmstest.OIDOCSP))}}, ana)}, {"rule 3: a revocation format by number", cmstest.Signature(msg, cmstest.Options{CRLs: [][]byte{cmstest.TLV(0xa1, cmstest.Int(2), cmstest.Null())}}, ana)}, {"rule 3: an OCSP whose arc wraps in 64 bits", cmstest.Signature(msg, cmstest.Options{CRLs: [][]byte{cmstest.TLV(0xa1, wrapOID(cmstest.OIDOCSP, 64), cmstest.Null())}}, ana)}, {"signerInfos as a SEQUENCE", cmstest.Edit(good, cmstest.Retag(0x30), cmstest.SignerInfosPath(good)...)}, {"no SignerInfo", cmstest.Edit(good, cmstest.Replace(cmstest.TLV(0x31)), cmstest.SignerInfosPath(good)...)}, {"signerInfos out of order", cmstest.Signature(msg, cmstest.Options{Unsorted: true}, ana, luis)}, {"a SignerInfo without signedAttrs", cmstest.Edit(good, func(b []byte) []byte { k := cmstest.Children(b); return cmstest.Seq(k[0], k[1], k[2], k[4], k[5]) }, si...)}, {"signedAttrs as [1]", cmstest.Edit(good, cmstest.Retag(0xa1), path(si, 3)...)}, {"a SignerInfo without its signature", cmstest.Edit(good, func(b []byte) []byte { return cmstest.Seq(cmstest.Children(b)[:5]...) }, si...)}, {"the version of a SignerInfo as OCTETS", cmstest.Edit(good, cmstest.Retag(0x04), path(si, 0)...)}, {"the version 2", cmstest.Signature(msg, cmstest.Options{Version: 2}, ana)}, {"the version 3 with issuerAndSerialNumber", cmstest.Signature(msg, cmstest.Options{Version: 3}, ana)}, {"the version 1 with subjectKeyIdentifier", cmstest.Signature(msg, cmstest.Options{Version: 1, SKI: true}, ana)}, {"the version 4 with subjectKeyIdentifier", cmstest.Signature(msg, cmstest.Options{Version: 4, SKI: true}, ana)}, {"the version 257", cmstest.Edit(good, cmstest.Replace(cmstest.Int(257)), path(si, 0)...)}, {"the version 1 in two bytes", cmstest.Edit(good, cmstest.Replace(cmstest.IntBytes([]byte{0, 1})), path(si, 0)...)}, {"the digestAlgorithm as a SET", cmstest.Edit(good, cmstest.Retag(0x31), path(si, 2)...)}, {"the signatureAlgorithm as a SET", cmstest.Edit(good, cmstest.Retag(0x31), path(si, 4)...)}, {"the digestAlgorithm of a SignerInfo, no OID", cmstest.Edit(good, cmstest.Replace(cmstest.Seq(cmstest.Int(1))), path(si, 2)...)}, {"a signatureAlgorithm without an OID", cmstest.Edit(good, cmstest.Replace(cmstest.Seq(cmstest.Int(1))), path(si, 4)...)}, {"an empty signatureAlgorithm", cmstest.Edit(good, cmstest.Replace(cmstest.Seq()), path(si, 4)...)}, {"the signature as a BIT STRING", cmstest.Edit(good, func(b []byte) []byte { return cmstest.BitString(contentOf(b)) }, path(si, 5)...)}, {"a field after the unsigned attributes", cmstest.Signature(msg, cmstest.Options{Token: tok, EditSignerInfo: func(f [][]byte) [][]byte { return append(f, cmstest.Null()) }}, ana)}, {"a field [2] after the signature", cmstest.Signature(msg, cmstest.Options{EditSignerInfo: func(f [][]byte) [][]byte { return append(f, cmstest.TLV(0xa2, cmstest.BigArcAttr())) }}, ana)}, {"unsigned attributes as [2]", cmstest.Signature(msg, cmstest.Options{Token: tok, EditSignerInfo: func(f [][]byte) [][]byte { f[6] = append([]byte{0xa2}, f[6][1:]...); return f }}, ana)}, {"a sid of three elements", cmstest.Edit(good, cmstest.Append(cmstest.Null()), path(si, 1)...)}, {"a sid of one element", cmstest.Edit(good, func(b []byte) []byte { return cmstest.Seq(cmstest.Children(b)[0]) }, path(si, 1)...)}, {"the issuer of the sid as a SET", cmstest.Edit(good, cmstest.Retag(0x31), path(si, 1, 0)...)}, {"the serial of the sid as an OCTET STRING", cmstest.Edit(good, cmstest.Retag(0x04), path(si, 1, 1)...)}, {"the serial of another certificate", cmstest.Edit(good, cmstest.Replace(cmstest.Int(12345)), path(si, 1, 1)...)}, {"a sid of another choice", cmstest.Edit(good, cmstest.Retag(0x81), path(si, 1)...)}, {"a sid [0] constructed", cmstest.Edit(cmstest.Signature(msg, cmstest.Options{SKI: true}, ana), cmstest.Replace(cmstest.TLV(0xa0, cmstest.Octets(ana.Cert.SubjectKeyId))), path(si, 1)...)}, {"a subjectKeyIdentifier of no certificate", cmstest.Edit(cmstest.Signature(msg, cmstest.Options{SKI: true}, ana), cmstest.Replace(cmstest.TLV(0x80, []byte("other"))), path(si, 1)...)}, {"an empty subjectKeyIdentifier in the sid", cmstest.Edit(cmstest.Signature(msg, cmstest.Options{SKI: true}, ana), cmstest.Replace(cmstest.TLV(0x80)), path(si, 1)...)}, {"no certificate of the signer", cmstest.Signature(msg, cmstest.Options{OmitCert: true}, ana)}, {"two certificates of one issuer and serial", same(cmstest.CertSpec{CN: "A", Serial: cmstest.Int(7)}, cmstest.CertSpec{CN: "B", Serial: cmstest.Int(7), Issuer: cmstest.Name(cmstest.ATV(cmstest.OIDCommonName, cmstest.UTF8("A")))})}, {"two certificates of one keyIdentifier", cmstest.Signature(msg, cmstest.Options{SKI: true, ExtraCerts: [][]byte{cmstest.NewCert(cmstest.CertSpec{CN: "B", SKI: ana.Cert.SubjectKeyId}, ecKey2).Cert.Raw}}, ana)}, {"an empty keyIdentifier, a certificate of none", cmstest.Signature(msg, cmstest.Options{SKI: true}, cmstest.NewCert(cmstest.CertSpec{CN: "Sin SKI", NoExtensions: true}, ecKey))}, {"two SignerInfo of one certificate", cmstest.Signature(msg, cmstest.Options{}, ana, ana)}, {"one SignerInfo twice", cmstest.Signature(msg, cmstest.Options{SignerInfoTwice: true}, ana)}, {"the signer's certificate breaks the profile", cmstest.Signature(msg, cmstest.Options{}, cmstest.NewCert(cmstest.CertSpec{CN: "Ana", NoVersion: true}, ecKey))}, {"signedAttrs out of order", cmstest.Signature(msg, cmstest.Options{UnsortedAttrs: true}, ana)}, {"an attribute as a SET", extra(cmstest.TLV(0x31, cmstest.OID(unknownOID), cmstest.Set(0x31, cmstest.Null())))}, {"an attribute of three fields", extra(cmstest.Seq(cmstest.OID(unknownOID), cmstest.Set(0x31, cmstest.Null()), cmstest.Null()))}, {"an attribute of one field", extra(cmstest.Seq(cmstest.OID(unknownOID)))}, {"an attribute that is an INTEGER", extra(cmstest.Int(5))}, {"the values of an attribute as a SEQUENCE", extra(cmstest.Seq(cmstest.OID(unknownOID), cmstest.Seq(cmstest.Null())))}, {"an attribute whose type is an INTEGER", extra(cmstest.Seq(cmstest.Int(1), cmstest.Set(0x31, cmstest.Null())))}, {"an unknown attribute without a value", extra(cmstest.Seq(cmstest.OID(unknownOID), cmstest.Set(0x31)))}, {"values of an attribute out of order", extra(cmstest.Seq(cmstest.OID(unknownOID), cmstest.TLV(0x31, cmstest.Int(2), cmstest.Int(1))))}, {"two content-type attributes", cmstest.Signature(msg, cmstest.Options{ContentType2: true}, ana)}, {"a content-type of two values", attrsOf(func(a [][]byte) [][]byte { a[0] = cmstest.Attr(cmstest.OIDContentType, cmstest.OID(cmstest.OIDData), cmstest.OID(cmstest.OIDTSTInfo)) return a })}, {"a content-type of id-data twice in its values", attrsOf(func(a [][]byte) [][]byte { a[0] = cmstest.Attr(cmstest.OIDContentType, cmstest.OID(cmstest.OIDData), cmstest.OID(cmstest.OIDData)) return a })}, {"a message-digest of two values", attrsOf(func(a [][]byte) [][]byte { s := sha256.Sum256(msg) a[1] = cmstest.Attr(cmstest.OIDMessageDigest, cmstest.Octets(s[:]), cmstest.Octets(append(s[:], 0))) return a })}, {"a second content-type without a value", extra(cmstest.Seq(cmstest.OID(cmstest.OIDContentType), cmstest.Set(0x31)))}, {"no content-type", attrsOf(func(a [][]byte) [][]byte { return a[1:] })}, {"the content-type id-signedData", attrsOf(func(a [][]byte) [][]byte { a[0] = cmstest.Attr(cmstest.OIDContentType, cmstest.OID(cmstest.OIDSignedData)) return a })}, {"the content-type id-ct-TSTInfo", attrsOf(func(a [][]byte) [][]byte { a[0] = cmstest.Attr(cmstest.OIDContentType, cmstest.OID(cmstest.OIDTSTInfo)) return a })}, {"a content-type that is OCTETS", attrsOf(func(a [][]byte) [][]byte { a[0] = cmstest.Attr(cmstest.OIDContentType, cmstest.Octets([]byte("data"))) return a })}, {"a content-type whose arc wraps to id-data in 64 bits", attrsOf(func(a [][]byte) [][]byte { a[0] = cmstest.Attr(cmstest.OIDContentType, wrapOID(cmstest.OIDData, 64)) return a })}, {"no message-digest", cmstest.Signature(msg, cmstest.Options{NoMessageDigest: true}, ana)}, {"two message-digest attributes", attrsOf(func(a [][]byte) [][]byte { return append(a, a[1]) })}, {"a message-digest that is [0]", attrsOf(func(a [][]byte) [][]byte { s := sha256.Sum256(msg) a[1] = cmstest.Attr(cmstest.OIDMessageDigest, cmstest.TLV(0x80, s[:])) return a })}, {"a message-digest of 31 bytes", attrsOf(func(a [][]byte) [][]byte { s := sha256.Sum256(msg) a[1] = cmstest.Attr(cmstest.OIDMessageDigest, cmstest.Octets(s[:31])) return a })}, {"a message-digest of 33 bytes", attrsOf(func(a [][]byte) [][]byte { s := sha256.Sum256(msg) a[1] = cmstest.Attr(cmstest.OIDMessageDigest, cmstest.Octets(append(s[:], 0))) return a })}, {"no signing-certificate-v2", cmstest.Signature(msg, cmstest.Options{NoSigCertV2: true}, ana)}, {"only a signing-certificate", cmstest.Signature(msg, cmstest.Options{NoSigCertV2: true, SigCertV1: true}, ana)}, {"two signing-certificate-v2 attributes", attrsOf(func(a [][]byte) [][]byte { return append(a, a[2]) })}, {"a signing-certificate-v2 of two values", cmstest.Signature(msg, cmstest.Options{Mutate: func(a [][]byte) [][]byte { a[2] = cmstest.Seq(cmstest.OID(cmstest.OIDSigCertV2), cmstest.Set(0x31, cmstest.Seq(cmstest.Seq(cmstest.Seq(cmstest.Octets(h[:])))), cmstest.Seq(cmstest.Seq(cmstest.Seq(cmstest.Octets(make([]byte, 33))))))) return a }}, ana)}, {"a SigningCertificateV2 as a SET", attrsOf(v2(cmstest.TLV(0x31, cmstest.Seq(cmstest.Seq(cmstest.Octets(h[:]))))))}, {"an empty SigningCertificateV2", attrsOf(v2(cmstest.Seq()))}, {"its certs as a SET", attrsOf(v2(cmstest.Seq(cmstest.TLV(0x31, cmstest.Seq(cmstest.Octets(h[:]))))))}, {"its certs empty", attrsOf(v2(cmstest.Seq(cmstest.Seq())))}, {"an ESSCertIDv2 as a SET", attrsOf(v2(cmstest.Seq(cmstest.Seq(cmstest.TLV(0x31, cmstest.Octets(h[:]))))))}, {"an empty ESSCertIDv2", attrsOf(v2(cmstest.Seq(cmstest.Seq(cmstest.Seq()))))}, {"an ESSCertIDv2 of only its algorithm", attrsOf(v2(cmstest.Seq(cmstest.Seq(cmstest.Seq(cmstest.HashAlg(crypto.SHA256))))))}, {"a certHash as [0]", attrsOf(v2(cmstest.Seq(cmstest.Seq(cmstest.Seq(cmstest.TLV(0x80, h[:]))))))}, {"a hashAlgorithm without an OID", attrsOf(v2(cmstest.Seq(cmstest.Seq(cmstest.Seq(cmstest.Seq(cmstest.Int(1)), cmstest.Octets(h[:]))))))}, {"an ESSCertIDv2 of SHA-1", cmstest.Signature(msg, cmstest.Options{ESSHashAlg: cmstest.HashAlg(crypto.SHA1)}, ana)}, {"an ESSCertIDv2 of SHA-256 with an INTEGER", cmstest.Signature(msg, cmstest.Options{ESSHashAlg: cmstest.AlgID(cmstest.OIDSHA256, cmstest.Int(0))}, ana)}, {"an ESSCertIDv2 whose hash arc wraps to SHA-256 in 64 bits", attrsOf(v2(cmstest.Seq(cmstest.Seq(cmstest.Seq(cmstest.Seq(wrapOID(cmstest.OIDSHA256, 64)), cmstest.Octets(h[:]))))))}, {"the hash of another certificate", cmstest.Signature(msg, cmstest.Options{ESSCert: luis.Cert.Raw}, ana)}, {"a certHash a byte short", attrsOf(v2(cmstest.Seq(cmstest.Seq(cmstest.Seq(cmstest.Octets(h[:31]))))))}, {"a certHash with a byte more", attrsOf(v2(cmstest.Seq(cmstest.Seq(cmstest.Seq(cmstest.Octets(append(h[:], 0)))))))}, {"a second ESSCertIDv2 of another certificate", attrsOf(v2(cmstest.Seq(cmstest.Seq(cmstest.Seq(cmstest.Octets(h[:])), cmstest.Seq(cmstest.Octets(make([]byte, 32)))))))}, {"the first ESSCertIDv2 of another certificate", attrsOf(v2(cmstest.Seq(cmstest.Seq(cmstest.Seq(cmstest.Octets(make([]byte, 32))), cmstest.Seq(cmstest.Octets(h[:]))))))}, {"a SigningCertificateV2 with policies", attrsOf(v2(cmstest.Seq(cmstest.Seq(cmstest.Seq(cmstest.Octets(h[:]))), cmstest.Seq(cmstest.Seq(cmstest.OID(unknownOID))))))}, {"two timestamp attributes, the second a token of nothing", cmstest.Signature(msg, cmstest.Options{Token2: true, Token: func(s []byte) []byte { return cmstest.Seq(cmstest.OID(cmstest.OIDData)) }}, ana)}, {"a signing-certificate with another hash", cmstest.Signature(msg, cmstest.Options{Mutate: func(attrs [][]byte) [][]byte { attrs[2] = cmstest.Seq(cmstest.OID(cmstest.OIDSigCertV2), cmstest.Set(0x31, cmstest.Seq(cmstest.Seq(cmstest.Seq(cmstest.Octets(make([]byte, 32))))))) return attrs }}, ana)}, {"no message-digest, its attribute removed", cmstest.Signature(msg, cmstest.Options{Mutate: func(attrs [][]byte) [][]byte { return append(attrs[:1], attrs[2:]...) }}, ana)}, {"two signature-time-stamp attributes", cmstest.Signature(msg, cmstest.Options{Token: tok, TimeStamps2: true}, ana)}, {"a signature-time-stamp of two values", cmstest.Signature(msg, cmstest.Options{Token: tok, Token2: true}, ana)}, {"unsigned attributes out of order", cmstest.Edit(sealed, func(b []byte) []byte { k := append(slices.Clone(cmstest.Children(b)), cmstest.Attr(unknownOID, cmstest.Null())) slices.SortFunc(k, func(x, y []byte) int { return bytes.Compare(y, x) }) return cmstest.TLV(0xa1, k...) }, path(firstSignerInfo(sealed), 6)...)}, {"an unsigned attribute without a value", cmstest.Signature(msg, cmstest.Options{ExtraUnsigned: [][]byte{cmstest.Seq(cmstest.OID(unknownOID), cmstest.Set(0x31))}}, ana)}, {"a signature-time-stamp attribute twice, the same", cmstest.Signature(msg, cmstest.Options{ExtraUnsigned: [][]byte{cmstest.Attr(cmstest.OIDTimeStamp, cmstest.Null()), cmstest.Attr(cmstest.OIDTimeStamp, cmstest.Null())}}, ana)}, {"ContentInfo as a SET", append([]byte{0x31}, good[1:]...)}, {"ContentInfo as [3]", append([]byte{0xa3}, good[1:]...)}, {"an attribute without a value", cmstest.Signature(msg, cmstest.Options{ExtraAttrs: [][]byte{cmstest.Seq(cmstest.OID(cmstest.OIDContentType), cmstest.Set(0x31))}}, ana)}, {"a second content-type", cmstest.Signature(msg, cmstest.Options{ExtraAttrs: [][]byte{cmstest.Seq(cmstest.OID(cmstest.OIDContentType), cmstest.Set(0x31, cmstest.OID(cmstest.OIDData)))}}, ana)}, {"the content type whose arc wraps to id-signedData in 64 bits", cmstest.Edit(good, cmstest.Replace(wrapOID(cmstest.OIDSignedData, 64)), 0)}, {"eContentType whose arc wraps to id-data in 64 bits", cmstest.Edit(good, cmstest.Replace(cmstest.Seq(wrapOID(cmstest.OIDData, 64))), path(sd, 2)...)}, } { addF(c.name, c.b) } // What decides nothing (form_test.go), and more of it. v1 := cmstest.NewCert(cmstest.CertSpec{CN: "Intermedia v1", NoVersion: true}, ecKey2) bigArc := func(oid asn1.ObjectIdentifier, bits uint, values ...[]byte) []byte { return cmstest.Seq(wrapOID(oid, bits), cmstest.Set(0x31, values...)) } for _, c := range []struct { name string o cmstest.Options }{ {"an attribute certificate [1]", cmstest.Options{ExtraCerts: [][]byte{cmstest.TLV(0xa1, cmstest.Seq(cmstest.Int(1)))}}}, {"the other choices [0], [2] and [3]", cmstest.Options{ExtraCerts: [][]byte{cmstest.TLV(0xa0, cmstest.Null()), cmstest.TLV(0xa2, cmstest.Null()), cmstest.TLV(0xa3, cmstest.Null())}}}, {"the certificate twice", cmstest.Options{ExtraCerts: [][]byte{ana.Cert.Raw}}}, {"the certificate three times", cmstest.Options{ExtraCerts: [][]byte{ana.Cert.Raw, ana.Cert.Raw}}}, {"a certificate of version 1", cmstest.Options{ExtraCerts: [][]byte{v1.Cert.Raw}}}, {"a certificate that is not one", cmstest.Options{ExtraCerts: [][]byte{cmstest.Seq(cmstest.Int(1))}}}, {"two OCSP responses", cmstest.Options{OCSP: cmstest.Seq(cmstest.Int(0)), CRLs: [][]byte{cmstest.TLV(0xa1, cmstest.OID(cmstest.OIDOCSP), cmstest.Seq(cmstest.Int(9)))}}}, {"one OCSP response twice", cmstest.Options{CRLs: [][]byte{cmstest.TLV(0xa1, cmstest.OID(cmstest.OIDOCSP), cmstest.Seq(cmstest.Int(9))), cmstest.TLV(0xa1, cmstest.OID(cmstest.OIDOCSP), cmstest.Seq(cmstest.Int(9)))}}}, {"an attribute with an arc of 2^31", cmstest.Options{ExtraAttrs: [][]byte{cmstest.BigArcAttr()}}}, {"a signing-time", cmstest.Options{ExtraAttrs: [][]byte{cmstest.Attr(cmstest.OIDSigningTime, cmstest.UTCTime("260930120000Z"))}}}, {"an unknown attribute of two values", cmstest.Options{ExtraAttrs: [][]byte{cmstest.Attr(unknownOID, cmstest.Int(1), cmstest.Int(2))}}}, {"an unknown attribute of one value twice", cmstest.Options{ExtraAttrs: [][]byte{cmstest.Attr(unknownOID, cmstest.Null(), cmstest.Null())}}}, {"an unknown attribute twice, the same", cmstest.Options{ExtraAttrs: [][]byte{cmstest.Attr(unknownOID, cmstest.Null()), cmstest.Attr(unknownOID, cmstest.Null())}}}, {"a signing-certificate beside the v2", cmstest.Options{SigCertV1: true}}, {"a wrong signing-certificate, and v2", cmstest.Options{ExtraAttrs: [][]byte{cmstest.Attr(cmstest.OIDSigCertV1, cmstest.Seq(cmstest.Seq(cmstest.Seq(cmstest.Octets(make([]byte, 20))))))}}}, {"an ESSCertIDv2 of SHA-256 written", cmstest.Options{ESSHashAlg: cmstest.HashAlg(crypto.SHA256)}}, {"an ESSCertIDv2 of SHA-256 with NULL", cmstest.Options{ESSHashAlg: cmstest.AlgID(cmstest.OIDSHA256, cmstest.Null())}}, {"an ESSCertIDv2 of SHA-384", cmstest.Options{ESSHashAlg: cmstest.HashAlg(crypto.SHA384)}}, {"an ESSCertIDv2 of SHA-512", cmstest.Options{ESSHashAlg: cmstest.HashAlg(crypto.SHA512)}}, {"an unknown unsigned attribute", cmstest.Options{Token: tok, ExtraUnsigned: [][]byte{cmstest.BigArcAttr()}}}, {"unsigned attributes, no time-stamp", cmstest.Options{ExtraUnsigned: [][]byte{cmstest.BigArcAttr()}}}, {"a sid by subjectKeyIdentifier", cmstest.Options{SKI: true}}, {"the version written 1, as by default", cmstest.Options{Version: 1}}, {"a CAdES-T", cmstest.Options{Token: tok}}, {"a CAdES-T of a token that does not read", cmstest.Options{Token: func([]byte) []byte { return cmstest.Seq(cmstest.Int(1)) }}}, {"an attribute whose arc wraps to content-type in 64 bits", cmstest.Options{ExtraAttrs: [][]byte{bigArc(cmstest.OIDContentType, 64, cmstest.OID(cmstest.OIDData))}}}, {"an attribute whose arc wraps to content-type in 32 bits", cmstest.Options{ExtraAttrs: [][]byte{bigArc(cmstest.OIDContentType, 32, cmstest.Null())}}}, {"an attribute whose arc wraps to message-digest in 64 bits", cmstest.Options{ExtraAttrs: [][]byte{bigArc(cmstest.OIDMessageDigest, 64, cmstest.Octets(nil))}}}, {"an attribute whose arc wraps to signing-certificate-v2 in 64 bits", cmstest.Options{ExtraAttrs: [][]byte{bigArc(cmstest.OIDSigCertV2, 64, cmstest.Null())}}}, {"an unsigned attribute whose arc wraps to signature-time-stamp in 64 bits, twice", cmstest.Options{Token: tok, ExtraUnsigned: [][]byte{bigArc(cmstest.OIDTimeStamp, 64, cmstest.Null(), cmstest.Int(1))}}}, {"an unsigned attribute whose arc wraps to signature-time-stamp in 32 bits", cmstest.Options{ExtraUnsigned: [][]byte{bigArc(cmstest.OIDTimeStamp, 32, cmstest.Null())}}}, {"an OCSP response and a CRL of a SET", cmstest.Options{OCSP: cmstest.Set(0x31, cmstest.Null())}}, {"a junk unsigned attribute of 300 bytes", cmstest.Options{Junk: 300}}, } { addF("decides nothing: "+c.name, cmstest.Signature(msg, c.o, ana)) } addF("certificates and OCSP", cmstest.Signature(msg, cmstest.Options{OCSP: cmstest.Seq(cmstest.Int(0)), ExtraCerts: [][]byte{ana.Cert.Raw, v1.Cert.Raw}}, ana, luis)) // A co-signature finds each certificate by issuer and serial, or by SKI. issuerName := cmstest.Name(cmstest.ATV(cmstest.OIDCommonName, cmstest.UTF8("CA de prueba"))) for _, c := range []struct { name string pair [2]cmstest.CertSpec ski bool }{ {"one issuer, two serials", [2]cmstest.CertSpec{{CN: "A", Issuer: issuerName, Serial: cmstest.Int(1)}, {CN: "B", Issuer: issuerName, Serial: cmstest.Int(2)}}, false}, {"one serial, two issuers", [2]cmstest.CertSpec{{CN: "A", Serial: cmstest.Int(5)}, {CN: "B", Serial: cmstest.Int(5)}}, false}, {"one serial, two issuers by SKI", [2]cmstest.CertSpec{{CN: "A", Serial: cmstest.Int(5)}, {CN: "B", Serial: cmstest.Int(5)}}, true}, } { a, b := cmstest.NewCert(c.pair[0], ecKey), cmstest.NewCert(c.pair[1], ecKey2) addF("co-signature: "+c.name, cmstest.Signature(msg, cmstest.Options{SKI: c.ski}, a, b)) } inn := cmstest.ATV(asn1.ObjectIdentifier{1, 2, 643, 3, 131, 1, 1}, cmstest.Numeric("123456789012")) addF("a NumericString in the name of the signer", cmstest.Signature(msg, cmstest.Options{}, cmstest.NewCert(cmstest.CertSpec{Subject: cmstest.Name(cn(cmstest.UTF8("Ivan Petrov")), inn)}, ecKey))) var formCases []any for _, c := range form { formCases = append(formCases, g.sigCase(c.name, c.b)) } // The algorithms (cms_test.go, verify_test.go). var algs []named addA := func(name string, b []byte) { algs = append(algs, named{name, b}) } rsa2048 := cmstest.NewRSA("Ana López", 2048, from, to) p256 := cmstest.NewECDSA("Luis", elliptic.P256(), from, to) p384s := cmstest.NewECDSA("Eva", elliptic.P384(), from, to) p521s := cmstest.NewECDSA("Raúl", elliptic.P521(), from, to) for _, c := range []struct { name string o cmstest.Options s cmstest.Signer }{ {"RSA PKCS1 SHA-256", cmstest.Options{}, rsa2048}, {"RSA PKCS1 SHA-384", cmstest.Options{Hash: crypto.SHA384}, rsa2048}, {"RSA PKCS1 SHA-512", cmstest.Options{Hash: crypto.SHA512}, rsa2048}, {"RSA PSS SHA-256", cmstest.Options{PSS: true}, rsa2048}, {"RSA PSS SHA-512", cmstest.Options{PSS: true, Hash: crypto.SHA512}, rsa2048}, {"RSA by subjectKeyIdentifier", cmstest.Options{SKI: true}, rsa2048}, {"ECDSA P-256", cmstest.Options{}, p256}, {"ECDSA P-384 SHA-384", cmstest.Options{Hash: crypto.SHA384}, p384s}, {"ECDSA P-521 SHA-512", cmstest.Options{Hash: crypto.SHA512}, p521s}, } { addA(c.name, cmstest.Signature(msg, c.o, c.s)) } addA("a co-signature of ECDSA and RSA", cmstest.Signature(msg, cmstest.Options{}, cmstest.NewECDSA("Ana", elliptic.P256(), from, to), cmstest.NewRSA("Banco S.A.", 2048, from, to))) addA("RSA of 1024 bits", cmstest.Signature(msg, cmstest.Options{}, cmstest.NewRSA("Chica", 1024, from, to))) alg, null := cmstest.AlgID, cmstest.Null h0 := cmstest.TLV(0xa0, cmstest.HashAlg(crypto.SHA256)) m1 := cmstest.TLV(0xa1, alg(cmstest.OIDMGF1, cmstest.HashAlg(crypto.SHA256))) s2 := cmstest.TLV(0xa2, cmstest.Int(32)) p384 := cmstest.NewCert(cmstest.CertSpec{CN: "P-384"}, g.p384Key) p521 := cmstest.NewCert(cmstest.CertSpec{CN: "P-521"}, g.p521Key) for _, c := range []struct { name string s cmstest.Signer o cmstest.Options }{ {"SHA-1, ECDSA", ana, cmstest.Options{Hash: crypto.SHA1}}, {"SHA-1, RSA", luis, cmstest.Options{Hash: crypto.SHA1}}, {"SHA-1, and another message", ana, cmstest.Options{Hash: crypto.SHA1, Message: []byte("other")}}, {"SHA-256 with NULL", ana, cmstest.Options{DigestAlg: alg(cmstest.OIDSHA256, null())}}, {"SHA-256 with an INTEGER", ana, cmstest.Options{DigestAlg: alg(cmstest.OIDSHA256, cmstest.Int(0))}}, {"SHA-256 whose arc wraps in 64 bits", ana, cmstest.Options{DigestAlg: cmstest.Seq(wrapOID(cmstest.OIDSHA256, 64))}}, {"SHA-256 of three fields", ana, cmstest.Options{DigestAlg: alg(cmstest.OIDSHA256, null(), null())}}, {"P-256 with SHA-384", ana, cmstest.Options{Hash: crypto.SHA384}}, {"P-256 with SHA-512", ana, cmstest.Options{Hash: crypto.SHA512}}, {"P-384 with SHA-384", p384, cmstest.Options{Hash: crypto.SHA384}}, {"P-384 with SHA-256", p384, cmstest.Options{}}, {"P-384 with SHA-512", p384, cmstest.Options{Hash: crypto.SHA512}}, {"P-521 with SHA-512", p521, cmstest.Options{Hash: crypto.SHA512}}, {"P-521 with SHA-256", p521, cmstest.Options{}}, {"P-521 with SHA-384", p521, cmstest.Options{Hash: crypto.SHA384}}, {"rsaEncryption", luis, cmstest.Options{}}, {"rsaEncryption without parameters", luis, cmstest.Options{SigAlg: alg(cmstest.OIDRSA)}}, {"rsaEncryption with an INTEGER", luis, cmstest.Options{SigAlg: alg(cmstest.OIDRSA, cmstest.Int(0))}}, {"rsaEncryption with SHA-512", luis, cmstest.Options{Hash: crypto.SHA512}}, {"rsaEncryption with SHA-384", luis, cmstest.Options{Hash: crypto.SHA384}}, {"sha256WithRSAEncryption", luis, cmstest.Options{SigAlg: alg(cmstest.OIDSHA256RSA, null())}}, {"sha256WithRSAEncryption without NULL", luis, cmstest.Options{SigAlg: alg(cmstest.OIDSHA256RSA)}}, {"sha256WithRSAEncryption, INTEGER", luis, cmstest.Options{SigAlg: alg(cmstest.OIDSHA256RSA, cmstest.Int(0))}}, {"sha256WithRSAEncryption, SHA-384", luis, cmstest.Options{Hash: crypto.SHA384, SigAlg: alg(cmstest.OIDSHA256RSA, null())}}, {"sha384WithRSAEncryption", luis, cmstest.Options{Hash: crypto.SHA384, SigAlg: alg(cmstest.OIDSHA384RSA, null())}}, {"sha384WithRSAEncryption, SHA-256", luis, cmstest.Options{SigAlg: alg(cmstest.OIDSHA384RSA, null())}}, {"sha384WithRSAEncryption, INTEGER", luis, cmstest.Options{Hash: crypto.SHA384, SigAlg: alg(cmstest.OIDSHA384RSA, cmstest.Int(0))}}, {"sha512WithRSAEncryption", luis, cmstest.Options{Hash: crypto.SHA512, SigAlg: alg(cmstest.OIDSHA512RSA, null())}}, {"sha512WithRSAEncryption, SHA-256", luis, cmstest.Options{SigAlg: alg(cmstest.OIDSHA512RSA, null())}}, {"sha512WithRSAEncryption, INTEGER", luis, cmstest.Options{Hash: crypto.SHA512, SigAlg: alg(cmstest.OIDSHA512RSA, cmstest.Int(0))}}, {"sha1WithRSAEncryption", luis, cmstest.Options{Hash: crypto.SHA1, SigAlg: alg(asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 5}, null())}}, {"ecdsa-with-SHA256 with NULL", ana, cmstest.Options{SigAlg: alg(cmstest.OIDECDSA256, null())}}, {"ecdsa-with-SHA256, SHA-384", ana, cmstest.Options{Hash: crypto.SHA384, SigAlg: alg(cmstest.OIDECDSA256)}}, {"ecdsa-with-SHA384, SHA-256", ana, cmstest.Options{SigAlg: alg(cmstest.OIDECDSA384)}}, {"ecdsa-with-SHA384 with NULL", ana, cmstest.Options{Hash: crypto.SHA384, SigAlg: alg(cmstest.OIDECDSA384, null())}}, {"ecdsa-with-SHA512, SHA-256", ana, cmstest.Options{SigAlg: alg(cmstest.OIDECDSA512)}}, {"ecdsa-with-SHA512 with NULL", ana, cmstest.Options{Hash: crypto.SHA512, SigAlg: alg(cmstest.OIDECDSA512, null())}}, {"ecdsa-with-SHA256 whose arc wraps in 64 bits", ana, cmstest.Options{SigAlg: cmstest.Seq(wrapOID(cmstest.OIDECDSA256, 64))}}, {"an algorithm outside the table", ana, cmstest.Options{SigAlg: alg(asn1.ObjectIdentifier{1, 3, 101, 112})}}, {"another algorithm, an RSA key", luis, cmstest.Options{SigAlg: alg(asn1.ObjectIdentifier{1, 3, 101, 112})}}, {"another algorithm with PSS parameters", luis, cmstest.Options{PSS: true, SigAlg: alg(asn1.ObjectIdentifier{1, 2, 3, 4}, cmstest.Seq(h0, m1, s2))}}, {"PSS", luis, cmstest.Options{PSS: true}}, {"PSS with SHA-384", luis, cmstest.Options{PSS: true, Hash: crypto.SHA384}}, {"PSS with SHA-512", luis, cmstest.Options{PSS: true, Hash: crypto.SHA512}}, {"PSS, its fields written again", luis, cmstest.Options{PSS: true, SigAlg: pss(h0, m1, s2)}}, {"PSS with NULL in its hashes", luis, cmstest.Options{PSS: true, SigAlg: pss(cmstest.TLV(0xa0, alg(cmstest.OIDSHA256, null())), cmstest.TLV(0xa1, alg(cmstest.OIDMGF1, alg(cmstest.OIDSHA256, null()))), s2)}}, {"PSS without parameters", luis, cmstest.Options{PSS: true, SigAlg: alg(cmstest.OIDPSS)}}, {"PSS with NULL parameters", luis, cmstest.Options{PSS: true, SigAlg: alg(cmstest.OIDPSS, null())}}, {"PSS parameters as a SET", luis, cmstest.Options{PSS: true, SigAlg: alg(cmstest.OIDPSS, cmstest.TLV(0x31, h0, m1, s2))}}, {"PSS with [0] of two elements", luis, cmstest.Options{PSS: true, SigAlg: pss(cmstest.TLV(0xa0, cmstest.HashAlg(crypto.SHA256), null()), m1, s2)}}, {"PSS with [0] primitive", luis, cmstest.Options{PSS: true, SigAlg: pss(cmstest.TLV(0x80, cmstest.HashAlg(crypto.SHA256)), m1, s2)}}, {"PSS with [1] before [0]", luis, cmstest.Options{PSS: true, SigAlg: pss(m1, h0, s2)}}, {"PSS with [0] twice", luis, cmstest.Options{PSS: true, SigAlg: pss(h0, h0, m1, s2)}}, {"PSS of SHA-512 with SHA-256", luis, cmstest.Options{PSS: true, SigAlg: pss(cmstest.TLV(0xa0, cmstest.HashAlg(crypto.SHA512)), m1, s2)}}, {"PSS with a hash of an INTEGER", luis, cmstest.Options{PSS: true, SigAlg: pss(cmstest.TLV(0xa0, alg(cmstest.OIDSHA256, cmstest.Int(0))), m1, s2)}}, {"PSS with a hash without an OID", luis, cmstest.Options{PSS: true, SigAlg: pss(cmstest.TLV(0xa0, cmstest.Seq(cmstest.Int(0))), m1, s2)}}, {"PSS with another mask", luis, cmstest.Options{PSS: true, SigAlg: pss(h0, cmstest.TLV(0xa1, alg(asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 9}, cmstest.HashAlg(crypto.SHA256))), s2)}}, {"PSS with MGF1 without its hash", luis, cmstest.Options{PSS: true, SigAlg: pss(h0, cmstest.TLV(0xa1, alg(cmstest.OIDMGF1)), s2)}}, {"PSS with MGF1 not an algorithm", luis, cmstest.Options{PSS: true, SigAlg: pss(h0, cmstest.TLV(0xa1, cmstest.Seq(cmstest.Int(1))), s2)}}, {"PSS with MGF1 of SHA-512", luis, cmstest.Options{PSS: true, SigAlg: pss(h0, cmstest.TLV(0xa1, alg(cmstest.OIDMGF1, cmstest.HashAlg(crypto.SHA512))), s2)}}, {"PSS with MGF1 of a hash with INTEGER", luis, cmstest.Options{PSS: true, SigAlg: pss(h0, cmstest.TLV(0xa1, alg(cmstest.OIDMGF1, alg(cmstest.OIDSHA256, cmstest.Int(0)))), s2)}}, {"PSS with MGF1 of a hash without OID", luis, cmstest.Options{PSS: true, SigAlg: pss(h0, cmstest.TLV(0xa1, alg(cmstest.OIDMGF1, cmstest.Seq(cmstest.Int(0)))), s2)}}, {"PSS with MGF1 of a hash of three fields", luis, cmstest.Options{PSS: true, SigAlg: pss(h0, cmstest.TLV(0xa1, alg(cmstest.OIDMGF1, alg(cmstest.OIDSHA256, null(), null()))), s2)}}, {"PSS with a salt of 20", luis, cmstest.Options{PSS: true, SigAlg: pss(h0, m1, cmstest.TLV(0xa2, cmstest.Int(20)))}}, {"PSS with a salt of 32 in two bytes", luis, cmstest.Options{PSS: true, SigAlg: pss(h0, m1, cmstest.TLV(0xa2, cmstest.IntBytes([]byte{0, 32})))}}, {"PSS with a salt in OCTETS", luis, cmstest.Options{PSS: true, SigAlg: pss(h0, m1, cmstest.TLV(0xa2, cmstest.Octets([]byte{32})))}}, {"PSS with a salt of 2^64 + 32", luis, cmstest.Options{PSS: true, SigAlg: pss(h0, m1, cmstest.TLV(0xa2, cmstest.IntBytes([]byte{1, 0, 0, 0, 0, 0, 0, 0, 32})))}}, {"PSS with a salt of 2^32 + 32", luis, cmstest.Options{PSS: true, SigAlg: pss(h0, m1, cmstest.TLV(0xa2, cmstest.IntBytes([]byte{1, 0, 0, 0, 32})))}}, {"PSS with a negative salt", luis, cmstest.Options{PSS: true, SigAlg: pss(h0, m1, cmstest.TLV(0xa2, cmstest.Int(-224)))}}, {"PSS with trailerField", luis, cmstest.Options{PSS: true, PSSTrailer: true}}, {"PSS with a field [4]", luis, cmstest.Options{PSS: true, SigAlg: pss(h0, m1, s2, cmstest.TLV(0xa4, cmstest.Int(1)))}}, {"PSS without [0]", luis, cmstest.Options{PSS: true, SigAlg: pss(m1, s2)}}, {"PSS without [1]", luis, cmstest.Options{PSS: true, SigAlg: pss(h0, s2)}}, {"PSS without [2]", luis, cmstest.Options{PSS: true, SigAlg: pss(h0, m1)}}, {"PSS whose arc wraps in 64 bits", luis, cmstest.Options{PSS: true, SigAlg: cmstest.Seq(wrapOID(cmstest.OIDPSS, 64), cmstest.Seq(h0, m1, s2))}}, {"step 3: an RSA key with ECDSA", luis, cmstest.Options{SigAlg: alg(cmstest.OIDECDSA256)}}, {"step 3: an RSA key with ECDSA, another message", luis, cmstest.Options{SigAlg: alg(cmstest.OIDECDSA256), Message: []byte("other")}}, {"step 3: an EC key with PKCS #1", ana, cmstest.Options{SigAlg: alg(cmstest.OIDRSA, null())}}, {"step 3: an EC key with PSS", ana, cmstest.Options{SigAlg: pss(h0, m1, s2)}}, {"step 3: a P-384 key with sha256WithRSAEncryption", p384, cmstest.Options{SigAlg: alg(cmstest.OIDSHA256RSA, null())}}, {"step 3: PKCS #1, a bit flipped", luis, cmstest.Options{CorruptSignature: true}}, {"step 3: PSS, a bit flipped", luis, cmstest.Options{PSS: true, CorruptSignature: true}}, {"step 3: ECDSA, a bit flipped", ana, cmstest.Options{CorruptSignature: true}}, {"step 3: P-521, a bit flipped", p521, cmstest.Options{Hash: crypto.SHA512, CorruptSignature: true}}, {"step 3: the digest of another message", ana, cmstest.Options{Message: []byte("other")}}, {"step 3: the digest of another message, RSA", luis, cmstest.Options{Message: []byte("other")}}, } { addA(c.name, cmstest.Signature(msg, c.o, c.s)) } // The keys of the table (verify_test.go). n, e := rsaKey.N, big.NewInt(65537) two := func(bits uint) *big.Int { return new(big.Int).Add(new(big.Int).Lsh(big.NewInt(1), bits), big.NewInt(1)) } key := func(fields ...[]byte) []byte { return cmstest.BitString(cmstest.Seq(fields...)) } rsaAlg := cmstest.AlgID(cmstest.OIDRSA, cmstest.Null()) ecAlg := cmstest.AlgID(cmstest.OIDECPublicKey, cmstest.OID(cmstest.OIDP256)) point := cmstest.Uncompressed(&ecKey.PublicKey) off := bytes.Clone(point) off[len(off)-1] ^= 1 even := evenPointKey() evenPoint := cmstest.Uncompressed(&even.PublicKey) var keyCases []named for _, c := range []struct { name string spki []byte key crypto.Signer }{ {"RSA of 2048 bits", cmstest.SPKIRSA(n, e), rsaKey}, {"RSA of 2047 bits", cmstest.SPKIRSA(two(2046), e), rsaKey}, {"RSA of 4096 bits that is another", cmstest.SPKIRSA(two(4095), e), rsaKey}, {"RSA of 4097 bits", cmstest.SPKIRSA(two(4096), e), rsaKey}, {"an even modulus", cmstest.SPKIRSA(new(big.Int).Add(n, big.NewInt(1)), e), rsaKey}, {"a negative modulus", cmstest.SPKIRSA(new(big.Int).Neg(n), e), rsaKey}, {"a modulus with a zero byte more in front", cmstest.Seq(rsaAlg, key(cmstest.IntBytes(append([]byte{0, 0}, n.Bytes()...)), cmstest.BigInt(e))), rsaKey}, {"an exponent of 1", cmstest.SPKIRSA(n, big.NewInt(1)), rsaKey}, {"an exponent of 3", cmstest.SPKIRSA(n, big.NewInt(3)), rsaKey}, {"an even exponent", cmstest.SPKIRSA(n, big.NewInt(65536)), rsaKey}, {"an exponent of 2^31 - 1", cmstest.SPKIRSA(n, big.NewInt(1<<31-1)), rsaKey}, {"an exponent of 2^31 + 1", cmstest.SPKIRSA(n, big.NewInt(1<<31+1)), rsaKey}, {"an exponent of 2^64 + 65537", cmstest.SPKIRSA(n, new(big.Int).Add(new(big.Int).Lsh(big.NewInt(1), 64), e)), rsaKey}, {"a negative exponent", cmstest.SPKIRSA(n, big.NewInt(-1)), rsaKey}, {"an exponent of 0", cmstest.SPKIRSA(n, big.NewInt(0)), rsaKey}, {"an RSAPublicKey with a byte more", cmstest.Seq(rsaAlg, cmstest.BitString(append(cmstest.Seq(cmstest.BigInt(n), cmstest.BigInt(e)), 0))), rsaKey}, {"an RSAPublicKey as a SET", cmstest.Seq(rsaAlg, cmstest.BitString(cmstest.TLV(0x31, cmstest.BigInt(n), cmstest.BigInt(e)))), rsaKey}, {"an RSAPublicKey of three INTEGERs", cmstest.Seq(rsaAlg, key(cmstest.BigInt(n), cmstest.BigInt(e), cmstest.Int(1))), rsaKey}, {"an RSAPublicKey that is an INTEGER", cmstest.Seq(rsaAlg, cmstest.BitString(cmstest.BigInt(n))), rsaKey}, {"an RSAPublicKey not in DER", cmstest.Seq(rsaAlg, cmstest.BitString(append([]byte{0x30, 0x82, 0x01, 0x0a}, append(cmstest.BigInt(n), cmstest.TLV(0x02, []byte{0, 1, 0, 1})...)...))), rsaKey}, {"a modulus in OCTETS", cmstest.Seq(rsaAlg, key(cmstest.Octets(append([]byte{0}, n.Bytes()...)), cmstest.BigInt(e))), rsaKey}, {"an exponent in OCTETS", cmstest.Seq(rsaAlg, key(cmstest.BigInt(n), cmstest.Octets(e.Bytes()))), rsaKey}, {"rsaEncryption without NULL", cmstest.Seq(cmstest.AlgID(cmstest.OIDRSA), key(cmstest.BigInt(n), cmstest.BigInt(e))), rsaKey}, {"rsaEncryption whose arc wraps in 64 bits", cmstest.Seq(cmstest.Seq(wrapOID(cmstest.OIDRSA, 64), cmstest.Null()), key(cmstest.BigInt(n), cmstest.BigInt(e))), rsaKey}, {"an RSA key of id-RSASSA-PSS", cmstest.Seq(cmstest.AlgID(cmstest.OIDPSS, cmstest.Null()), key(cmstest.BigInt(n), cmstest.BigInt(e))), rsaKey}, {"an SPKI of three elements", cmstest.Seq(rsaAlg, key(cmstest.BigInt(n), cmstest.BigInt(e)), cmstest.Null()), rsaKey}, {"an SPKI whose key is OCTETS", cmstest.Seq(rsaAlg, cmstest.Octets(append([]byte{0}, cmstest.Seq(cmstest.BigInt(n), cmstest.BigInt(e))...))), rsaKey}, {"an SPKI whose algorithm is a SET", cmstest.Seq(cmstest.TLV(0x31, cmstest.OID(cmstest.OIDRSA), cmstest.Null()), key(cmstest.BigInt(n), cmstest.BigInt(e))), rsaKey}, {"an SPKI of no algorithm", cmstest.Seq(cmstest.Seq(cmstest.Int(1)), key(cmstest.BigInt(n), cmstest.BigInt(e))), rsaKey}, {"an RSA key in a BIT STRING of 1 unused bit", cmstest.Seq(rsaAlg, cmstest.TLV(0x03, append([]byte{1}, cmstest.Seq(cmstest.BigInt(n), cmstest.BigInt(e))...))), rsaKey}, {"P-256", cmstest.SPKIEC(cmstest.OID(cmstest.OIDP256), point), ecKey}, {"a compressed point", cmstest.SPKICompressed(&ecKey.PublicKey), ecKey}, {"a point off the curve", cmstest.SPKIEC(cmstest.OID(cmstest.OIDP256), off), ecKey}, {"a point of P-256 said P-384", cmstest.SPKIEC(cmstest.OID(cmstest.OIDP384), point), ecKey}, {"brainpoolP256r1", cmstest.SPKIEC(cmstest.OID(cmstest.OIDBrainpoolP256), point), ecKey}, {"a point of P-521, another curve", cmstest.SPKIEC(cmstest.OID(cmstest.OIDBrainpoolP256), cmstest.Uncompressed(&g.p521Key.PublicKey)), g.p521Key}, {"P-256 whose arc wraps in 64 bits", cmstest.SPKIEC(wrapOID(cmstest.OIDP256, 64), point), ecKey}, {"id-ecPublicKey whose arc wraps in 64 bits", cmstest.Seq(cmstest.Seq(wrapOID(cmstest.OIDECPublicKey, 64), cmstest.OID(cmstest.OIDP256)), cmstest.BitString(point)), ecKey}, {"id-ecPublicKey without a curve", cmstest.Seq(cmstest.AlgID(cmstest.OIDECPublicKey), cmstest.BitString(point)), ecKey}, {"id-ecPublicKey with NULL", cmstest.SPKIEC(cmstest.Null(), point), ecKey}, {"id-ecPublicKey with specified parameters", cmstest.SPKIEC(cmstest.Seq(cmstest.Int(1)), point), ecKey}, {"an EC key of id-ecDH", cmstest.Seq(cmstest.AlgID(asn1.ObjectIdentifier{1, 3, 132, 1, 12}, cmstest.OID(cmstest.OIDP256)), cmstest.BitString(point)), ecKey}, {"a BIT STRING of 1 unused bit", cmstest.Seq(ecAlg, cmstest.TLV(0x03, append([]byte{1}, evenPoint...))), even}, {"an empty BIT STRING", cmstest.Seq(ecAlg, cmstest.TLV(0x03, []byte{0})), ecKey}, {"a point with a byte more", cmstest.SPKIEC(cmstest.OID(cmstest.OIDP256), append(slices.Clone(point), 0)), ecKey}, {"the point at infinity", cmstest.SPKIEC(cmstest.OID(cmstest.OIDP256), []byte{0}), ecKey}, {"a hybrid point", cmstest.SPKIEC(cmstest.OID(cmstest.OIDP256), append([]byte{6 | point[len(point)-1]&1}, point[1:]...)), ecKey}, } { s := cmstest.NewCert(cmstest.CertSpec{CN: "Clave", SPKI: c.spki}, c.key) keyCases = append(keyCases, named{c.name, cmstest.Signature(msg, cmstest.Options{}, s)}) } var algCases, keyOut []any for _, c := range algs { algCases = append(algCases, g.sigCase(c.name, c.b)) } for _, c := range keyCases { keyOut = append(keyOut, g.sigCase(c.name, c.b)) } f.sections = []section{{"form", formCases}} fa.sections = []section{{"algorithms", algCases}, {"keys", keyOut}} f.compress() fa.compress() return f, fa } // --------------------------------------------------------------------------- // cms_tokens.json // TokCase is a token, given or as an edit of a base, the subject it is // checked over when it is not that of the file, and what cms.ParseToken // gives. type TokCase struct { Name string `json:"name,omitempty"` DER any `json:"der,omitempty"` Subject string `json:"subject,omitempty"` Edit TokOut raw []byte } func tstFields(subject []byte, genTime []byte) [][]byte { h := sha256.Sum256(subject) return [][]byte{ cmstest.Int(1), cmstest.OID(unknownOID), cmstest.Seq(cmstest.Seq(cmstest.OID(cmstest.OIDSHA256)), cmstest.Octets(h[:])), cmstest.Int(42), genTime, } } func tstInfo(subject []byte, genTime []byte, after ...[]byte) []byte { return cmstest.Seq(append(tstFields(subject, genTime), after...)...) } func (g *gen) tokenFile() *vfile { f := &vfile{name: "tokens", description: "cms.ParseToken: the error and its kind (form for ErrForm, the verdict S2, or algorithm for ErrAlgorithm, S1), or genTime, the accuracy in microseconds, the spans of the algorithm of the messageImprint, of the imprint and of the certificate of the authority, ImprintIsSHA256, and Token.Check over the subject of the file. The tokens of the tests of internal/cms (cms_test.go, form_test.go, verify_test.go, hostile_test.go), and others.", every: map[string]int{"tokens": 4, "tstinfo": 3}, head: [][2]any{{"subject", hx(sealSubject)}}} tsa, luis := g.tsa, g.luis ecKey, ecKey2 := g.ecKey, g.ecKey2 subject := sealSubject var toks []named add := func(name string, b []byte) { toks = append(toks, named{name, b}) } tok := func(o cmstest.TokenOptions, s cmstest.Signer) []byte { return cmstest.Token(subject, now, o, s) } add("a token of ECDSA P-256", tok(cmstest.TokenOptions{}, tsa)) add("a token over a signature value, with an accuracy of 2 s", func() []byte { return cmstest.Token(subject, now, cmstest.TokenOptions{Accuracy: 2 * time.Second}, cmstest.NewRSA("TSA de prueba", 2048, from, to)) }()) tsaP := cmstest.NewECDSA("TSA", elliptic.P256(), from, to) old := cmstest.NewECDSA("TSA caducada", elliptic.P256(), from, time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)) add("another imprint", cmstest.Token(subject, now, cmstest.TokenOptions{Imprint: bytes.Repeat([]byte{1}, 32)}, tsaP)) add("the TSA had expired", cmstest.Token(subject, now, cmstest.TokenOptions{}, old)) add("a TSTInfo of version 2", cmstest.Token(subject, now, cmstest.TokenOptions{Version: 2}, tsaP)) add("not DER", []byte{0x30, 0x80, 0x00, 0x00}) add("empty", nil) add("a key of 1024 bits", cmstest.Token(subject, now, cmstest.TokenOptions{}, cmstest.NewRSA("TSA 1024", 1024, from, to))) // TestTokenProfile. small := cmstest.NewCert(cmstest.CertSpec{CN: "TSA 1024"}, cmstest.RSAKey(1024)) compressed := cmstest.NewCert(cmstest.CertSpec{CN: "TSA comprimida", SPKI: cmstest.SPKICompressed(&ecKey2.PublicKey)}, ecKey2) notYet := cmstest.NewCert(cmstest.CertSpec{CN: "TSA futura", From: now.Add(time.Second)}, ecKey2) expired := cmstest.NewCert(cmstest.CertSpec{CN: "TSA caducada", To: now.Add(-time.Second)}, ecKey2) exact := cmstest.NewCert(cmstest.CertSpec{CN: "TSA justa", From: now, To: now}, ecKey2) window := cmstest.NewCert(cmstest.CertSpec{CN: "TSA de diez segundos", From: now, To: now.Add(10 * time.Second)}, ecKey2) badImprintAlg := cmstest.TokenRaw(cmstest.Seq(cmstest.Int(1), cmstest.OID(unknownOID), cmstest.Seq(cmstest.Seq(cmstest.Int(1)), cmstest.Octets(make([]byte, 32))), cmstest.Int(42), cmstest.GeneralizedTimeOf(now)), tsa) for _, c := range []named{ {"S1: an imprint of SHA-1", tok(cmstest.TokenOptions{Hash: crypto.SHA1}, tsa)}, {"S1: a signature of SHA-1", tok(cmstest.TokenOptions{CMS: cmstest.Options{Hash: crypto.SHA1}}, tsa)}, {"S1: a key of 1024 bits", tok(cmstest.TokenOptions{}, small)}, {"S1: a compressed key", tok(cmstest.TokenOptions{}, compressed)}, {"S1: PSS with trailerField", tok(cmstest.TokenOptions{CMS: cmstest.Options{PSS: true, PSSTrailer: true}}, luis)}, {"S1: an imprint whose arc wraps to SHA-256 in 64 bits", cmstest.TokenRaw(cmstest.Seq(cmstest.Int(1), cmstest.OID(unknownOID), cmstest.Seq(cmstest.Seq(wrapOID(cmstest.OIDSHA256, 64)), cmstest.Octets(sha256Of(subject))), cmstest.Int(42), cmstest.GeneralizedTimeOf(now)), tsa)}, {"S1: an imprint algorithm with an INTEGER", cmstest.TokenRaw(cmstest.Seq(cmstest.Int(1), cmstest.OID(unknownOID), cmstest.Seq(cmstest.AlgID(cmstest.OIDSHA256, cmstest.Int(0)), cmstest.Octets(sha256Of(subject))), cmstest.Int(42), cmstest.GeneralizedTimeOf(now)), tsa)}, {"S2 before S1: SHA-1 and version 2", tok(cmstest.TokenOptions{Hash: crypto.SHA1, Version: 2}, tsa)}, {"S2 before S1: SHA-1 and no digest", tok(cmstest.TokenOptions{Hash: crypto.SHA1, NoMessageDigest: true}, tsa)}, {"S2: an imprint algorithm that is no one", badImprintAlg}, {"S2: an imprint algorithm of three fields", cmstest.TokenRaw(cmstest.Seq(cmstest.Int(1), cmstest.OID(unknownOID), cmstest.Seq(cmstest.AlgID(cmstest.OIDSHA256, cmstest.Null(), cmstest.Null()), cmstest.Octets(sha256Of(subject))), cmstest.Int(42), cmstest.GeneralizedTimeOf(now)), tsa)}, {"S3: the signature of the authority, a bit flipped", tok(cmstest.TokenOptions{CMS: cmstest.Options{CorruptSignature: true}}, tsa)}, {"S3: the message-digest of another TSTInfo", tok(cmstest.TokenOptions{CMS: cmstest.Options{Message: []byte("other")}}, tsa)}, {"S3: an imprint of 33 bytes", tok(cmstest.TokenOptions{Imprint: append(sha256Of(subject), 0)}, tsa)}, {"S3: an imprint of 31 bytes", tok(cmstest.TokenOptions{Imprint: sha256Of(subject)[:31]}, tsa)}, {"S3: an imprint of 64 bytes, the hash twice", tok(cmstest.TokenOptions{Imprint: append(sha256Of(subject), sha256Of(subject)...)}, tsa)}, {"S3: an empty imprint", tok(cmstest.TokenOptions{Imprint: []byte{}}, tsa)}, {"S3: another imprint", tok(cmstest.TokenOptions{Imprint: make([]byte, 32)}, tsa)}, {"S3: an imprint of SHA-384 of 32 bytes", tok(cmstest.TokenOptions{Hash: crypto.SHA384, Imprint: sha256Of(subject)}, tsa)}, {"S3: an authority not yet valid", tok(cmstest.TokenOptions{}, notYet)}, {"S3: an authority expired", tok(cmstest.TokenOptions{}, expired)}, {"S3: an authority of PSS, a bit flipped", tok(cmstest.TokenOptions{CMS: cmstest.Options{PSS: true, CorruptSignature: true}}, luis)}, {"an authority valid exactly then", tok(cmstest.TokenOptions{}, exact)}, {"an authority of RSA", tok(cmstest.TokenOptions{}, luis)}, {"an authority of PSS", tok(cmstest.TokenOptions{CMS: cmstest.Options{PSS: true}}, luis)}, {"an authority of PSS with SHA-512", tok(cmstest.TokenOptions{CMS: cmstest.Options{PSS: true, Hash: crypto.SHA512}}, luis)}, {"a signature of SHA-512", tok(cmstest.TokenOptions{CMS: cmstest.Options{Hash: crypto.SHA512}}, tsa)}, {"an imprint of SHA-384", tok(cmstest.TokenOptions{Hash: crypto.SHA384}, tsa)}, {"an imprint of SHA-512", tok(cmstest.TokenOptions{Hash: crypto.SHA512}, tsa)}, {"an imprint of SHA-256 with NULL", cmstest.TokenRaw(cmstest.Seq(cmstest.Int(1), cmstest.OID(unknownOID), cmstest.Seq(cmstest.AlgID(cmstest.OIDSHA256, cmstest.Null()), cmstest.Octets(sha256Of(subject))), cmstest.Int(42), cmstest.GeneralizedTimeOf(now)), tsa)}, // The validity of the authority at its ends, to the nanosecond. {"genTime at the start of the validity", cmstest.Token(subject, now, cmstest.TokenOptions{}, window)}, {"genTime at the end of the validity", cmstest.Token(subject, now.Add(10*time.Second), cmstest.TokenOptions{}, window)}, {"genTime a nanosecond before the validity", cmstest.Token(subject, now.Add(-time.Nanosecond), cmstest.TokenOptions{}, window)}, {"genTime a nanosecond after the validity", cmstest.Token(subject, now.Add(10*time.Second+time.Nanosecond), cmstest.TokenOptions{}, window)}, {"genTime a second before the validity", cmstest.Token(subject, now.Add(-time.Second), cmstest.TokenOptions{}, window)}, {"genTime a second after the validity", cmstest.Token(subject, now.Add(11*time.Second), cmstest.TokenOptions{}, window)}, {"genTime half a second after the end", cmstest.Token(subject, now.Add(10*time.Second+500*time.Millisecond), cmstest.TokenOptions{}, window)}, {"genTime with ten digits of fraction", cmstest.TokenRaw(tstInfo(subject, cmstest.GeneralizedTime("20260930120005.1234567891Z")), cmstest.NewCert(cmstest.CertSpec{CN: "TSA", From: now, To: now.Add(time.Minute)}, ecKey2))}, } { add(c.name, c.b) } // TestTokenFormRules. info := cmstest.TSTInfo(subject, now, cmstest.TokenOptions{}) encap := path(cmstest.SignedDataPath, 2) good := tok(cmstest.TokenOptions{}, tsa) other := cmstest.NewCert(cmstest.CertSpec{CN: "Otra TSA"}, ecKey) withAttrs := func(mutate func(a [][]byte) [][]byte) []byte { return tok(cmstest.TokenOptions{CMS: cmstest.Options{Mutate: mutate}}, tsa) } ess1 := func(c []byte) []byte { return cmstest.Attr(cmstest.OIDSigCertV1, cmstest.Seq(cmstest.Seq(cmstest.Seq(cmstest.Octets(sha1Sum(c)))))) } ess2 := func(c []byte) []byte { h := sha256.Sum256(c) return cmstest.Attr(cmstest.OIDSigCertV2, cmstest.Seq(cmstest.Seq(cmstest.Seq(cmstest.Octets(h[:]))))) } twoCRLs := tok(cmstest.TokenOptions{CMS: cmstest.Options{CRLs: [][]byte{cmstest.Seq(cmstest.Int(1)), cmstest.Seq(cmstest.Int(2))}}}, tsa) for _, c := range []named{ {"two SignerInfo", cmstest.Merge(cmstest.TokenRaw(info, tsa), cmstest.TokenRaw(info, other))}, {"BER", tok(cmstest.TokenOptions{CMS: cmstest.Options{BER: true}}, tsa)}, {"id-data", cmstest.Edit(good, cmstest.Replace(cmstest.Seq(cmstest.OID(cmstest.OIDData), cmstest.TLV(0xa0, cmstest.Octets(info)))), encap...)}, {"no eContent", cmstest.Edit(good, cmstest.Replace(cmstest.Seq(cmstest.OID(cmstest.OIDTSTInfo))), encap...)}, {"an eContent [1]", cmstest.Edit(good, cmstest.Replace(cmstest.Seq(cmstest.OID(cmstest.OIDTSTInfo), cmstest.TLV(0xa1, cmstest.Octets(info)))), encap...)}, {"an eContent of two OCTET STRINGs", cmstest.Edit(good, cmstest.Replace(cmstest.Seq(cmstest.OID(cmstest.OIDTSTInfo), cmstest.TLV(0xa0, cmstest.Octets(info), cmstest.Octets(info)))), encap...)}, {"an empty eContent", cmstest.Edit(good, cmstest.Replace(cmstest.Seq(cmstest.OID(cmstest.OIDTSTInfo), cmstest.TLV(0xa0))), encap...)}, {"an eContent that is a SEQUENCE", cmstest.Edit(good, cmstest.Replace(cmstest.Seq(cmstest.OID(cmstest.OIDTSTInfo), cmstest.TLV(0xa0, cmstest.Seq(info)))), encap...)}, {"an eContent of an empty OCTET STRING", cmstest.Edit(good, cmstest.Replace(cmstest.Seq(cmstest.OID(cmstest.OIDTSTInfo), cmstest.TLV(0xa0, cmstest.Octets(nil)))), encap...)}, {"eContentType whose arc wraps in 64 bits", cmstest.Edit(good, cmstest.Replace(cmstest.Seq(wrapOID(cmstest.OIDTSTInfo, 64), cmstest.TLV(0xa0, cmstest.Octets(info)))), encap...)}, {"the content-type id-data", withAttrs(func(a [][]byte) [][]byte { a[0] = cmstest.Attr(cmstest.OIDContentType, cmstest.OID(cmstest.OIDData)) return a })}, {"no message-digest", tok(cmstest.TokenOptions{NoMessageDigest: true}, tsa)}, {"no signing certificate", withAttrs(func(a [][]byte) [][]byte { return a[:2] })}, {"two signing-certificate attributes", withAttrs(func(a [][]byte) [][]byte { return append(a, a[2]) })}, {"a signing-certificate of two values", withAttrs(func(a [][]byte) [][]byte { a[2] = cmstest.Seq(cmstest.OID(cmstest.OIDSigCertV1), cmstest.Set(0x31, cmstest.Children(cmstest.Children(a[2])[1])[0], cmstest.Seq(cmstest.Seq(cmstest.Seq(cmstest.Octets(make([]byte, 21))))))) return a })}, {"two signing-certificate-v2 and a v1", withAttrs(func(a [][]byte) [][]byte { return append(a, ess2(tsa.Cert.Raw), ess2(tsa.Cert.Raw)) })}, {"a signing-certificate of another TSA", withAttrs(func(a [][]byte) [][]byte { a[2] = ess1(other.Cert.Raw); return a })}, {"a signing-certificate-v2 of another", tok(cmstest.TokenOptions{SigCertV2: true, CMS: cmstest.Options{ESSCert: other.Cert.Raw}}, tsa)}, {"no certificate of the authority", tok(cmstest.TokenOptions{CMS: cmstest.Options{OmitCert: true}}, tsa)}, {"crls out of order", cmstest.Edit(twoCRLs, func(b []byte) []byte { k := slices.Clone(cmstest.Children(b)) slices.Reverse(k) return cmstest.TLV(0xa1, k...) }, path(cmstest.SignedDataPath, 4)...)}, {"a signing-certificate whose hash is of 19 bytes", withAttrs(func(a [][]byte) [][]byte { a[2] = cmstest.Attr(cmstest.OIDSigCertV1, cmstest.Seq(cmstest.Seq(cmstest.Seq(cmstest.Octets(sha1Sum(tsa.Cert.Raw)[:19]))))) return a })}, {"a signing-certificate whose ESSCertID holds a hashAlgorithm", withAttrs(func(a [][]byte) [][]byte { a[2] = cmstest.Attr(cmstest.OIDSigCertV1, cmstest.Seq(cmstest.Seq(cmstest.Seq(cmstest.HashAlg(crypto.SHA1), cmstest.Octets(sha1Sum(tsa.Cert.Raw)))))) return a })}, {"a CRL", tok(cmstest.TokenOptions{CRL: cmstest.Seq(cmstest.Int(1))}, tsa)}, {"two CRLs", twoCRLs}, {"one CRL twice", tok(cmstest.TokenOptions{CMS: cmstest.Options{CRLs: [][]byte{cmstest.Seq(cmstest.Int(1)), cmstest.Seq(cmstest.Int(1))}}}, tsa)}, {"crls of any element", tok(cmstest.TokenOptions{CMS: cmstest.Options{CRLs: [][]byte{cmstest.Int(1), cmstest.TLV(0xa1, cmstest.Null())}}}, tsa)}, {"the certificate twice", tok(cmstest.TokenOptions{TSATwice: true}, tsa)}, {"signing-certificate-v2", tok(cmstest.TokenOptions{SigCertV2: true}, tsa)}, {"v2 beside a wrong v1", tok(cmstest.TokenOptions{SigCertV2: true, CMS: cmstest.Options{ExtraAttrs: [][]byte{ess1(other.Cert.Raw)}}}, tsa)}, {"a sid by keyIdentifier", tok(cmstest.TokenOptions{CMS: cmstest.Options{SKI: true}}, tsa)}, {"an accuracy of 1.5 s", tok(cmstest.TokenOptions{Accuracy: 1500 * time.Millisecond}, tsa)}, {"an accuracy of 2 s and 3 us", tok(cmstest.TokenOptions{Accuracy: 2*time.Second + 3*time.Microsecond}, tsa)}, {"a token with OCSP in its crls", tok(cmstest.TokenOptions{CMS: cmstest.Options{OCSP: cmstest.Seq(cmstest.Int(0))}}, tsa)}, } { add(c.name, c.b) } var tokCases []any for _, c := range toks { tokCases = append(tokCases, &TokCase{Name: c.name, DER: hx(c.b), TokOut: tokOutcome(c.b, subject, false), raw: c.b}) } // The TSTInfo field by field (hostile_test.go), each in a token signed // again. goodTime := cmstest.GeneralizedTime("20260930120000Z") tsaName := cmstest.TLV(0xa0, cmstest.TLV(0xa4, cmstest.Seq())) exts := cmstest.TLV(0xa1, cmstest.Seq(cmstest.OID(asn1.ObjectIdentifier{1, 2, 3}), cmstest.Octets(nil))) ms := func(c ...byte) []byte { return cmstest.TLV(0x80, c) } us := func(c ...byte) []byte { return cmstest.TLV(0x81, c) } fields := tstFields(subject, goodTime) with := func(i int, v []byte) []byte { f := append([][]byte(nil), fields...) f[i] = v return cmstest.Seq(f...) } h := sha256.Sum256(subject) huge := []byte{1, 0, 0, 0, 0, 0, 0, 0, 5} // 2^64 + 5, 5 in 64 bits var infos []named for _, c := range []named{ {"the baseline", tstInfo(subject, goodTime)}, {"a fraction and an accuracy", tstInfo(subject, cmstest.GeneralizedTime("20260930120000.5Z"), cmstest.Seq(cmstest.Int(2), cmstest.TLV(0x80, []byte{5})))}, {"an empty accuracy", tstInfo(subject, goodTime, cmstest.Seq())}, {"only millis", tstInfo(subject, goodTime, cmstest.Seq(ms(5)))}, {"only micros", tstInfo(subject, goodTime, cmstest.Seq(us(7)))}, {"millis and micros of 999", tstInfo(subject, goodTime, cmstest.Seq(ms(0x03, 0xe7), us(0x03, 0xe7)))}, {"millis of 1 and micros of 1", tstInfo(subject, goodTime, cmstest.Seq(ms(1), us(1)))}, {"millis of 128", tstInfo(subject, goodTime, cmstest.Seq(ms(0, 0x80)))}, {"millis of 300", tstInfo(subject, goodTime, cmstest.Seq(ms(0x01, 0x2c)))}, {"micros of 255", tstInfo(subject, goodTime, cmstest.Seq(us(0, 0xff)))}, {"seconds, millis and micros", tstInfo(subject, goodTime, cmstest.Seq(cmstest.Int(1), ms(5), us(7)))}, {"0 seconds", tstInfo(subject, goodTime, cmstest.Seq(cmstest.Int(0)))}, {"2^31 - 1 seconds", tstInfo(subject, goodTime, cmstest.Seq(cmstest.Int(1<<31-1)))}, {"2^31 - 1 seconds, 999 millis and 999 micros", tstInfo(subject, goodTime, cmstest.Seq(cmstest.Int(1<<31-1), ms(0x03, 0xe7), us(0x03, 0xe7)))}, {"2^24 seconds", tstInfo(subject, goodTime, cmstest.Seq(cmstest.Int(1<<24)))}, {"ordering TRUE", tstInfo(subject, goodTime, cmstest.Bool(true))}, {"an accuracy and ordering TRUE", tstInfo(subject, goodTime, cmstest.Seq(cmstest.Int(1)), cmstest.Bool(true))}, {"a nonce", tstInfo(subject, goodTime, cmstest.Int(99))}, {"ordering TRUE and a nonce", tstInfo(subject, goodTime, cmstest.Bool(true), cmstest.Int(99))}, {"a tsa", tstInfo(subject, goodTime, tsaName)}, {"extensions", tstInfo(subject, goodTime, exts)}, {"every field", tstInfo(subject, goodTime, cmstest.Seq(cmstest.Int(3)), cmstest.Bool(true), cmstest.Int(99), tsaName, exts)}, {"a negative accuracy", tstInfo(subject, goodTime, cmstest.Seq(cmstest.Int(-31536000)))}, {"an accuracy of -1", tstInfo(subject, goodTime, cmstest.Seq(cmstest.Int(-1)))}, {"an accuracy that overflows", tstInfo(subject, goodTime, cmstest.Seq(cmstest.Int(9223372037)))}, {"an accuracy of 2^31 seconds", tstInfo(subject, goodTime, cmstest.Seq(cmstest.Int(1<<31)))}, {"an accuracy of 2^32 - 1 seconds", tstInfo(subject, goodTime, cmstest.Seq(cmstest.Int(1<<32-1)))}, {"an accuracy of 2^64 + 5 seconds", tstInfo(subject, goodTime, cmstest.Seq(cmstest.IntBytes(huge)))}, {"millis of 0", tstInfo(subject, goodTime, cmstest.Seq(ms(0)))}, {"millis of 1000", tstInfo(subject, goodTime, cmstest.Seq(ms(0x03, 0xe8)))}, {"millis of 5000", tstInfo(subject, goodTime, cmstest.Seq(ms(0x13, 0x88)))}, {"millis of -1", tstInfo(subject, goodTime, cmstest.Seq(ms(0xff)))}, {"millis of 5 in two bytes", tstInfo(subject, goodTime, cmstest.Seq(ms(0, 5)))}, {"millis of 2^64 + 5", tstInfo(subject, goodTime, cmstest.Seq(ms(huge...)))}, {"millis of 999 in three bytes", tstInfo(subject, goodTime, cmstest.Seq(ms(0, 0x03, 0xe7)))}, {"empty millis", tstInfo(subject, goodTime, cmstest.Seq(ms()))}, {"millis constructed", tstInfo(subject, goodTime, cmstest.Seq(cmstest.TLV(0xa0, cmstest.Int(5))))}, {"micros of 0", tstInfo(subject, goodTime, cmstest.Seq(us(0)))}, {"micros of 1000", tstInfo(subject, goodTime, cmstest.Seq(us(0x03, 0xe8)))}, {"micros before millis", tstInfo(subject, goodTime, cmstest.Seq(us(1), ms(1)))}, {"millis twice", tstInfo(subject, goodTime, cmstest.Seq(ms(1), ms(2)))}, {"seconds after millis", tstInfo(subject, goodTime, cmstest.Seq(ms(1), cmstest.Int(1)))}, {"a field after the micros", tstInfo(subject, goodTime, cmstest.Seq(us(1), cmstest.TLV(0x82, []byte{1})))}, {"millis as an INTEGER", tstInfo(subject, goodTime, cmstest.Seq(cmstest.Int(1), cmstest.Int(5)))}, {"genTime with an offset", tstInfo(subject, cmstest.GeneralizedTime("20260930130000+0100"))}, {"genTime with a trailing zero", tstInfo(subject, cmstest.GeneralizedTime("20260930120000.50Z"))}, {"genTime with an empty fraction", tstInfo(subject, cmstest.GeneralizedTime("20260930120000.Z"))}, {"genTime with a comma", tstInfo(subject, cmstest.GeneralizedTime("20260930120000,5Z"))}, {"genTime without seconds", tstInfo(subject, cmstest.GeneralizedTime("202609301200Z"))}, {"genTime as a UTCTime", tstInfo(subject, cmstest.UTCTime("260930120000Z"))}, {"genTime of 30 February", tstInfo(subject, cmstest.GeneralizedTime("20260230120000Z"))}, {"genTime of the year 0", tstInfo(subject, cmstest.GeneralizedTime("00000101000000Z"))}, {"ordering FALSE written", tstInfo(subject, goodTime, cmstest.Bool(false))}, {"ordering of two bytes", tstInfo(subject, goodTime, cmstest.TLV(0x01, []byte{0xff, 0xff}))}, {"two accuracies", tstInfo(subject, goodTime, cmstest.Seq(cmstest.Int(1)), cmstest.Seq(cmstest.Int(1)))}, {"two orderings", tstInfo(subject, goodTime, cmstest.Bool(true), cmstest.Bool(true))}, {"an extra INTEGER at the end", tstInfo(subject, goodTime, cmstest.Int(7), cmstest.Int(8), cmstest.Int(9))}, {"two tsa fields", tstInfo(subject, goodTime, tsaName, tsaName)}, {"two extensions", tstInfo(subject, goodTime, exts, exts)}, {"a field after the extensions", tstInfo(subject, goodTime, exts, cmstest.Int(1))}, {"a field out of order", tstInfo(subject, goodTime, cmstest.Int(7), cmstest.Seq(cmstest.Int(1)))}, {"extensions before the tsa", tstInfo(subject, goodTime, exts, tsaName)}, {"a reserved tag in the extensions", tstInfo(subject, goodTime, cmstest.TLV(0xa1, cmstest.TLV(0x0e, []byte{0x41})))}, {"an unused-bits BIT STRING inside", tstInfo(subject, goodTime, cmstest.TLV(0xa1, cmstest.TLV(0x03, []byte{7, 0xff})))}, {"version 2", cmstest.Seq(cmstest.Int(2))}, {"version 2, the fields complete", with(0, cmstest.Int(2))}, {"version 0", with(0, cmstest.Int(0))}, {"version 2^64 + 1", with(0, cmstest.IntBytes([]byte{1, 0, 0, 0, 0, 0, 0, 0, 1}))}, {"version 1 in five bytes", with(0, cmstest.IntBytes([]byte{0, 0, 0, 0, 1}))}, {"the version as an ENUMERATED", with(0, cmstest.TLV(0x0a, []byte{1}))}, {"the policy as an INTEGER", with(1, cmstest.Int(1))}, {"the serialNumber as OCTETS", with(3, cmstest.Octets([]byte{42}))}, {"the messageImprint as a SET", with(2, cmstest.TLV(0x31, cmstest.Seq(cmstest.OID(cmstest.OIDSHA256)), cmstest.Octets(h[:])))}, {"a messageImprint of three fields", with(2, cmstest.Seq(cmstest.Seq(cmstest.OID(cmstest.OIDSHA256)), cmstest.Octets(h[:]), cmstest.Null()))}, {"a messageImprint of one field", with(2, cmstest.Seq(cmstest.Seq(cmstest.OID(cmstest.OIDSHA256))))}, {"a messageImprint algorithm as a SET", with(2, cmstest.Seq(cmstest.TLV(0x31, cmstest.OID(cmstest.OIDSHA256)), cmstest.Octets(h[:])))}, {"a hashedMessage as a BIT STRING", with(2, cmstest.Seq(cmstest.Seq(cmstest.OID(cmstest.OIDSHA256)), cmstest.BitString(h[:])))}, {"four fields", cmstest.Seq(fields[:4]...)}, {"a SET", cmstest.TLV(0x31, fields...)}, {"not a SEQUENCE", cmstest.Int(1)}, {"not DER", append(tstInfo(subject, goodTime), 0)}, {"nothing", nil}, } { infos = append(infos, c) } var infoCases []any for _, c := range infos { b := cmstest.TokenRaw(c.b, tsa) infoCases = append(infoCases, &TokCase{Name: c.name, DER: hx(b), TokOut: tokOutcome(b, subject, false), raw: b}) } f.sections = []section{{"tokens", tokCases}, {"tstinfo", infoCases}} f.compress() return f } // --------------------------------------------------------------------------- // cms_mutations.json func (g *gen) mutationFile() *vfile { f := &vfile{name: "mutations", description: "Signatures and tokens edited node by node from the bases, as cmstest.Edit does it, with the SHA-256 of the result: path is the index of a child at each level from the base; op is retag (to the identifier octet arg), remove, dup (the node twice), null (NULL in its place), append (a NULL after its children), reverse (its children), flip (bit bit of the byte arg of its content), trunc (its last byte), extend (the byte arg after its content) or empty (its content); raw flips bit bit of the byte arg of the base itself, its tags and lengths included. The outcome of a signature is that of cms_signatures.json, over the message and the other message of this file, and that of a token, of cms_tokens.json, over its subject.", every: map[string]int{"signature_bases": 1, "signatures": 25, "token_bases": 1, "tokens": 20}, head: [][2]any{{"message", hx(msg)}, {"other_message", hx(otherMsg)}, {"subject", hx(sealSubject)}}} ana, luis, tsa := g.ana, g.luis, g.tsa tok := func(sig []byte) []byte { return cmstest.Token(sig, now, cmstest.TokenOptions{Accuracy: 1500 * time.Millisecond, After: [][]byte{cmstest.Bool(true), cmstest.Int(7)}}, tsa) } sigBases := []named{ {"sealed", cmstest.Signature(msg, cmstest.Options{Token: tok, OCSP: cmstest.Seq(cmstest.Int(0)), SigCertV1: true}, ana)}, {"co-signature", cmstest.Signature(msg, cmstest.Options{}, ana, luis)}, {"pss by keyIdentifier", cmstest.Signature(msg, cmstest.Options{PSS: true, SKI: true, Hash: crypto.SHA384}, luis)}, } tsaName := cmstest.TLV(0xa0, cmstest.TLV(0xa4, cmstest.Seq())) exts := cmstest.TLV(0xa1, cmstest.Seq(cmstest.OID(asn1.ObjectIdentifier{1, 2, 3}), cmstest.Octets(nil))) tokBases := []named{ {"ecdsa", cmstest.Token(sealSubject, now, cmstest.TokenOptions{Accuracy: 2*time.Second + 5*time.Millisecond, After: [][]byte{cmstest.Bool(true), cmstest.Int(99), tsaName, exts}}, tsa)}, {"rsa", cmstest.Token(sealSubject, now, cmstest.TokenOptions{SigCertV2: true, TSATwice: true, CRL: cmstest.Seq(cmstest.Int(1)), CMS: cmstest.Options{PSS: true}}, luis)}, } var sb, se, tb, te []any for _, b := range sigBases { sb = append(sb, &SigCase{Name: b.name, DER: hx(b.b), SigOut: sigOutcome(b.b, msg, false)}) for _, e := range edits(g.r, b.name, b.b, 360) { se = append(se, &SigCase{Edit: e, SigOut: sigOutcome(apply(b.b, e), msg, false)}) } } for _, b := range tokBases { tb = append(tb, &TokCase{Name: b.name, DER: hx(b.b), TokOut: tokOutcome(b.b, sealSubject, false)}) for _, e := range edits(g.r, b.name, b.b, 320) { te = append(te, &TokCase{Edit: e, TokOut: tokOutcome(apply(b.b, e), sealSubject, false)}) } } f.sections = []section{{"signature_bases", sb}, {"signatures", se}, {"token_bases", tb}, {"tokens", te}} return f } // --------------------------------------------------------------------------- // cms_corpus.json type corpusSig struct { Names []string `json:"names"` DER any `json:"der"` SHA256 string `json:"sha256,omitempty"` Message string `json:"message"` SigOut raw []byte } type corpusSeal struct { Names []string `json:"names"` DER any `json:"der"` SHA256 string `json:"sha256,omitempty"` Subject string `json:"subject"` TokOut raw []byte } type fixtureCase struct { Fixture string `json:"fixture"` Kind string `json:"kind"` Span Span `json:"span"` SHA256 string `json:"sha256"` Message string `json:"message,omitempty"` Subject string `json:"subject,omitempty"` Signature *SigOut `json:"signature,omitempty"` Seal *TokOut `json:"seal,omitempty"` } // cborKeys reads the definite map of unsigned keys of SECURITY_CBOR and // returns the content of each value that is a byte string, nil when it is // not such a map. func cborKeys(b []byte) map[uint64][]byte { pos := 0 head := func() (major byte, arg uint64, ok bool) { if pos >= len(b) { return 0, 0, false } major, info := b[pos]>>5, b[pos]&0x1f pos++ switch { case info < 24: return major, uint64(info), true case info <= 27: n := 1 << (info - 24) if pos+n > len(b) { return 0, 0, false } for _, c := range b[pos : pos+n] { arg = arg<<8 | uint64(c) } pos += n return major, arg, true } return 0, 0, false } major, n, ok := head() if !ok || major != 5 { return nil } out := map[uint64][]byte{} for i := uint64(0); i < n; i++ { km, k, ok := head() if !ok || km != 0 { return nil } vm, v, ok := head() if !ok { return nil } switch vm { case 0: case 2, 3: if uint64(len(b)-pos) < v { return nil } if vm == 2 { out[k] = b[pos : pos+int(v)] } pos += int(v) default: return nil } } return out } func (g *gen) corpusFile() *vfile { f := &vfile{name: "corpus", description: "Every CMS signature of an author-signature of alg 2, and every token of a seal, of testdata/vectors/security_cms.json and of the fixtures format3_signed_cms and format3_sealed of the reference, each once with the names of its cases, read as capsule reads them for its verdicts: a signature with cms.ParseSignature, each signer checked over the AUTHOR_MESSAGE of its case (capsule.AuthorMessage), with Holder and IssuerName of its certificate, and its token, when it has one, read with cms.ParseToken and checked over the signature value, with Holder of the authority and ValidAt of the certificate of the signer at genTime; a token with cms.ParseToken and checked over the SEAL_SUBJECT of its case (capsule.SealSubject). A fixture gives the span of the signature or the token inside its security_cbor.", every: map[string]int{"signatures": 12, "seals": 6}, head: [][2]any{{"other_message", hx(otherMsg)}}} raw, err := os.ReadFile(filepath.Join("..", "testdata", "vectors", "security_cms.json")) if err != nil { panic(err) } var file struct { Cases []struct { Name string `json:"name"` Area string `json:"security_cbor"` Context struct { ControlCommit string `json:"control_commit"` HeadDigest string `json:"head_digest"` } `json:"context"` } `json:"cases"` } if err := json.Unmarshal(raw, &file); err != nil { panic(err) } type sigKey struct{ der, message string } var sigs []*corpusSig sigAt := map[sigKey]*corpusSig{} var seals []*corpusSeal sealAt := map[sigKey]*corpusSeal{} for _, c := range file.Cases { area := mustHex(c.Area) var cc, hd [32]byte copy(cc[:], mustHex(c.Context.ControlCommit)) copy(hd[:], mustHex(c.Context.HeadDigest)) keys := cborKeys(area) if content, ok := keys[2]; ok { if alg, signers, value, err := capsule.DecodeAuthorSignature(content); err == nil && alg == capsule.AlgCMS { message := capsule.AuthorMessage(cc, hd, capsule.SignersDigest(capsule.AlgCMS, signers)) k := sigKey{hx(value), hx(message)} if s := sigAt[k]; s != nil { s.Names = append(s.Names, c.Name) } else { s := &corpusSig{Names: []string{c.Name}, DER: k.der, Message: k.message, SigOut: sigOutcome(value, message, true), raw: value} sigAt[k] = s sigs = append(sigs, s) } } } if _, ok := keys[3]; ok { if st, tokenBytes, err := capsule.SecurityKey3(area); err == nil && st == capsule.SealTypeRFC3161 { var sigPart []byte if content, ok := keys[2]; ok { sigPart = capsule.SigPart(content) } else { sigPart = capsule.SigPart(nil) } subject := capsule.SealSubject(cc, hd, sigPart) k := sigKey{hx(tokenBytes), hx(subject[:])} if s := sealAt[k]; s != nil { s.Names = append(s.Names, c.Name) } else { s := &corpusSeal{Names: []string{c.Name}, DER: k.der, Subject: k.message, TokOut: tokOutcome(tokenBytes, subject[:], true), raw: tokenBytes} sealAt[k] = s seals = append(seals, s) } } } } var fixtures []any for _, name := range []string{"format3_signed_cms", "format3_sealed"} { raw, err := os.ReadFile(filepath.Join("..", "testdata", "fixtures", name+".json")) if err != nil { panic(err) } var rec struct { Area string `json:"security_cbor"` Signature struct { Message string `json:"author_message"` } `json:"signature"` Seal struct { Subject string `json:"seal_subject"` Token string `json:"token"` } `json:"seal"` } if err := json.Unmarshal(raw, &rec); err != nil { panic(err) } area := mustHex(rec.Area) keys := cborKeys(area) if name == "format3_signed_cms" { _, _, value, err := capsule.DecodeAuthorSignature(keys[2]) if err != nil { panic(err) } message := []byte(rec.Signature.Message) if bytes.Count(area, value) != 1 { panic("the signature is not once in the area") } out := sigOutcome(value, message, true) sum := sha256.Sum256(value) fixtures = append(fixtures, fixtureCase{Fixture: name, Kind: "signature", Span: Span{bytes.Index(area, value), len(value)}, SHA256: hx(sum[:]), Message: hx(message), Signature: &out}) } else { _, tokenBytes, err := capsule.SecurityKey3(area) if err != nil || !bytes.Equal(tokenBytes, mustHex(rec.Seal.Token)) { panic("the token of " + name) } subject := mustHex(rec.Seal.Subject) if bytes.Count(area, tokenBytes) != 1 { panic("the token is not once in the area") } out := tokOutcome(tokenBytes, subject, true) sum := sha256.Sum256(tokenBytes) fixtures = append(fixtures, fixtureCase{Fixture: name, Kind: "seal", Span: Span{bytes.Index(area, tokenBytes), len(tokenBytes)}, SHA256: hx(sum[:]), Subject: hx(subject), Seal: &out}) } } var so, se []any for _, s := range sigs { so = append(so, s) } for _, s := range seals { se = append(se, s) } f.sections = []section{{"signatures", so}, {"seals", se}, {"fixtures", fixtures}} f.compress() return f }