You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
DateKeys/internal/testkit/genfixtures/cmsvectors.go

826 lines
51 KiB

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[:]
}

Powered by TurnKey Linux.