The frozen vector of alg 2 and seal_type 2, made again with the profile of
v0.12:
- each case carries the lines of Verdicts.Lines, so that a second
implementation compares the texts byte for byte, and its times keep the
fraction of the token;
- the two cases without a context, which gave the verdicts of a reader of
v0.10, are gone, and the case named a seal from before the certificate
was valid, which gave an invalid seal, is named so;
- new cases for each row of §29.7 and each item of the lists of §64 for
v0.11 and v0.12: out of validity with a valid authority, SIGNERS that
break its rule beside a valid CMS (out of order, empty, 17 entries, 31
bytes, a hash twice) and 16 signers, the version against the sid, two
content-type attributes, the ESSCertIDv2, PSS with and without
trailerField, an arc of 2^31, a certificate twice or of version 1, keys
outside the table, every hash and curve of the table, BER, two
SignerInfo of one certificate, two time-stamps, the names of the holder
and of the issuer in each string type and against each rule, and the
edges of the token: accuracy, genTime, ordering, fields after the last,
the imprint, crls and the authority.
The generator checks each case against what the spec gives, written apart
from the code: the verdicts, the result of each signer and the lines,
built from the texts of §29.7. It fails when the reader gives anything
else. capsule reads every field of the file, the lines included.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
// check compares what the reader gave with what the spec gives: the
Spec:testkit.SpecVersion,
// verdicts, the result of each signer and the lines, written from the texts
Description:"SECURITY_CBOR with an author signature of alg 2 or a time seal of seal_type 2, its context and the verdicts of spec v0.11 29.7, 29.10 and 29.11. "+
// of §29.7.
"Certificates and tokens are made once with test keys and the file is frozen. See testdata/README.md.",
lines=append(lines,"Firmado con la clave "+c.authorKey+". No prueba quién la tiene.")
casecapsule.VerdictSignedComplete:
varnames,each[]string
before:=false
for_,r:=rangereqs{
names=append(names,q(r.s.holder))
when:="no antes de la fecha de apertura"
ifr.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+".")
}
}
f.Cases=append(f.Cases,vc)
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...)
must:=func(b[]byte,errerror)[]byte{
ifbefore{
iferr!=nil{
lines=append(lines," DateKeys no comprueba quién emitió los sellos.")
errs=append(errs,err)
}
}
returnb
default:
}
returnfmt.Errorf("no lines for %s",c.sig)
both:=[]cmstest.Signer{ana,luis}
}
for_,w:=rangec.foreign{
c:=ctx()
lines=append(lines," Otro firmante, "+q(w.s.holder)+": "+resultTexts[w.result]+". No cuenta.")
add("alg 2: two signers, each sealed before the round time",must(cmsArea(c,both,both,tsa,vecSigned,nil)),c,capsule.VerdictSignedComplete,capsule.VerdictNoSeal)
}
add("alg 2: a seal after the round time proves nothing before it",must(cmsArea(c,[]cmstest.Signer{ana},[]cmstest.Signer{ana},tsa,vecRound.Add(time.Hour),nil)),c,capsule.VerdictSignedComplete,capsule.VerdictNoSeal)
switchc.seal{
add("alg 2: a signer who is not required shows apart",must(cmsArea(c,[]cmstest.Signer{ana},[]cmstest.Signer{ana,otro},tsa,vecSigned,nil)),c,capsule.VerdictSignedComplete,capsule.VerdictNoSeal)
casecapsule.VerdictNoSeal:
add("alg 2: a required signer is absent",must(cmsArea(c,both,[]cmstest.Signer{ana},tsa,vecSigned,nil)),c,capsule.VerdictSignedIncomplete,capsule.VerdictNoSeal)
casecapsule.VerdictSealUnsupported:
add("alg 2: no seal",must(cmsArea(c,[]cmstest.Signer{ana},[]cmstest.Signer{ana},cmstest.Signer{},vecSigned,nil)),c,capsule.VerdictSignedIncomplete,capsule.VerdictNoSeal)
lines=append(lines,textS1)
add("alg 2: a seal from before the certificate was valid",must(cmsArea(c,[]cmstest.Signer{ana},[]cmstest.Signer{ana},tsa,certFrom.AddDate(-1,0,0),nil)),c,capsule.VerdictSignedIncomplete,capsule.VerdictNoSeal)
casecapsule.VerdictSealUnreadable:
add("alg 2: a key 3 beside it",must(cmsArea(c,[]cmstest.Signer{ana},[]cmstest.Signer{ana},tsa,vecSigned,must(capsule.EncodeSeal(1,[]byte{1})))),c,capsule.VerdictSignedIncomplete,capsule.VerdictSealUnsupported)
lines=append(lines,textS2)
other:=ctx()
casecapsule.VerdictSealInvalid:
other.HeadDigest[5]^=9
lines=append(lines,textS3)
add("alg 2: another head",must(cmsArea(c,[]cmstest.Signer{ana},[]cmstest.Signer{ana},tsa,vecSigned,nil)),other,capsule.VerdictSignatureInvalid,capsule.VerdictNoSeal)
casecapsule.VerdictSealedLate:
add("alg 2: without the context of a capsule",must(cmsArea(c,[]cmstest.Signer{ana},[]cmstest.Signer{ana},tsa,vecSigned,nil)),nil,capsule.VerdictSignatureUnchecked,capsule.VerdictNoSeal)
lines=append(lines,textS5)
casecapsule.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.")
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),
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),
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),
add("seal: after the round time",sealed(tok(subject[:],vecRound.Add(time.Minute),cmstest.TokenOptions{},tsa)),c,okSig,capsule.VerdictSealedLate)
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),
add("seal: the accuracy reaches the round time",sealed(tok(subject[:],vecRound.Add(-time.Second),cmstest.TokenOptions{Accuracy:2*time.Second},tsa)),c,okSig,capsule.VerdictSealedLate)
sig:capsule.VerdictSignedIncomplete,seal:capsule.VerdictNoSeal,signers:[]want{result(expired,"out of validity")}})
add("seal: over another subject",sealed(tok([]byte("other"),vecSigned,cmstest.TokenOptions{},tsa)),c,okSig,capsule.VerdictSealInvalid)
// The validity is inclusive (RFC 5280 4.1.2.5): sealed at the last second.
add("seal: the authority had expired at its time",sealed(tok(subject[:],certTo.AddDate(1,0,0),cmstest.TokenOptions{},tsa)),c,okSig,capsule.VerdictSealInvalid)
add("seal: SHA-384 in the imprint",sealed(tok(subject[:],vecSigned,cmstest.TokenOptions{Hash:crypto.SHA384},tsa)),c,okSig,capsule.VerdictSealUnsupported)
// Step 5: a token of the profile of §29.11 that gives S3, S2 or S1 is an
add("seal: without a context, as a reader of v0.10",sealed(tok(subject[:],vecSigned,cmstest.TokenOptions{},tsa)),nil,capsule.VerdictSignatureUnchecked,capsule.VerdictSealUnsupported)
add("seal: over a capsule without a signature",must(capsule.EncodeSecurityWith(nil,must(capsule.EncodeSeal(capsule.SealTypeRFC3161,tok(noSig[:],vecSigned,cmstest.TokenOptions{},tsa))))),
{"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{
{"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{})}},
// 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),
// 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),
// 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}))),
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{},tsa.Signer)))),
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),
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),
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.
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{})
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),
sealedAt("seal: before the round time: S4",tok(vecSigned,cmstest.TokenOptions{},tsa),tsa,vecSigned,capsule.VerdictSealed)
sealedAt("seal: after the round time: S5",tok(vecRound.Add(time.Minute),cmstest.TokenOptions{},tsa),tsa,vecRound.Add(time.Minute),capsule.VerdictSealedLate)
early:=vecRound.Add(-time.Second)
sealedAt("seal: t plus the accuracy past the round time: S5",tok(early,cmstest.TokenOptions{Accuracy:2*time.Second},tsa),tsa,early,capsule.VerdictSealedLate)
sealedAt("seal: t plus the accuracy equal to the round time: S5",tok(early,cmstest.TokenOptions{Accuracy:time.Second},tsa),tsa,early,capsule.VerdictSealedLate)
sealedAt("seal: t plus an accuracy of 999 ms and 999 µs, a microsecond before the round time: S4",
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,cmstest.TokenOptions{},tsa),tsa,fraction,capsule.VerdictSealed)
sealedAt("seal: the certificate of the authority twice: S4",tok(vecSigned,cmstest.TokenOptions{TSATwice:true},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}))},tsa),tsa,vecSigned,capsule.VerdictSealed)
sealedAt("seal: signing-certificate-v2 in the token: S4",tok(vecSigned,cmstest.TokenOptions{SigCertV2:true},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")))))
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}},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}},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,cmstest.TokenOptions{},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)
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{
namestring
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("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: 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)
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
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,cmstest.TokenOptions{},tsa.Signer)))),
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,cmstest.TokenOptions{},tsa.Signer)))),