package main import ( "bytes" "crypto" "crypto/elliptic" "crypto/sha256" "encoding/hex" "errors" "fmt" "math/big" "os" "path/filepath" "reflect" "slices" "strings" "time" "g.activething.com/go/DateKeys/authorkey" "g.activething.com/go/DateKeys/capsule" "g.activething.com/go/DateKeys/internal/cms/cmstest" "g.activething.com/go/DateKeys/internal/testkit" ) // frozenVectors writes testdata/vectors/security_cms.json and locator.json // when they are missing: their bytes hold the randomness of certificates, of // age and of tlock, so they are made once and kept, as the fixtures are. // Delete a file to make it again. func frozenVectors(dir string) error { for _, v := range []struct { name string gen func() (any, error) }{ {"security_cms.json", securityCMSVectors}, {"locator.json", locatorVectors}, } { path := filepath.Join(dir, v.name) if _, err := os.Stat(path); err == nil { continue } out, err := v.gen() if err != nil { return fmt.Errorf("%s: %w", v.name, err) } if err := testkit.WriteJSON(path, out); err != nil { return err } } return nil } var ( vecRound = time.Date(2030, 1, 1, 0, 0, 0, 0, time.UTC) vecSigned = time.Date(2026, 9, 30, 12, 0, 0, 0, time.UTC) ) // cmsVectorSpec labels security_cms.json, as every file of testdata, with // SpecVersion. Its verdicts are those of v0.16, with the profile of the // certificate of §29.10, the texts of §29.7 and the accuracy of §29.11. const cmsVectorSpec = testkit.SpecVersion func hex32(b [32]byte) string { return hex.EncodeToString(b[:]) } // The texts of the verdicts, copied from the table of spec v0.16 §29.7: the // lines of each case are checked against them, not against Verdicts.Lines. const ( textF0 = "Sin firma de autor." textF1 = "No se ha comprobado ninguna firma: trátala como no firmada." textF2 = "La firma no corresponde a este contenido." textF5 = "Faltan firmas o sellos que la propia cápsula exige: trátala como no firmada." textS1 = "Lleva un sello de tiempo que esta versión no sabe comprobar: aquí no prueba nada." textS2 = "El sello de tiempo es ilegible: no prueba nada." textS3 = "El sello no corresponde a este contenido." textS5 = "No acredita que se sellara antes de la fecha de apertura: " ) // reasonTexts are the reasons of S5 in the texts of §29.7 (v0.16). var reasonTexts = map[capsule.SealReason]string{ capsule.ReasonLate: "se selló después de esa fecha o demasiado cerca de ella", capsule.ReasonNoAccuracyBTSP: "el sello no dice la precisión que exige su política", capsule.ReasonNoAccuracy: "el sello no dice su precisión", } // resultTexts are the results of a signer in the lines of §29.7. var resultTexts = map[string]string{ "valid": "válida", "invalid": "inválida", "not verifiable": "no verificable", "without seal": "sin sello", "invalid seal": "con el sello inválido", "out of validity": "con el certificado fuera de validez", } // vsigner is a signer with the names of its certificate as §29.7 shows them: // the holder and the issuer, or their SHA-256 when they break its rules. type vsigner struct { cmstest.Signer holder, issuer string } // named is a signer whose holder and issuer are the commonName cn, as in // the self-signed certificates of cmstest.NewECDSA and cmstest.NewRSA. func named(s cmstest.Signer, cn string) vsigner { return vsigner{s, cn, cn} } func (s vsigner) hash() [32]byte { return sha256.Sum256(s.Cert.Raw) } // hashOfCert and hashOfIssuer are what §29.7 shows for a name that breaks its // rules: the SHA-256 of the certificate, or of the DER of the Name of the // issuer. func hashOfCert(s cmstest.Signer) string { return hex32(sha256.Sum256(s.Cert.Raw)) } func hashOfIssuer(s cmstest.Signer) string { return hex32(sha256.Sum256(s.Cert.RawIssuer)) } // want is the result that §29.10 gives a signer: for a valid one, the // authority of its seal, t, and whether its seal proves that it came before // round_time, or why not (§29.7, §29.11). type want struct { s vsigner result string tsa vsigner t time.Time before bool reason capsule.SealReason } // vcase is a case of security_cms.json with what spec v0.12 gives for it. type vcase struct { name string area []byte ctx *capsule.SecurityContext // nil: the common context sig, seal capsule.Verdict // signers are the required signers that have a SignerInfo, absent those // that have none, and foreign the signers that are not required. signers []want absent []vsigner foreign []want // sealTSA and sealTime are the authority and t of a valid seal (S4, S5), // sealReason the reason of S5, and authorKey the key of a valid signature // of alg 1 (F4). sealTSA vsigner sealTime time.Time sealReason capsule.SealReason authorKey string } // cmsGen builds the cases over a common context and checks each against // what the spec gives. type cmsGen struct { ctx *capsule.SecurityContext file testkit.CMSVectorFile errs []error ana, luis, otro, tsa vsigner } func (g *cmsGen) fail(err error) { if err != nil { g.errs = append(g.errs, err) } } func (g *cmsGen) must(b []byte, err error) []byte { g.fail(err) return b } // signersOf is SIGNERS for the required signers, canonical. func (g *cmsGen) signersOf(required ...vsigner) []byte { var hashes [][32]byte for _, s := range required { hashes = append(hashes, s.hash()) } return g.must(capsule.EncodeSigners(hashes)) } // messageOf is AUTHOR_MESSAGE for the SIGNERS list, in the common context. func (g *cmsGen) messageOf(list []byte) []byte { return capsule.AuthorMessage(g.ctx.ControlCommit, g.ctx.HeadDigest, capsule.SignersDigest(capsule.AlgCMS, list)) } // content is the content of key 2 of a signature of alg 2 with the SIGNERS // list and the CMS signature sig. func (g *cmsGen) content(list, sig []byte) []byte { return g.must(capsule.EncodeAuthorSignature(capsule.AlgCMS, list, sig)) } // signed is the content of key 2 of a signature by the signers, with the // options o, for the required signers. func (g *cmsGen) signed(required []vsigner, o cmstest.Options, signers ...vsigner) []byte { list := g.signersOf(required...) return g.content(list, cmstest.Signature(g.messageOf(list), o, plain(signers)...)) } func plain(ss []vsigner) []cmstest.Signer { out := make([]cmstest.Signer, len(ss)) for i, s := range ss { out[i] = s.Signer } return out } // area is SECURITY_CBOR with the contents of keys 2 and 3, nil when absent. func (g *cmsGen) area(key2, key3 []byte) []byte { return g.must(capsule.EncodeSecurityWith(key2, key3)) } // sealedBy is the option of a CAdES-T: tsa seals each signature at when. func sealedBy(tsa vsigner, when time.Time, o cmstest.TokenOptions) func([]byte) []byte { return func(sig []byte) []byte { return cmstest.Token(sig, when, o, tsa.Signer) } } // add evaluates the case, checks it against what the spec gives, and writes // its record. func (g *cmsGen) add(c vcase) { ctx := c.ctx if ctx == nil { ctx = g.ctx } v := capsule.EvaluateSecurityIn(c.area, ctx) rec := testkit.CMSVectorCase{ Name: c.name, SecurityCBOR: hex.EncodeToString(c.area), Context: testkit.CMSVectorContext{ControlCommit: hex32(ctx.ControlCommit), HeadDigest: hex32(ctx.HeadDigest), RoundTime: ctx.RoundTime.UTC().Format(time.RFC3339)}, Signature: string(v.Signature), Seal: string(v.Seal), Lines: v.Lines(), } if d := v.Detail; d != nil { rec.Signers, rec.Foreign = cmsSignerResults(d.Signers), cmsSignerResults(d.Foreign) if !d.SealTime.IsZero() { rec.SealHolder, rec.SealTime = d.SealHolder, d.SealTime.UTC().Format(time.RFC3339Nano) } rec.SealReason = string(d.SealReason) } if err := c.check(v, rec); err != nil { g.errs = append(g.errs, fmt.Errorf("%s: %w", c.name, err)) } for _, other := range g.file.Cases { if other.Name == c.name { g.errs = append(g.errs, fmt.Errorf("%s: two cases of that name", c.name)) } } g.file.Cases = append(g.file.Cases, rec) } // cmsSignerResults are the results of the signers as the record writes them, // t with its fraction when it has one. func cmsSignerResults(lines []capsule.SignerLine) []testkit.FixtureSignerResult { var out []testkit.FixtureSignerResult for _, l := range lines { r := testkit.FixtureSignerResult{Holder: l.Holder, Issuer: l.Issuer, Result: l.Result, Before: l.Before, SealReason: string(l.Reason)} if !l.SealTime.IsZero() { r.SealTime = l.SealTime.UTC().Format(time.RFC3339Nano) } out = append(out, r) } return out } func (w want) record() testkit.FixtureSignerResult { r := testkit.FixtureSignerResult{Holder: w.s.holder, Issuer: w.s.issuer, Result: w.result} if w.result == "valid" { r.SealTime, r.Before = w.t.UTC().Format(time.RFC3339Nano), w.before if !w.before { r.SealReason = string(w.reason) } } return r } // check compares what the reader gave with what the spec gives: the // verdicts, the result of each signer and the lines, written from the texts // of §29.7. func (c vcase) check(v capsule.Verdicts, rec testkit.CMSVectorCase) error { if v.Signature != c.sig || v.Seal != c.seal { return fmt.Errorf("verdicts %s and %s, want %s and %s", v.Signature, v.Seal, c.sig, c.seal) } // The required signers in the order of SIGNERS: of their hashes, in // ascending order of bytes. type req struct { h [32]byte w *want s vsigner } var reqs []req for i := range c.signers { reqs = append(reqs, req{c.signers[i].s.hash(), &c.signers[i], c.signers[i].s}) } for _, s := range c.absent { reqs = append(reqs, req{s.hash(), nil, s}) } slices.SortFunc(reqs, func(a, b req) int { return bytes.Compare(a.h[:], b.h[:]) }) var signers, foreign []testkit.FixtureSignerResult for _, r := range reqs { if r.w == nil { signers = append(signers, testkit.FixtureSignerResult{Holder: hex32(r.h), Result: "absent"}) } else { signers = append(signers, r.w.record()) } } for _, w := range c.foreign { foreign = append(foreign, w.record()) } if !reflect.DeepEqual(signers, rec.Signers) || !reflect.DeepEqual(foreign, rec.Foreign) { return fmt.Errorf("signers %+v and foreign %+v, want %+v and %+v", rec.Signers, rec.Foreign, signers, foreign) } var sealHolder, sealTime string if c.seal == capsule.VerdictSealed || c.seal == capsule.VerdictSealedLate { sealHolder, sealTime = c.sealTSA.holder, c.sealTime.UTC().Format(time.RFC3339Nano) } if rec.SealHolder != sealHolder || rec.SealTime != sealTime { return fmt.Errorf("the seal of %q at %s, want %q at %s", rec.SealHolder, rec.SealTime, sealHolder, sealTime) } if want := c.sealReason; c.seal != capsule.VerdictSealedLate && want != capsule.ReasonNone || rec.SealReason != string(want) { return fmt.Errorf("the reason of the seal %q, want %q", rec.SealReason, want) } // The lines: the signature, the foreign signers apart, and the seal. q := func(s string) string { return "«" + s + "»" } at := func(t time.Time) string { return t.UTC().Format(time.RFC3339Nano) } var lines []string switch c.sig { case capsule.VerdictNoSignature: lines = append(lines, textF0) case capsule.VerdictSignatureUnchecked: lines = append(lines, textF1) case capsule.VerdictSignatureInvalid: lines = append(lines, textF2) case capsule.VerdictSignedIncomplete: lines = append(lines, textF5) case capsule.VerdictSignedOther: lines = append(lines, "Firmado con la clave "+c.authorKey+". No prueba quién la tiene.") case capsule.VerdictSignedComplete: var names, each []string before := false for _, r := range reqs { names = append(names, q(r.s.holder)) when := "sin acreditar que fuera antes de la fecha de apertura: " + reasonTexts[r.w.reason] if r.w.before { when, before = "antes de la fecha de apertura", true } each = append(each, " "+q(r.s.holder)+" (emisor según su certificado: "+q(r.s.issuer)+"), sellado por "+q(r.w.tsa.holder)+" el "+at(r.w.t)+", "+when+".") } lines = append(lines, "Firmado con un certificado a nombre de "+strings.Join(names, ", ")+". DateKeys no comprueba quién lo emitió: para eso, exporta la firma a un validador oficial.") lines = append(lines, each...) if before { lines = append(lines, " DateKeys no comprueba quién emitió los sellos.") } default: return fmt.Errorf("no lines for %s", c.sig) } for _, w := range c.foreign { lines = append(lines, " Otro firmante, "+q(w.s.holder)+": "+resultTexts[w.result]+". No cuenta.") } switch c.seal { case capsule.VerdictNoSeal: case capsule.VerdictSealUnsupported: lines = append(lines, textS1) case capsule.VerdictSealUnreadable: lines = append(lines, textS2) case capsule.VerdictSealInvalid: lines = append(lines, textS3) case capsule.VerdictSealedLate: lines = append(lines, textS5+reasonTexts[c.sealReason]+".") case capsule.VerdictSealed: lines = append(lines, "Según un sello a nombre de "+q(c.sealTSA.holder)+", existía el "+at(c.sealTime)+", antes de que la cápsula pudiera abrirse. DateKeys no comprueba quién emitió el sello.") default: return fmt.Errorf("no line for %s", c.seal) } if !slices.Equal(lines, rec.Lines) { return fmt.Errorf("lines %q, want %q", rec.Lines, lines) } return nil } // securityCMSVectors makes the cases of security_cms.json: each one with the // verdicts, the results and the lines that spec v0.12 gives, §29.7, §29.10 // and §29.11, and the list of §64 for the signature, the seal and the names. // The generator fails when the reader gives anything else. func securityCMSVectors() (any, error) { g := &cmsGen{ctx: &capsule.SecurityContext{ControlCommit: sha256.Sum256([]byte("control")), HeadDigest: sha256.Sum256([]byte("head")), RoundTime: vecRound}} g.file = testkit.CMSVectorFile{ Spec: cmsVectorSpec, Description: "SECURITY_CBOR with an author signature of alg 2 or a time seal of seal_type 2, the context of its capsule, " + "and the verdicts, the result of each signer and the lines of spec v0.12 29.7, 29.10 and 29.11. " + "Certificates and tokens are made once with test keys and the file is frozen. See testdata/README.md.", } g.signatureCases() g.signersCases() g.formCases() g.algorithmCases() g.nameCases() g.sealCases() if err := errors.Join(g.errs...); err != nil { return nil, err } return g.file, nil } // people makes the signers of the cases once. The certificates of // cmstest.NewECDSA and NewRSA are self-signed, so the issuer of each is its // holder. func (g *cmsGen) people() (ana, luis, otro, tsa vsigner) { if g.ana.Key == nil { g.ana = named(cmstest.NewECDSA("Ana López", elliptic.P256(), certFrom, certTo), "Ana López") g.luis = named(cmstest.NewRSA("Luis Gómez", 2048, certFrom, certTo), "Luis Gómez") g.otro = named(cmstest.NewECDSA("Otro", elliptic.P384(), certFrom, certTo), "Otro") g.tsa = named(cmstest.NewECDSA("Autoridad de Sellado de prueba", elliptic.P256(), certFrom, certTo), "Autoridad de Sellado de prueba") } return g.ana, g.luis, g.otro, g.tsa } // valid is the result of a required signer that verifies, sealed by tsa at // vecSigned with an accuracy of a second: before round_time. func valid(s, tsa vsigner) want { return want{s: s, result: "valid", tsa: tsa, t: vecSigned, before: true} } func result(s vsigner, r string) want { return want{s: s, result: r} } // signatureCases are the verdicts of alg 2 (spec §29.10, "Verificación"): // F6 when every required signer is valid and sealed, F5 when one is absent, // not verifiable, without a seal, with an invalid seal or out of validity, // or when key 3 exists, and F2 when one is invalid, before F5. func (g *cmsGen) signatureCases() { ana, luis, otro, tsa := g.people() both := []vsigner{ana, luis} sealed := cmstest.Options{Token: sealedBy(tsa, vecSigned, cmstest.TokenOptions{Accuracy: time.Second})} only := func(s vsigner) []vsigner { return []vsigner{s} } g.add(vcase{name: "alg 2: two signers, each sealed before the round time: F6", area: g.area(g.signed(both, sealed, ana, luis), nil), sig: capsule.VerdictSignedComplete, seal: capsule.VerdictNoSeal, signers: []want{valid(ana, tsa), valid(luis, tsa)}}) late := cmstest.Options{Token: sealedBy(tsa, vecRound.Add(time.Hour), cmstest.TokenOptions{Accuracy: time.Second})} g.add(vcase{name: "alg 2: sealed after the round time: F6, not before the opening date", area: g.area(g.signed(only(ana), late, ana), nil), sig: capsule.VerdictSignedComplete, seal: capsule.VerdictNoSeal, signers: []want{{s: ana, result: "valid", tsa: tsa, t: vecRound.Add(time.Hour), reason: capsule.ReasonLate}}}) // t + accuracy equal to round_time is not before it (§29.7). edge := cmstest.Options{Token: sealedBy(tsa, vecRound.Add(-time.Second), cmstest.TokenOptions{Accuracy: time.Second})} g.add(vcase{name: "alg 2: t plus the accuracy of the seal equals the round time: F6, not before the opening date", area: g.area(g.signed(only(ana), edge, ana), nil), sig: capsule.VerdictSignedComplete, seal: capsule.VerdictNoSeal, signers: []want{{s: ana, result: "valid", tsa: tsa, t: vecRound.Add(-time.Second), reason: capsule.ReasonLate}}}) // Spec v0.16, §29.7: a seal without accuracy does not prove that it came // before the opening date, and its line gives the reason. bare := cmstest.Options{Token: sealedBy(tsa, vecSigned, cmstest.TokenOptions{})} g.add(vcase{name: "alg 2: a seal without accuracy: F6, not proven before the opening date", area: g.area(g.signed(only(ana), bare, ana), nil), sig: capsule.VerdictSignedComplete, seal: capsule.VerdictNoSeal, signers: []want{{s: ana, result: "valid", tsa: tsa, t: vecSigned, reason: capsule.ReasonNoAccuracy}}}) btsp := cmstest.Options{Token: sealedBy(tsa, vecSigned, cmstest.TokenOptions{Policy: cmstest.BTSPPolicy})} g.add(vcase{name: "alg 2: a seal of the BTSP policy without accuracy: F6, not proven before the opening date, by its policy", area: g.area(g.signed(only(ana), btsp, ana), nil), sig: capsule.VerdictSignedComplete, seal: capsule.VerdictNoSeal, signers: []want{{s: ana, result: "valid", tsa: tsa, t: vecSigned, reason: capsule.ReasonNoAccuracyBTSP}}}) g.add(vcase{name: "alg 2: a signer who is not required shows apart and does not count: F6", area: g.area(g.signed(only(ana), sealed, ana, otro), nil), sig: capsule.VerdictSignedComplete, seal: capsule.VerdictNoSeal, signers: []want{valid(ana, tsa)}, foreign: []want{valid(otro, tsa)}}) g.add(vcase{name: "alg 2: a required signer is absent: F5", area: g.area(g.signed(both, sealed, ana), nil), sig: capsule.VerdictSignedIncomplete, seal: capsule.VerdictNoSeal, signers: []want{valid(ana, tsa)}, absent: []vsigner{luis}}) { // §64: the author withdrawn and the others intact; a signature // withdrawn and SIGNERS changed to hide it, which changes // AUTHOR_MESSAGE, so the one who stays does not verify. list := g.signersOf(both...) sig := cmstest.Signature(g.messageOf(list), sealed, ana.Signer, luis.Signer) g.add(vcase{name: "alg 2: the author withdrawn and the co-signer intact: F5", area: g.area(g.content(list, cmstest.Withdraw(sig, ana.Signer)), nil), sig: capsule.VerdictSignedIncomplete, seal: capsule.VerdictNoSeal, signers: []want{valid(luis, tsa)}, absent: []vsigner{ana}}) g.add(vcase{name: "alg 2: a signature withdrawn and SIGNERS changed to hide it: F2", area: g.area(g.content(g.signersOf(ana), cmstest.Withdraw(sig, luis.Signer)), nil), sig: capsule.VerdictSignatureInvalid, seal: capsule.VerdictNoSeal, signers: []want{result(ana, "invalid")}}) g.add(vcase{name: "alg 2: the CAdES-T of a signer withdrawn: F5, without seal", area: g.area(g.content(list, cmstest.WithoutTimeStamp(sig, luis.Signer)), nil), sig: capsule.VerdictSignedIncomplete, seal: capsule.VerdictNoSeal, signers: []want{valid(ana, tsa), result(luis, "without seal")}}) } g.add(vcase{name: "alg 2: no seal: F5", area: g.area(g.signed(only(ana), cmstest.Options{}, ana), nil), sig: capsule.VerdictSignedIncomplete, seal: capsule.VerdictNoSeal, signers: []want{result(ana, "without seal")}}) // Step 6: a seal that verifies, at a time when the certificate of the // signer is no longer valid, and the authority still is. expired := named(cmstest.NewECDSA("Ana caducada", elliptic.P256(), certFrom, time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)), "Ana caducada") g.add(vcase{name: "alg 2: the certificate of the signer out of validity, its authority valid: F5, out of validity", area: g.area(g.signed(only(expired), sealed, expired), nil), sig: capsule.VerdictSignedIncomplete, seal: capsule.VerdictNoSeal, signers: []want{result(expired, "out of validity")}}) // The validity is inclusive (RFC 5280 4.1.2.5): sealed at the last second. lastDay := named(cmstest.NewECDSA("Eva Martín", elliptic.P256(), certFrom, vecSigned), "Eva Martín") g.add(vcase{name: "alg 2: sealed at the last second of the validity of the certificate: F6", area: g.area(g.signed(only(lastDay), sealed, lastDay), nil), sig: capsule.VerdictSignedComplete, seal: capsule.VerdictNoSeal, signers: []want{valid(lastDay, tsa)}}) // Step 5: a token of the profile of §29.11 that gives S3, S2 or S1 is an // invalid seal. for _, tc := range []struct { name string o cmstest.Options }{ {"alg 2: a seal whose authority was not valid at its time: F5, invalid seal", cmstest.Options{Token: sealedBy(tsa, certFrom.AddDate(-1, 0, 0), cmstest.TokenOptions{})}}, {"alg 2: a seal over another signature value: F5, invalid seal", cmstest.Options{Token: func([]byte) []byte { return cmstest.Token([]byte("other"), vecSigned, cmstest.TokenOptions{}, tsa.Signer) }}}, {"alg 2: a seal of a TSTInfo of version 2: F5, invalid seal", cmstest.Options{Token: sealedBy(tsa, vecSigned, cmstest.TokenOptions{Version: 2})}}, {"alg 2: a seal by an authority with a key of 1024 bits: F5, invalid seal", cmstest.Options{Token: sealedBy(named(cmstest.NewRSA("TSA de 1024 bits", 1024, certFrom, certTo), "TSA de 1024 bits"), vecSigned, cmstest.TokenOptions{})}}, } { g.add(vcase{name: tc.name, area: g.area(g.signed(only(ana), tc.o, ana), nil), sig: capsule.VerdictSignedIncomplete, seal: capsule.VerdictNoSeal, signers: []want{result(ana, "invalid seal")}}) } // Within alg 2 the imprint takes any hash of the table (§29.11 step 2). g.add(vcase{name: "alg 2: a seal with an imprint of SHA-384: F6", area: g.area(g.signed(only(ana), cmstest.Options{Token: sealedBy(tsa, vecSigned, cmstest.TokenOptions{Hash: crypto.SHA384, Accuracy: time.Second})}, ana), nil), sig: capsule.VerdictSignedComplete, seal: capsule.VerdictNoSeal, signers: []want{valid(ana, tsa)}}) // Rule 1: the token is inside the ContentInfo, which is DER in all of it. g.add(vcase{name: "alg 2: a seal in BER makes the signature not DER: F1", area: g.area(g.signed(only(ana), cmstest.Options{Token: sealedBy(tsa, vecSigned, cmstest.TokenOptions{CMS: cmstest.Options{BER: true}})}, ana), nil), sig: capsule.VerdictSignatureUnchecked, seal: capsule.VerdictNoSeal}) // Key 3 beside alg 2: F5, and the seal is evaluated apart (§29.3, §29.7). key2 := g.signed(only(ana), sealed, ana) g.add(vcase{name: "alg 2: a key 3 of seal_type 4294967295 beside it: F5 and S1", area: g.area(key2, g.must(capsule.EncodeSeal(capsule.SealTypeTest, []byte{1}))), sig: capsule.VerdictSignedIncomplete, seal: capsule.VerdictSealUnsupported, signers: []want{valid(ana, tsa)}}) subject := capsule.SealSubject(g.ctx.ControlCommit, g.ctx.HeadDigest, capsule.SigPart(key2)) g.add(vcase{name: "alg 2: a valid seal of seal_type 2 in key 3 beside it: F5 and S4", area: g.area(key2, g.must(capsule.EncodeSeal(capsule.SealTypeRFC3161, cmstest.Token(subject[:], vecSigned, cmstest.TokenOptions{Accuracy: time.Second}, tsa.Signer)))), sig: capsule.VerdictSignedIncomplete, seal: capsule.VerdictSealed, signers: []want{valid(ana, tsa)}, sealTSA: tsa, sealTime: vecSigned}) other := *g.ctx other.HeadDigest[5] ^= 9 g.add(vcase{name: "alg 2: in the context of another head: F2", area: g.area(g.signed(only(ana), sealed, ana), nil), ctx: &other, sig: capsule.VerdictSignatureInvalid, seal: capsule.VerdictNoSeal, signers: []want{result(ana, "invalid")}}) g.add(vcase{name: "alg 2: a message-digest of another message: F2", area: g.area(g.signed(only(ana), cmstest.Options{Token: sealed.Token, Message: []byte("another message")}, ana), nil), sig: capsule.VerdictSignatureInvalid, seal: capsule.VerdictNoSeal, signers: []want{result(ana, "invalid")}}) // F2 before F5: one invalid and one absent. g.add(vcase{name: "alg 2: one signer invalid and another absent: F2", area: g.area(g.signed(both, cmstest.Options{Token: sealed.Token, Message: []byte("another message")}, ana), nil), sig: capsule.VerdictSignatureInvalid, seal: capsule.VerdictNoSeal, signers: []want{result(ana, "invalid")}, absent: []vsigner{luis}}) g.add(vcase{name: "alg 2: not a CMS: F1", area: g.area(g.content(g.signersOf(ana), []byte("not DER")), nil), sig: capsule.VerdictSignatureUnchecked, seal: capsule.VerdictNoSeal}) } // signersCases are SIGNERS that break its profile (spec §29.10, "Firmantes // exigidos"), each beside a CMS signature that is valid for the // AUTHOR_MESSAGE of those very SIGNERS: F1 comes from the rule, and a reader // that skipped it would give F5 or F6. func (g *cmsGen) signersCases() { ana, luis, _, tsa := g.people() sealed := cmstest.Options{Token: sealedBy(tsa, vecSigned, cmstest.TokenOptions{Accuracy: time.Second})} bstr := func(h []byte) []byte { return append([]byte{0x58, byte(len(h))}, h...) } array := func(n int, items ...[]byte) []byte { return append([]byte{0x80 | byte(n)}, bytes.Join(items, nil)...) } a, l := ana.hash(), luis.hash() if bytes.Compare(a[:], l[:]) > 0 { a, l = l, a } cosigned := func(name string, list []byte, signers ...vsigner) { sig := cmstest.Signature(g.messageOf(list), sealed, plain(signers)...) g.add(vcase{name: name, area: g.area(g.content(list, sig), nil), sig: capsule.VerdictSignatureUnchecked, seal: capsule.VerdictNoSeal}) } cosigned("alg 2: SIGNERS out of order, beside a valid CMS: F1", array(2, bstr(l[:]), bstr(a[:])), ana, luis) cosigned("alg 2: SIGNERS empty, beside a valid CMS: F1", array(0), ana) ah := ana.hash() cosigned("alg 2: SIGNERS with an element of 31 bytes, beside a valid CMS: F1", array(1, bstr(ah[:31])), ana) cosigned("alg 2: SIGNERS with a certificate twice, beside a valid CMS: F1", array(2, bstr(a[:]), bstr(a[:])), ana, luis) var many []vsigner for i := range 17 { name := fmt.Sprintf("Firmante %02d", i+1) many = append(many, named(cmstest.NewECDSA(name, elliptic.P256(), certFrom, certTo), name)) } // 16, the most: F6. 17, beside a valid CMS of the 17: F1. var wants []want for _, s := range many[:16] { wants = append(wants, valid(s, tsa)) } g.add(vcase{name: "alg 2: SIGNERS of 16 entries, the most, each signer sealed: F6", area: g.area(g.signed(many[:16], sealed, many[:16]...), nil), sig: capsule.VerdictSignedComplete, seal: capsule.VerdictNoSeal, signers: wants}) hashes := make([][]byte, 17) for i, s := range many { h := s.hash() hashes[i] = h[:] } slices.SortFunc(hashes, bytes.Compare) var items [][]byte for _, h := range hashes { items = append(items, bstr(h)) } cosigned("alg 2: SIGNERS of 17 entries, beside a valid CMS of the 17: F1", append([]byte{0x91}, bytes.Join(items, nil)...), many...) } // formCases break the form of the CMS signature (spec §29.10, rules 1 to 4 // and the rules after them): F1. func (g *cmsGen) formCases() { ana, luis, _, tsa := g.people() tok := sealedBy(tsa, vecSigned, cmstest.TokenOptions{Accuracy: time.Second}) only := []vsigner{ana} unchecked := func(name string, required []vsigner, o cmstest.Options, signers ...vsigner) { o.Token = tok g.add(vcase{name: name, area: g.area(g.signed(required, o, signers...), nil), sig: capsule.VerdictSignatureUnchecked, seal: capsule.VerdictNoSeal}) } unchecked("alg 2: the signature in BER: F1", only, cmstest.Options{BER: true}, ana) unchecked("alg 2: signerInfos out of order: F1", []vsigner{ana, luis}, cmstest.Options{Unsorted: true}, ana, luis) unchecked("alg 2: two SignerInfo of one certificate: F1", only, cmstest.Options{}, ana, ana) unchecked("alg 2: the same SignerInfo twice: F1", only, cmstest.Options{SignerInfoTwice: true}, ana) unchecked("alg 2: a SignerInfo of version 3 with issuerAndSerialNumber: F1", only, cmstest.Options{Version: 3}, ana) unchecked("alg 2: a SignerInfo of version 1 with subjectKeyIdentifier: F1", only, cmstest.Options{Version: 1, SKI: true}, ana) unchecked("alg 2: two content-type attributes: F1", only, cmstest.Options{ContentType2: true}, ana) unchecked("alg 2: a second content-type with an empty set of values: F1", only, cmstest.Options{ExtraAttrs: [][]byte{cmstest.Seq(cmstest.OID(cmstest.OIDContentType), cmstest.Set(0x31))}}, ana) unchecked("alg 2: an ESSCertIDv2 of SHA-1: F1", only, cmstest.Options{ESSHashAlg: cmstest.HashAlg(crypto.SHA1)}, ana) unchecked("alg 2: an ESSCertIDv2 with the hash of another certificate: F1", only, cmstest.Options{ESSCert: luis.Cert.Raw}, ana) unchecked("alg 2: only a signing-certificate, without the v2: F1", only, cmstest.Options{NoSigCertV2: true, SigCertV1: true}, ana) unchecked("alg 2: two signature-time-stamp attributes: F1", only, cmstest.Options{TimeStamps2: true}, ana) unchecked("alg 2: a CRL in crls: F1", only, cmstest.Options{CRLs: [][]byte{cmstest.Seq(cmstest.Int(1))}}, ana) unchecked("alg 2: no certificate of the signer: F1", only, cmstest.Options{OmitCert: true}, ana) // A certificate that breaks the profile names no signer: rule 3. v1 := vsigner{Signer: cmstest.NewCert(cmstest.CertSpec{CN: "Ana v1", NoVersion: true}, cmstest.ECKey(elliptic.P256()))} unchecked("alg 2: the certificate of the signer of version 1: F1", []vsigner{v1}, cmstest.Options{}, v1) frac := vsigner{Signer: cmstest.NewCert(cmstest.CertSpec{CN: "Ana", NotAfter: cmstest.GeneralizedTime("20391231235959.5Z")}, cmstest.ECKey(elliptic.P256()))} unchecked("alg 2: the certificate of the signer valid until a fraction of a second: F1", []vsigner{frac}, cmstest.Options{}, frac) twice := vsigner{Signer: cmstest.NewCert(cmstest.CertSpec{CN: "Ana", Extensions: [][]byte{cmstest.ExtSKI([]byte{1}), cmstest.ExtKeyUsage(), cmstest.ExtKeyUsage()}}, cmstest.ECKey(elliptic.P256()))} unchecked("alg 2: the certificate of the signer with an extension twice: F1", []vsigner{twice}, cmstest.Options{}, twice) } // algorithmCases are the table of algorithms and keys (spec §29.10, // "Algoritmos" and step 2), and what decides nothing. func (g *cmsGen) algorithmCases() { ana, luis, otro, tsa := g.people() tok := sealedBy(tsa, vecSigned, cmstest.TokenOptions{Accuracy: time.Second}) complete := func(name string, s vsigner, o cmstest.Options) { o.Token = tok g.add(vcase{name: name, area: g.area(g.signed([]vsigner{s}, o, s), nil), sig: capsule.VerdictSignedComplete, seal: capsule.VerdictNoSeal, signers: []want{valid(s, tsa)}}) } incomplete := func(name string, s vsigner, o cmstest.Options, r string) { o.Token = tok g.add(vcase{name: name, area: g.area(g.signed([]vsigner{s}, o, s), nil), sig: capsule.VerdictSignedIncomplete, seal: capsule.VerdictNoSeal, signers: []want{result(s, r)}}) } complete("alg 2: ECDSA P-256 with SHA-384: F6", ana, cmstest.Options{Hash: crypto.SHA384}) complete("alg 2: ECDSA P-256 with SHA-512: F6", ana, cmstest.Options{Hash: crypto.SHA512}) complete("alg 2: ECDSA P-384 with SHA-384: F6", otro, cmstest.Options{Hash: crypto.SHA384}) p521 := named(cmstest.NewECDSA("Raúl Sanz", elliptic.P521(), certFrom, certTo), "Raúl Sanz") complete("alg 2: ECDSA P-521 with SHA-512: F6", p521, cmstest.Options{Hash: crypto.SHA512}) complete("alg 2: RSA of 2048 bits, sha256WithRSAEncryption: F6", luis, cmstest.Options{SigAlg: cmstest.AlgID(cmstest.OIDSHA256RSA, cmstest.Null())}) complete("alg 2: RSA of 3072 bits: F6", named(cmstest.NewRSA("Sara Gil", 3072, certFrom, certTo), "Sara Gil"), cmstest.Options{}) complete("alg 2: RSA of 4096 bits with SHA-512: F6", named(cmstest.NewRSA("Pablo Ruiz", 4096, certFrom, certTo), "Pablo Ruiz"), cmstest.Options{Hash: crypto.SHA512}) complete("alg 2: RSASSA-PSS: F6", luis, cmstest.Options{PSS: true}) complete("alg 2: the sid by subjectKeyIdentifier: F6", ana, cmstest.Options{SKI: true}) complete("alg 2: a signing-certificate beside the v2: F6", ana, cmstest.Options{SigCertV1: true}) complete("alg 2: an ESSCertIDv2 with SHA-256 written: F6", ana, cmstest.Options{ESSHashAlg: cmstest.HashAlg(crypto.SHA256)}) complete("alg 2: an unknown attribute with an arc of 2^31: F6", ana, cmstest.Options{ExtraAttrs: [][]byte{cmstest.BigArcAttr()}}) complete("alg 2: the certificate of the signer twice in certificates: F6", ana, cmstest.Options{ExtraCerts: [][]byte{ana.Cert.Raw}}) v1 := cmstest.NewCert(cmstest.CertSpec{CN: "Intermedia de versión 1", NoVersion: true}, cmstest.ECKey(elliptic.P256())) complete("alg 2: a certificate of version 1 that names no signer: F6", ana, cmstest.Options{ExtraCerts: [][]byte{v1.Cert.Raw}}) complete("alg 2: an attribute certificate in certificates: F6", ana, cmstest.Options{ExtraCerts: [][]byte{cmstest.TLV(0xa1, cmstest.Seq(cmstest.Int(1)))}}) complete("alg 2: an OCSP response in crls: F6", ana, cmstest.Options{OCSP: cmstest.Seq(cmstest.Int(0))}) garbage := cmstest.NewCert(cmstest.CertSpec{CN: "Ana López", Signature: cmstest.BitString([]byte("not a signature"))}, cmstest.ECKey(elliptic.P256())) complete("alg 2: a certificate whose own signature is not one: F6", vsigner{garbage, "Ana López", "Ana López"}, cmstest.Options{}) numeric := cmstest.NewCert(cmstest.CertSpec{Subject: cmstest.Name(cmstest.ATV(cmstest.OIDCommonName, cmstest.UTF8("Ivan Petrov")), cmstest.ATV([]int{1, 2, 643, 3, 131, 1, 1}, cmstest.Numeric("123456789012")))}, cmstest.ECKey(elliptic.P256())) complete("alg 2: a NumericString in the subject, which is DER: F6", vsigner{numeric, "Ivan Petrov", "Ivan Petrov"}, cmstest.Options{}) // Step 2: outside the table, not verifiable. incomplete("alg 2: RSASSA-PSS with trailerField written: F5, not verifiable", luis, cmstest.Options{PSS: true, PSSTrailer: true}, "not verifiable") incomplete("alg 2: a digest outside the table, SHA-1: F5, not verifiable", ana, cmstest.Options{Hash: crypto.SHA1}, "not verifiable") incomplete("alg 2: RSA of 1024 bits: F5, not verifiable", named(cmstest.NewRSA("Clave corta", 1024, certFrom, certTo), "Clave corta"), cmstest.Options{}, "not verifiable") rsaKey := cmstest.RSAKey(2048) even := cmstest.NewCert(cmstest.CertSpec{CN: "Módulo par", SPKI: cmstest.SPKIRSA(new(big.Int).Add(rsaKey.N, big.NewInt(1)), big.NewInt(int64(rsaKey.E)))}, rsaKey) incomplete("alg 2: RSA with an even modulus: F5, not verifiable", vsigner{even, "Módulo par", "Módulo par"}, cmstest.Options{}, "not verifiable") ecKey := cmstest.ECKey(elliptic.P256()) compressed := cmstest.NewCert(cmstest.CertSpec{CN: "Punto comprimido", SPKI: cmstest.SPKICompressed(&ecKey.PublicKey)}, ecKey) incomplete("alg 2: an EC key compressed: F5, not verifiable", vsigner{compressed, "Punto comprimido", "Punto comprimido"}, cmstest.Options{}, "not verifiable") brainpool := cmstest.NewCert(cmstest.CertSpec{CN: "Curva brainpool", SPKI: cmstest.SPKIEC(cmstest.OID(cmstest.OIDBrainpoolP256), cmstest.Uncompressed(&ecKey.PublicKey))}, ecKey) incomplete("alg 2: a key on brainpoolP256r1: F5, not verifiable", vsigner{brainpool, "Curva brainpool", "Curva brainpool"}, cmstest.Options{}, "not verifiable") // Step 3: a key of another scheme than its algorithm is invalid. g.add(vcase{name: "alg 2: an RSA key with an ECDSA algorithm: F2", area: g.area(g.signed([]vsigner{luis}, cmstest.Options{Token: tok, SigAlg: cmstest.AlgID(cmstest.OIDECDSA256)}, luis), nil), sig: capsule.VerdictSignatureInvalid, seal: capsule.VerdictNoSeal, signers: []want{result(luis, "invalid")}}) } // nameCases are the names of a certificate as spec §29.7 shows them: a // holder from givenName and surname, or from commonName, with the rules of // the declared author, at most 64 code points and no two spaces in a row, and // the text of §29.10; otherwise the SHA-256 of the certificate. The issuer: // its commonName, or its organizationName without one, with the same rules; // otherwise the SHA-256 of its Name. func (g *cmsGen) nameCases() { _, _, _, tsa := g.people() tok := sealedBy(tsa, vecSigned, cmstest.TokenOptions{Accuracy: time.Second}) ca := cmstest.Name(cmstest.ATV(cmstest.OIDCommonName, cmstest.UTF8("CA de prueba"))) cn := func(v []byte) []byte { return cmstest.ATV(cmstest.OIDCommonName, v) } utf8 := cmstest.UTF8 holder := func(name string, subject []byte, shownAs string) { s := cmstest.NewCert(cmstest.CertSpec{Subject: subject, Issuer: ca}, cmstest.ECKey(elliptic.P256())) if shownAs == "" { shownAs = hashOfCert(s) } vs := vsigner{s, shownAs, "CA de prueba"} g.add(vcase{name: name, area: g.area(g.signed([]vsigner{vs}, cmstest.Options{Token: tok}, vs), nil), sig: capsule.VerdictSignedComplete, seal: capsule.VerdictNoSeal, signers: []want{valid(vs, tsa)}}) } sixtyFour := strings.Repeat("ñ", 64) holder("names: a commonName of 64 code points, shown", cmstest.Name(cn(utf8(sixtyFour))), sixtyFour) holder("names: a commonName of 65 code points, its SHA-256", cmstest.Name(cn(utf8(sixtyFour+"a"))), "") holder("names: two spaces in a row, its SHA-256", cmstest.Name(cn(utf8("Ana López"))), "") holder("names: an escape, its SHA-256", cmstest.Name(cn(utf8("Ana\x1b[2JLópez"))), "") holder("names: U+202E, its SHA-256", cmstest.Name(cn(utf8("Ana \xe2\x80\xaezepóL"))), "") holder("names: a byte order mark, its SHA-256", cmstest.Name(cn(utf8("\xef\xbb\xbfAna López"))), "") holder("names: a line feed, its SHA-256", cmstest.Name(cn(utf8("Ana\nLópez"))), "") holder("names: givenName, surname and a commonName with the NIF, the name without the NIF", cmstest.Name( cmstest.ATV(cmstest.OIDCountry, cmstest.Printable("ES")), cmstest.ATV(cmstest.OIDSerialNumber, cmstest.Printable("IDCES-12345678Z")), cmstest.ATV(cmstest.OIDSurname, utf8("ESPAÑOL ESPAÑOL")), cmstest.ATV(cmstest.OIDGivenName, utf8("JUAN")), cn(utf8("ESPAÑOL ESPAÑOL JUAN - 12345678Z"))), "JUAN ESPAÑOL ESPAÑOL") holder("names: a commonName in a VisibleString, no text: its SHA-256", cmstest.Name(cn(cmstest.Visible("Ana Lopez"))), "") holder("names: a PrintableString, shown", cmstest.Name(cn(cmstest.Printable("Ana Lopez"))), "Ana Lopez") holder("names: a PrintableString with an underscore, no text: its SHA-256", cmstest.Name(cn(cmstest.Printable("Ana_Lopez"))), "") holder("names: a PrintableString with an at sign, no text: its SHA-256", cmstest.Name(cn(cmstest.Printable("ana@example.com"))), "") holder("names: a BMPString, shown", cmstest.Name(cn(cmstest.BMPText("Ana López"))), "Ana López") holder("names: a BMPString of odd length, no text: its SHA-256", cmstest.Name(cn(cmstest.BMP(append(cmstest.BMPText("Ana")[2:], 0)))), "") holder("names: a BMPString with a surrogate, no text: its SHA-256", cmstest.Name(cn(cmstest.BMPText("Ana \xf0\x9f\x98\x80"))), "") holder("names: a TeletexString in ASCII, shown", cmstest.Name(cn(cmstest.Teletex("Ana Lopez"))), "Ana Lopez") holder("names: a TeletexString with a byte of 0xE9, no text: its SHA-256", cmstest.Name(cn(cmstest.Teletex("Ana L\xe9a"))), "") holder("names: an IA5String, shown", cmstest.Name(cn(cmstest.IA5("ana.lopez@example.com"))), "ana.lopez@example.com") holder("names: a UTF8String that is not UTF-8, no text: its SHA-256", cmstest.Name(cn(utf8("Ana L\xf3pez"))), "") holder("names: two commonNames, no text: its SHA-256", cmstest.Name(cn(utf8("Ana López")), cn(utf8("Luis Gómez"))), "") issuer := func(name string, issuerName []byte, shownAs string) { s := cmstest.NewCert(cmstest.CertSpec{CN: "Ana López", Issuer: issuerName}, cmstest.ECKey(elliptic.P256())) if shownAs == "" { shownAs = hashOfIssuer(s) } vs := vsigner{s, "Ana López", shownAs} g.add(vcase{name: name, area: g.area(g.signed([]vsigner{vs}, cmstest.Options{Token: tok}, vs), nil), sig: capsule.VerdictSignedComplete, seal: capsule.VerdictNoSeal, signers: []want{valid(vs, tsa)}}) } issuer("names: an issuer without a commonName, its organizationName", cmstest.Name(cmstest.ATV(cmstest.OIDCountry, cmstest.Printable("ES")), cmstest.ATV(cmstest.OIDOrganization, utf8("Banco de Pruebas S.A."))), "Banco de Pruebas S.A.") issuer("names: an issuer without text, the SHA-256 of its name", cmstest.Name(cmstest.ATV(cmstest.OIDCountry, cmstest.Printable("ES")), cmstest.ATV(cmstest.OIDOrgUnit, utf8("Unidad de pruebas"))), "") issuer("names: an issuer whose commonName has an escape, the SHA-256 of its name and not its organizationName", cmstest.Name( cmstest.ATV(cmstest.OIDOrganization, utf8("Banco de Pruebas S.A.")), cn(utf8("CA\x1b[2J de prueba"))), "") } // sealCases are the seals of seal_type 2 (spec §29.11), over an alg 1 // signature, which gives F4, unless a case says otherwise. func (g *cmsGen) sealCases() { _, _, _, tsa := g.people() key, err := authorkey.NewFromSeed(bytes.Repeat([]byte{7}, 32)) if err != nil { g.fail(err) return } pub, err := authorkey.PublicString(key.Public()) g.fail(err) msg := capsule.AuthorMessage(g.ctx.ControlCommit, g.ctx.HeadDigest, capsule.SignersDigest(capsule.AlgEd25519, nil)) sig := g.must(capsule.EncodeAuthorSignature(capsule.AlgEd25519, key.Public(), key.Sign(msg))) subject := capsule.SealSubject(g.ctx.ControlCommit, g.ctx.HeadDigest, capsule.SigPart(sig)) tok := func(when time.Time, o cmstest.TokenOptions, s vsigner) []byte { return cmstest.Token(subject[:], when, o, s.Signer) } sealArea := func(token []byte) []byte { return g.area(sig, g.must(capsule.EncodeSeal(capsule.SealTypeRFC3161, token))) } seal := func(name string, token []byte, verdict capsule.Verdict) { g.add(vcase{name: name, area: sealArea(token), sig: capsule.VerdictSignedOther, seal: verdict, authorKey: pub}) } sealedAt := func(name string, token []byte, s vsigner, t time.Time, verdict capsule.Verdict) { g.add(vcase{name: name, area: sealArea(token), sig: capsule.VerdictSignedOther, seal: verdict, authorKey: pub, sealTSA: s, sealTime: t}) } late := func(name string, token []byte, s vsigner, t time.Time, reason capsule.SealReason) { g.add(vcase{name: name, area: sealArea(token), sig: capsule.VerdictSignedOther, seal: capsule.VerdictSealedLate, authorKey: pub, sealTSA: s, sealTime: t, sealReason: reason}) } // The tokens of the cases about something else carry an accuracy of a // second: without it, a valid seal proves nothing before the round time // (spec v0.16, §29.11). second := cmstest.TokenOptions{Accuracy: time.Second} sealedAt("seal: before the round time: S4", tok(vecSigned, second, tsa), tsa, vecSigned, capsule.VerdictSealed) late("seal: after the round time, without accuracy: S5, sealed after", tok(vecRound.Add(time.Minute), cmstest.TokenOptions{}, tsa), tsa, vecRound.Add(time.Minute), capsule.ReasonLate) early := vecRound.Add(-time.Second) late("seal: t plus the accuracy past the round time: S5", tok(early, cmstest.TokenOptions{Accuracy: 2 * time.Second}, tsa), tsa, early, capsule.ReasonLate) late("seal: t plus the accuracy equal to the round time: S5", tok(early, cmstest.TokenOptions{Accuracy: time.Second}, tsa), tsa, early, capsule.ReasonLate) late("seal: without accuracy, years before the round time: S5, it does not say its precision", tok(vecSigned, cmstest.TokenOptions{}, tsa), tsa, vecSigned, capsule.ReasonNoAccuracy) late("seal: of the BTSP policy of ETSI, without accuracy: S5, it does not say the precision its policy requires", tok(vecSigned, cmstest.TokenOptions{Policy: cmstest.BTSPPolicy}, tsa), tsa, vecSigned, capsule.ReasonNoAccuracyBTSP) late("seal: of the BTSP policy, without accuracy, after the round time: S5, sealed after", tok(vecRound, cmstest.TokenOptions{Policy: cmstest.BTSPPolicy}, tsa), tsa, vecRound, capsule.ReasonLate) sealedAt("seal: of the BTSP policy, with accuracy: S4", tok(vecSigned, cmstest.TokenOptions{Policy: cmstest.BTSPPolicy, Accuracy: time.Second}, tsa), tsa, vecSigned, capsule.VerdictSealed) sealedAt("seal: an accuracy of 0 seconds, a microsecond before the round time: S4", tok(vecRound.Add(-time.Microsecond), cmstest.TokenOptions{AccuracyRaw: cmstest.Seq(cmstest.Int(0))}, tsa), tsa, vecRound.Add(-time.Microsecond), capsule.VerdictSealed) sealedAt("seal: an empty accuracy, a precision of 0: S4", tok(vecSigned, cmstest.TokenOptions{AccuracyRaw: cmstest.Seq()}, tsa), tsa, vecSigned, capsule.VerdictSealed) sealedAt("seal: t plus an accuracy of 999 ms and 999 µs, a microsecond before the round time: S4", tok(early, cmstest.TokenOptions{Accuracy: 999*time.Millisecond + 999*time.Microsecond}, tsa), tsa, early, capsule.VerdictSealed) sealedAt("seal: an accuracy of seconds, millis and micros: S4", tok(vecSigned, cmstest.TokenOptions{Accuracy: time.Second + 5*time.Millisecond + 7*time.Microsecond}, tsa), tsa, vecSigned, capsule.VerdictSealed) fraction := vecSigned.Add(250 * time.Millisecond) sealedAt("seal: a genTime with a fraction of a second: S4, t with its fraction", tok(fraction, second, tsa), tsa, fraction, capsule.VerdictSealed) sealedAt("seal: the certificate of the authority twice: S4", tok(vecSigned, cmstest.TokenOptions{TSATwice: true, Accuracy: time.Second}, tsa), tsa, vecSigned, capsule.VerdictSealed) sealedAt("seal: a CRL in the token decides nothing: S4", tok(vecSigned, cmstest.TokenOptions{CRL: cmstest.Seq(cmstest.Seq(cmstest.Int(1)), cmstest.Seq(cmstest.OID(cmstest.OIDECDSA256)), cmstest.BitString([]byte{0})), Accuracy: time.Second}, tsa), tsa, vecSigned, capsule.VerdictSealed) sealedAt("seal: signing-certificate-v2 in the token: S4", tok(vecSigned, cmstest.TokenOptions{SigCertV2: true, Accuracy: time.Second}, tsa), tsa, vecSigned, capsule.VerdictSealed) tsaName := cmstest.TLV(0xa0, cmstest.TLV(0xa4, cmstest.Name(cmstest.ATV(cmstest.OIDCommonName, cmstest.UTF8("Autoridad de Sellado de prueba"))))) exts := cmstest.TLV(0xa1, cmstest.Extension([]int{1, 2, 3, 4}, false, cmstest.Null())) sealedAt("seal: ordering TRUE, a nonce, a tsa and extensions: S4", tok(vecSigned, cmstest.TokenOptions{After: [][]byte{cmstest.Bool(true), cmstest.Int(99), tsaName, exts}, Accuracy: time.Second}, tsa), tsa, vecSigned, capsule.VerdictSealed) rsaTSA := named(cmstest.NewRSA("Autoridad RSA de prueba", 2048, certFrom, certTo), "Autoridad RSA de prueba") sealedAt("seal: an authority of RSA with RSASSA-PSS and SHA-512: S4", tok(vecSigned, cmstest.TokenOptions{CMS: cmstest.Options{PSS: true, Hash: crypto.SHA512}, Accuracy: time.Second}, rsaTSA), rsaTSA, vecSigned, capsule.VerdictSealed) // The case of §76, change 1: a name that lines up a text of its own. spaced := cmstest.NewECDSA("TSA"+strings.Repeat(" ", 50)+"Firmado con la clave que guardaste como Banco", elliptic.P256(), certFrom, certTo) vspaced := vsigner{spaced, hashOfCert(spaced), ""} sealedAt("seal: an authority named with 50 spaces and the text of F3: S4, by its SHA-256", tok(vecSigned, second, vspaced), vspaced, vecSigned, capsule.VerdictSealed) // S3: it reads, and does not verify (§29.11, step 3). seal("seal: over another subject: S3", cmstest.Token([]byte("other"), vecSigned, cmstest.TokenOptions{}, tsa.Signer), capsule.VerdictSealInvalid) seal("seal: a messageImprint of 33 bytes: S3", tok(vecSigned, cmstest.TokenOptions{Imprint: append(sha256Of(subject[:]), 0)}, tsa), capsule.VerdictSealInvalid) seal("seal: the authority had expired at its time: S3", tok(certTo.AddDate(1, 0, 0), cmstest.TokenOptions{}, tsa), capsule.VerdictSealInvalid) seal("seal: the authority was not yet valid at its time: S3", tok(certFrom.AddDate(-1, 0, 0), cmstest.TokenOptions{}, tsa), capsule.VerdictSealInvalid) seal("seal: the signature of the authority does not verify: S3", tok(vecSigned, cmstest.TokenOptions{CMS: cmstest.Options{CorruptSignature: true}}, tsa), capsule.VerdictSealInvalid) seal("seal: the message-digest of the token is not that of its TSTInfo: S3", tok(vecSigned, cmstest.TokenOptions{CMS: cmstest.Options{Message: []byte("another TSTInfo")}}, tsa), capsule.VerdictSealInvalid) // S2: the form (§29.11, step 1). seal("seal: not DER: S2", []byte("not DER"), capsule.VerdictSealUnreadable) seal("seal: a token in BER: S2", tok(vecSigned, cmstest.TokenOptions{CMS: cmstest.Options{BER: true}}, tsa), capsule.VerdictSealUnreadable) seal("seal: a token without its message-digest: S2", tok(vecSigned, cmstest.TokenOptions{NoMessageDigest: true}, tsa), capsule.VerdictSealUnreadable) info := cmstest.TSTInfo(subject[:], vecSigned, cmstest.TokenOptions{}) other := named(cmstest.NewECDSA("Otra autoridad", elliptic.P256(), certFrom, certTo), "Otra autoridad") seal("seal: a token of two SignerInfo: S2", cmstest.Merge(cmstest.TokenRaw(info, tsa.Signer), cmstest.TokenRaw(info, other.Signer)), capsule.VerdictSealUnreadable) seal("seal: a TSTInfo of version 2: S2", tok(vecSigned, cmstest.TokenOptions{Version: 2}, tsa), capsule.VerdictSealUnreadable) for _, tc := range []struct { name string raw []byte }{ {"seal: a negative accuracy: S2", cmstest.Seq(cmstest.Int(-1))}, {"seal: an accuracy of seconds in an INTEGER that is not minimal: S2", cmstest.Seq(cmstest.IntBytes([]byte{0, 5}))}, {"seal: an accuracy of millis in an INTEGER that is not minimal: S2", cmstest.Seq(cmstest.TLV(0x80, []byte{0, 5}))}, {"seal: an accuracy of 0 millis: S2", cmstest.Seq(cmstest.TLV(0x80, []byte{0}))}, {"seal: an accuracy of 1000 millis: S2", cmstest.Seq(cmstest.TLV(0x80, []byte{0x03, 0xe8}))}, {"seal: an accuracy of 1000 micros: S2", cmstest.Seq(cmstest.TLV(0x81, []byte{0x03, 0xe8}))}, {"seal: an accuracy of 2^31 seconds: S2", cmstest.Seq(cmstest.Int(1 << 31))}, } { seal(tc.name, tok(vecSigned, cmstest.TokenOptions{AccuracyRaw: tc.raw}, tsa), capsule.VerdictSealUnreadable) } seal("seal: a genTime without Z: S2", tok(vecSigned, cmstest.TokenOptions{GenTimeRaw: cmstest.GeneralizedTime("20260930120000")}, tsa), capsule.VerdictSealUnreadable) seal("seal: a genTime with a trailing zero in its fraction: S2", tok(vecSigned, cmstest.TokenOptions{GenTimeRaw: cmstest.GeneralizedTime("20260930120000.50Z")}, tsa), capsule.VerdictSealUnreadable) seal("seal: ordering FALSE written: S2", tok(vecSigned, cmstest.TokenOptions{OrderingFalse: true}, tsa), capsule.VerdictSealUnreadable) seal("seal: a field after the last: S2", tok(vecSigned, cmstest.TokenOptions{After: [][]byte{exts, cmstest.Int(7)}}, tsa), capsule.VerdictSealUnreadable) // S1: the algorithms (§29.11, step 2), after the form. seal("seal: SHA-384 in the imprint: S1", tok(vecSigned, cmstest.TokenOptions{Hash: crypto.SHA384}, tsa), capsule.VerdictSealUnsupported) seal("seal: a token signed with SHA-1: S1", tok(vecSigned, cmstest.TokenOptions{CMS: cmstest.Options{Hash: crypto.SHA1}}, tsa), capsule.VerdictSealUnsupported) ecKey := cmstest.ECKey(elliptic.P256()) compressed := vsigner{cmstest.NewCert(cmstest.CertSpec{CN: "TSA comprimida", SPKI: cmstest.SPKICompressed(&ecKey.PublicKey)}, ecKey), "TSA comprimida", "TSA comprimida"} seal("seal: an authority with a compressed key: S1", tok(vecSigned, cmstest.TokenOptions{}, compressed), capsule.VerdictSealUnsupported) seal("seal: an authority with a key of 1024 bits: S1", tok(vecSigned, cmstest.TokenOptions{}, named(cmstest.NewRSA("TSA corta", 1024, certFrom, certTo), "TSA corta")), capsule.VerdictSealUnsupported) // The seal stands apart from the signature: over no signature, and over // a signature of an alg that the reader does not implement, whose bytes it // seals all the same (§29.11, SIG_PART). noSig := capsule.SealSubject(g.ctx.ControlCommit, g.ctx.HeadDigest, capsule.SigPart(nil)) g.add(vcase{name: "seal: over a capsule without a signature: F0 and S4", area: g.area(nil, g.must(capsule.EncodeSeal(capsule.SealTypeRFC3161, cmstest.Token(noSig[:], vecSigned, second, tsa.Signer)))), sig: capsule.VerdictNoSignature, seal: capsule.VerdictSealed, sealTSA: tsa, sealTime: vecSigned}) unknown := g.must(capsule.EncodeAuthorSignature(capsule.AlgTest, []byte{1}, []byte{1})) beside := capsule.SealSubject(g.ctx.ControlCommit, g.ctx.HeadDigest, capsule.SigPart(unknown)) g.add(vcase{name: "seal: beside a signature of alg 4294967295, which it seals all the same: F1 and S4", area: g.area(unknown, g.must(capsule.EncodeSeal(capsule.SealTypeRFC3161, cmstest.Token(beside[:], vecSigned, second, tsa.Signer)))), sig: capsule.VerdictSignatureUnchecked, seal: capsule.VerdictSealed, sealTSA: tsa, sealTime: vecSigned}) } func sha256Of(b []byte) []byte { h := sha256.Sum256(b) return h[:] }