security_cms.json for spec v0.12: 135 cases, each with its lines

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>
v0.12
dev 6 days ago
parent 4ce4d59c8d
commit edff5d1004

@ -4,6 +4,8 @@ import (
"encoding/hex" "encoding/hex"
"path/filepath" "path/filepath"
"reflect" "reflect"
"slices"
"strings"
"testing" "testing"
"time" "time"
@ -12,50 +14,93 @@ import (
) )
// testdata/vectors/security_cms.json is frozen: each case is evaluated again, // testdata/vectors/security_cms.json is frozen: each case is evaluated again,
// with its context, and must give its verdicts, the results of each signer // with its context, and must give its verdicts, the result of each signer,
// and the authority of a valid seal (spec v0.11 §29.7, §29.10, §29.11). // the authority and the time of a valid seal, and its lines, byte for byte
// (spec v0.12 §29.7, §29.10, §29.11).
func TestCMSVectors(t *testing.T) { func TestCMSVectors(t *testing.T) {
var f testkit.CMSVectorFile var f testkit.CMSVectorFile
if err := testkit.ReadJSON(filepath.Join("..", "testdata", "vectors", "security_cms.json"), &f); err != nil { if err := testkit.ReadJSON(filepath.Join("..", "testdata", "vectors", "security_cms.json"), &f); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if len(f.Cases) < 20 { if f.Spec != "0.12" || !strings.Contains(f.Description, "v0.12") || len(f.Cases) < 100 {
t.Fatalf("%d cases", len(f.Cases)) t.Fatalf("spec %q, %d cases: %s", f.Spec, len(f.Cases), f.Description)
} }
seen := map[string]bool{}
verdicts := map[string]bool{}
for _, c := range f.Cases { for _, c := range f.Cases {
if seen[c.Name] {
t.Errorf("two cases named %q", c.Name)
}
seen[c.Name] = true
verdicts[c.Signature] = true
verdicts[c.Seal] = true
t.Run(c.Name, func(t *testing.T) { t.Run(c.Name, func(t *testing.T) {
area, err := hex.DecodeString(c.SecurityCBOR) area, err := hex.DecodeString(c.SecurityCBOR)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
var v capsule.Verdicts
if c.NoContext {
v = capsule.EvaluateSecurity(area)
} else {
var sc capsule.SecurityContext var sc capsule.SecurityContext
cc, _ := hex.DecodeString(c.Context.ControlCommit) cc, err1 := hex.DecodeString(c.Context.ControlCommit)
hd, _ := hex.DecodeString(c.Context.HeadDigest) hd, err2 := hex.DecodeString(c.Context.HeadDigest)
if err1 != nil || err2 != nil || len(cc) != 32 || len(hd) != 32 {
t.Fatalf("the context: %+v", c.Context)
}
copy(sc.ControlCommit[:], cc) copy(sc.ControlCommit[:], cc)
copy(sc.HeadDigest[:], hd) copy(sc.HeadDigest[:], hd)
if sc.RoundTime, err = time.Parse(time.RFC3339, c.Context.RoundTime); err != nil { if sc.RoundTime, err = time.Parse(time.RFC3339, c.Context.RoundTime); err != nil {
t.Fatal(err) t.Fatal(err)
} }
v = capsule.EvaluateSecurityIn(area, &sc) v := capsule.EvaluateSecurityIn(area, &sc)
}
if string(v.Signature) != c.Signature || string(v.Seal) != c.Seal { if string(v.Signature) != c.Signature || string(v.Seal) != c.Seal {
t.Fatalf("verdicts %s and %s, want %s and %s", v.Signature, v.Seal, c.Signature, c.Seal) t.Fatalf("verdicts %s and %s, want %s and %s", v.Signature, v.Seal, c.Signature, c.Seal)
} }
var signers, foreign []testkit.FixtureSignerResult var signers, foreign []testkit.FixtureSignerResult
var holder, when string var holder, when string
if d := v.Detail; d != nil { if d := v.Detail; d != nil {
signers, foreign = signerResults(d.Signers), signerResults(d.Foreign) signers, foreign = vectorSignerResults(d.Signers), vectorSignerResults(d.Foreign)
if !d.SealTime.IsZero() { if !d.SealTime.IsZero() {
holder, when = d.SealHolder, d.SealTime.UTC().Format(time.RFC3339) holder, when = d.SealHolder, d.SealTime.UTC().Format(time.RFC3339Nano)
} }
} }
if !reflect.DeepEqual(signers, c.Signers) || !reflect.DeepEqual(foreign, c.Foreign) || holder != c.SealHolder || when != c.SealTime { if !reflect.DeepEqual(signers, c.Signers) || !reflect.DeepEqual(foreign, c.Foreign) || holder != c.SealHolder || when != c.SealTime {
t.Errorf("signers %+v foreign %+v seal %q %q; want %+v %+v %q %q", signers, foreign, holder, when, c.Signers, c.Foreign, c.SealHolder, c.SealTime) t.Errorf("signers %+v foreign %+v seal %q %q; want %+v %+v %q %q", signers, foreign, holder, when, c.Signers, c.Foreign, c.SealHolder, c.SealTime)
} }
if lines := v.Lines(); !slices.Equal(lines, c.Lines) {
t.Errorf("lines %q, want %q", lines, c.Lines)
}
// No line of the verdicts carries a control or a bidi character,
// whatever the certificates say (spec §29.7).
for _, line := range c.Lines {
for _, r := range line {
if r < 0x20 || r >= 0x7f && r <= 0x9f || r >= 0x202a && r <= 0x202e || r >= 0x2066 && r <= 0x2069 || r == 0xfeff {
t.Errorf("a line with %U: %q", r, line)
}
}
}
}) })
} }
// Every verdict of alg 2 and of seal_type 2 has a case.
for _, want := range []capsule.Verdict{
capsule.VerdictNoSignature, capsule.VerdictSignatureUnchecked, capsule.VerdictSignatureInvalid, capsule.VerdictSignedOther,
capsule.VerdictSignedIncomplete, capsule.VerdictSignedComplete, capsule.VerdictNoSeal, capsule.VerdictSealUnsupported,
capsule.VerdictSealUnreadable, capsule.VerdictSealInvalid, capsule.VerdictSealed, capsule.VerdictSealedLate,
} {
if !verdicts[string(want)] {
t.Errorf("no case gives %s", want)
}
}
}
// vectorSignerResults are the results of the signers as security_cms.json
// writes them: t with the fraction of its token when it has one.
func vectorSignerResults(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}
if !l.SealTime.IsZero() {
r.SealTime = l.SealTime.UTC().Format(time.RFC3339Nano)
}
out = append(out, r)
}
return out
} }

@ -3,9 +3,10 @@ package testkit
// CMSVectorFile is testdata/vectors/security_cms.json: SECURITY_CBOR areas // CMSVectorFile is testdata/vectors/security_cms.json: SECURITY_CBOR areas
// with an author signature of alg 2 (CMS with certificates) or a time seal // with an author signature of alg 2 (CMS with certificates) or a time seal
// of seal_type 2 (RFC 3161), each with the context of its capsule and the // of seal_type 2 (RFC 3161), each with the context of its capsule and the
// verdicts that spec v0.11 §29.7, §29.10 and §29.11 give. The certificates // verdicts, the results and the lines that spec v0.12 §29.7, §29.10 and
// and the tokens are made once, with test keys, and the file is frozen: a // §29.11 give. The certificates and the tokens are made once, with test
// second implementation reads them and must reach the same verdicts. // keys, and the file is frozen: a second implementation reads them and must
// reach the same verdicts and write the same lines, byte for byte.
type CMSVectorFile struct { type CMSVectorFile struct {
Description string `json:"description"` Description string `json:"description"`
Spec string `json:"spec"` Spec string `json:"spec"`
@ -20,22 +21,24 @@ type CMSVectorContext struct {
RoundTime string `json:"round_time"` RoundTime string `json:"round_time"`
} }
// CMSVectorCase is one case: the area, its context, and the verdicts. // CMSVectorCase is one case: the area, its context, and what a reader gives.
type CMSVectorCase struct { type CMSVectorCase struct {
Name string `json:"name"` Name string `json:"name"`
SecurityCBOR string `json:"security_cbor"` SecurityCBOR string `json:"security_cbor"`
// NoContext is true when the area is read without the context of a
// capsule, as a reader of v0.10 does: any signature is F1, any seal S1.
NoContext bool `json:"no_context,omitempty"`
Context CMSVectorContext `json:"context"` Context CMSVectorContext `json:"context"`
Signature string `json:"signature"` Signature string `json:"signature"`
Seal string `json:"seal"` Seal string `json:"seal"`
// Signers and Foreign are the results of an alg 2 signature, in the // Signers and Foreign are the results of an alg 2 signature, in the
// order of SIGNERS; SealHolder and SealTime are those of a valid seal. // order of SIGNERS and of the SignerInfo; SealHolder and SealTime are
// those of a valid seal of key 3. A time is in RFC 3339, with the
// fraction of the token when it has one.
Signers []FixtureSignerResult `json:"signers,omitempty"` Signers []FixtureSignerResult `json:"signers,omitempty"`
Foreign []FixtureSignerResult `json:"foreign_signers,omitempty"` Foreign []FixtureSignerResult `json:"foreign_signers,omitempty"`
SealHolder string `json:"seal_holder,omitempty"` SealHolder string `json:"seal_holder,omitempty"`
SealTime string `json:"seal_time,omitempty"` SealTime string `json:"seal_time,omitempty"`
// Lines are the verdicts as the official SDK shows them (§29.7):
// Verdicts.Lines.
Lines []string `json:"lines"`
} }
// LocatorVectorFile is testdata/vectors/locator.json: the extension // LocatorVectorFile is testdata/vectors/locator.json: the extension

@ -8,8 +8,12 @@ import (
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt" "fmt"
"math/big"
"os" "os"
"path/filepath" "path/filepath"
"reflect"
"slices"
"strings"
"time" "time"
"g.activething.com/go/DateKeys/authorkey" "g.activething.com/go/DateKeys/authorkey"
@ -27,19 +31,22 @@ import (
// age and of tlock, so they are made once and kept, as the fixtures are. // age and of tlock, so they are made once and kept, as the fixtures are.
// Delete a file to make it again. // Delete a file to make it again.
func frozenVectors(dir string) error { func frozenVectors(dir string) error {
for name, gen := range map[string]func() (any, error){ for _, v := range []struct {
"security_cms.json": securityCMSVectors, name string
"locator.json": locatorVectors, gen func() (any, error)
}{ }{
path := filepath.Join(dir, name) {"security_cms.json", securityCMSVectors},
{"locator.json", locatorVectors},
} {
path := filepath.Join(dir, v.name)
if _, err := os.Stat(path); err == nil { if _, err := os.Stat(path); err == nil {
continue continue
} }
v, err := gen() out, err := v.gen()
if err != nil { if err != nil {
return fmt.Errorf("%s: %w", name, err) return fmt.Errorf("%s: %w", v.name, err)
} }
if err := testkit.WriteJSON(path, v); err != nil { if err := testkit.WriteJSON(path, out); err != nil {
return err return err
} }
} }
@ -51,137 +58,736 @@ var (
vecSigned = time.Date(2026, 9, 30, 12, 0, 0, 0, time.UTC) vecSigned = time.Date(2026, 9, 30, 12, 0, 0, 0, time.UTC)
) )
// cmsVectorSpec is the version of the spec whose verdicts security_cms.json
// gives: the draft v0.12, with the profile of the certificate of §29.10 and
// the texts of §29.7.
const cmsVectorSpec = "0.12"
func hex32(b [32]byte) string { return hex.EncodeToString(b[:]) } func hex32(b [32]byte) string { return hex.EncodeToString(b[:]) }
// cmsArea builds SECURITY_CBOR with an alg 2 signature by the signers over // The texts of the verdicts, copied from the table of spec v0.12 §29.7: the
// the context c, sealed by tsa at when; required lists who must sign. // lines of each case are checked against them, not against Verdicts.Lines.
func cmsArea(c *capsule.SecurityContext, required, signers []cmstest.Signer, tsa cmstest.Signer, when time.Time, seal []byte) ([]byte, error) { const (
var hashes [][32]byte textF0 = "Sin firma de autor."
for _, s := range required { textF1 = "No se ha comprobado ninguna firma: trátala como no firmada."
hashes = append(hashes, sum256(s.Cert.Raw)) 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 = "Sellado después de la fecha de apertura: no prueba nada anterior."
)
// 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",
} }
list, err := capsule.EncodeSigners(hashes)
if err != nil { // vsigner is a signer with the names of its certificate as §29.7 shows them:
return nil, err // 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 t plus the accuracy is before
// round_time.
type want struct {
s vsigner
result string
tsa vsigner
t time.Time
before bool
} }
msg := capsule.AuthorMessage(c.ControlCommit, c.HeadDigest, capsule.SignersDigest(capsule.AlgCMS, list))
opts := cmstest.Options{} // vcase is a case of security_cms.json with what spec v0.12 gives for it.
if tsa.Key != nil { type vcase struct {
opts.Token = func(sig []byte) []byte { name string
return cmstest.Token(sig, when, cmstest.TokenOptions{Accuracy: time.Second}, tsa) 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),
// and authorKey the key of a valid signature of alg 1 (F4).
sealTSA vsigner
sealTime time.Time
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
} }
content, err := capsule.EncodeAuthorSignature(capsule.AlgCMS, list, cmstest.Signature(msg, opts, signers...))
func (g *cmsGen) fail(err error) {
if err != nil { if err != nil {
return nil, err g.errs = append(g.errs, err)
} }
return capsule.EncodeSecurityWith(content, seal)
} }
func securityCMSVectors() (any, error) { func (g *cmsGen) must(b []byte, err error) []byte {
ana := cmstest.NewECDSA("Ana López", elliptic.P256(), certFrom, certTo) g.fail(err)
luis := cmstest.NewRSA("Luis Gómez", 2048, certFrom, certTo) return b
otro := cmstest.NewECDSA("Otro", elliptic.P384(), certFrom, certTo)
tsa := cmstest.NewECDSA("Autoridad de Sellado de prueba", elliptic.P256(), certFrom, certTo)
ctx := func() *capsule.SecurityContext {
return &capsule.SecurityContext{ControlCommit: sha256.Sum256([]byte("control")), HeadDigest: sha256.Sum256([]byte("head")), RoundTime: vecRound}
} }
key, err := authorkey.NewFromSeed(bytes.Repeat([]byte{7}, 32))
if err != nil { // signersOf is SIGNERS for the required signers, canonical.
return nil, err 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))
} }
f := testkit.CMSVectorFile{ // messageOf is AUTHOR_MESSAGE for the SIGNERS list, in the common context.
Spec: testkit.SpecVersion, func (g *cmsGen) messageOf(list []byte) []byte {
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. " + return capsule.AuthorMessage(g.ctx.ControlCommit, g.ctx.HeadDigest, capsule.SignersDigest(capsule.AlgCMS, list))
"Certificates and tokens are made once with test keys and the file is frozen. See testdata/README.md.",
} }
var errs []error
add := func(name string, area []byte, c *capsule.SecurityContext, wantSig, wantSeal capsule.Verdict) { // content is the content of key 2 of a signature of alg 2 with the SIGNERS
var v capsule.Verdicts // list and the CMS signature sig.
vc := testkit.CMSVectorCase{Name: name, SecurityCBOR: hex.EncodeToString(area)} func (g *cmsGen) content(list, sig []byte) []byte {
if c == nil { return g.must(capsule.EncodeAuthorSignature(capsule.AlgCMS, list, sig))
v, vc.NoContext = capsule.EvaluateSecurity(area), true }
c = ctx()
} else { // signed is the content of key 2 of a signature by the signers, with the
v = capsule.EvaluateSecurityIn(area, c) // 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)...))
} }
vc.Context = testkit.CMSVectorContext{ControlCommit: hex32(c.ControlCommit), HeadDigest: hex32(c.HeadDigest), RoundTime: c.RoundTime.UTC().Format(time.RFC3339)}
vc.Signature, vc.Seal = string(v.Signature), string(v.Seal) func plain(ss []vsigner) []cmstest.Signer {
if v.Signature != wantSig || v.Seal != wantSeal { out := make([]cmstest.Signer, len(ss))
errs = append(errs, fmt.Errorf("%s: verdicts %s and %s, want %s and %s", name, v.Signature, v.Seal, wantSig, wantSeal)) 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 { if d := v.Detail; d != nil {
vc.Signers, vc.Foreign = signerResults(d.Signers), signerResults(d.Foreign) rec.Signers, rec.Foreign = cmsSignerResults(d.Signers), cmsSignerResults(d.Foreign)
if !d.SealTime.IsZero() { if !d.SealTime.IsZero() {
vc.SealHolder, vc.SealTime = d.SealHolder, d.SealTime.UTC().Format(time.RFC3339) rec.SealHolder, rec.SealTime = d.SealHolder, d.SealTime.UTC().Format(time.RFC3339Nano)
} }
} }
f.Cases = append(f.Cases, vc) if err := c.check(v, rec); err != nil {
g.errs = append(g.errs, fmt.Errorf("%s: %w", c.name, err))
} }
must := func(b []byte, err error) []byte { for _, other := range g.file.Cases {
if err != nil { if other.Name == c.name {
errs = append(errs, err) g.errs = append(g.errs, fmt.Errorf("%s: two cases of that name", c.name))
} }
return b
} }
both := []cmstest.Signer{ana, luis} g.file.Cases = append(g.file.Cases, rec)
}
c := ctx()
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)
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)
add("alg 2: a required signer is absent", must(cmsArea(c, both, []cmstest.Signer{ana}, tsa, vecSigned, nil)), c, capsule.VerdictSignedIncomplete, capsule.VerdictNoSeal)
add("alg 2: no seal", must(cmsArea(c, []cmstest.Signer{ana}, []cmstest.Signer{ana}, cmstest.Signer{}, vecSigned, nil)), c, capsule.VerdictSignedIncomplete, capsule.VerdictNoSeal)
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)
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)
other := ctx()
other.HeadDigest[5] ^= 9
add("alg 2: another head", must(cmsArea(c, []cmstest.Signer{ana}, []cmstest.Signer{ana}, tsa, vecSigned, nil)), other, capsule.VerdictSignatureInvalid, capsule.VerdictNoSeal)
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)
one, two := sum256([]byte("a")), sum256([]byte("b")) // cmsSignerResults are the results of the signers as the record writes them,
if bytes.Compare(one[:], two[:]) > 0 { // t with its fraction when it has one.
one, two = two, 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}
if !l.SealTime.IsZero() {
r.SealTime = l.SealTime.UTC().Format(time.RFC3339Nano)
} }
unsorted := append(append([]byte{0x82, 0x58, 0x20}, two[:]...), append([]byte{0x58, 0x20}, one[:]...)...) out = append(out, r)
for name, signers := range map[string][]byte{"alg 2: SIGNERS out of order": unsorted, "alg 2: an empty SIGNERS": {0x80}} {
content, _ := capsule.EncodeAuthorSignature(capsule.AlgCMS, signers, []byte{0x30, 0x00})
add(name, must(capsule.EncodeSecurityWith(content, nil)), c, capsule.VerdictSignatureUnchecked, capsule.VerdictNoSeal)
} }
{ return out
list, _ := capsule.EncodeSigners([][32]byte{sum256(ana.Cert.Raw)}) }
content, _ := capsule.EncodeAuthorSignature(capsule.AlgCMS, list, []byte("not DER"))
add("alg 2: not a CMS", must(capsule.EncodeSecurityWith(content, nil)), c, capsule.VerdictSignatureUnchecked, capsule.VerdictNoSeal) func (w want) record() testkit.FixtureSignerResult {
} r := testkit.FixtureSignerResult{Holder: w.s.holder, Issuer: w.s.issuer, Result: w.result}
if w.result == "valid" {
// Seals of seal_type 2 over an alg 1 signature. r.SealTime, r.Before = w.t.UTC().Format(time.RFC3339Nano), w.before
msg := capsule.AuthorMessage(c.ControlCommit, c.HeadDigest, capsule.SignersDigest(capsule.AlgEd25519, nil)) }
sig := must(capsule.EncodeAuthorSignature(capsule.AlgEd25519, key.Public(), key.Sign(msg))) return r
subject := capsule.SealSubject(c.ControlCommit, c.HeadDigest, capsule.SigPart(sig)) }
sealed := func(token []byte) []byte {
return must(capsule.EncodeSecurityWith(sig, must(capsule.EncodeSeal(capsule.SealTypeRFC3161, token)))) // 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
tok := func(subject []byte, when time.Time, o cmstest.TokenOptions, s cmstest.Signer) []byte { // of §29.7.
return cmstest.Token(subject, when, o, s) func (c vcase) check(v capsule.Verdicts, rec testkit.CMSVectorCase) error {
} if v.Signature != c.sig || v.Seal != c.seal {
okSig := capsule.VerdictSignedOther return fmt.Errorf("verdicts %s and %s, want %s and %s", v.Signature, v.Seal, c.sig, c.seal)
add("seal: before the round time", sealed(tok(subject[:], vecSigned, cmstest.TokenOptions{}, tsa)), c, okSig, capsule.VerdictSealed) }
add("seal: after the round time", sealed(tok(subject[:], vecRound.Add(time.Minute), cmstest.TokenOptions{}, tsa)), c, okSig, capsule.VerdictSealedLate) // The required signers in the order of SIGNERS: of their hashes, in
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) // ascending order of bytes.
add("seal: over another subject", sealed(tok([]byte("other"), vecSigned, cmstest.TokenOptions{}, tsa)), c, okSig, capsule.VerdictSealInvalid) type req struct {
add("seal: the authority had expired at its time", sealed(tok(subject[:], certTo.AddDate(1, 0, 0), cmstest.TokenOptions{}, tsa)), c, okSig, capsule.VerdictSealInvalid) h [32]byte
add("seal: a TSTInfo of version 2", sealed(tok(subject[:], vecSigned, cmstest.TokenOptions{Version: 2}, tsa)), c, okSig, capsule.VerdictSealUnreadable) w *want
add("seal: not DER", sealed([]byte("not DER")), c, okSig, capsule.VerdictSealUnreadable) s vsigner
add("seal: SHA-384 in the imprint", sealed(tok(subject[:], vecSigned, cmstest.TokenOptions{Hash: crypto.SHA384}, tsa)), c, okSig, capsule.VerdictSealUnsupported) }
add("seal: without a context, as a reader of v0.10", sealed(tok(subject[:], vecSigned, cmstest.TokenOptions{}, tsa)), nil, capsule.VerdictSignatureUnchecked, capsule.VerdictSealUnsupported) var reqs []req
noSig := capsule.SealSubject(c.ControlCommit, c.HeadDigest, capsule.SigPart(nil)) for i := range c.signers {
add("seal: over a capsule without a signature", must(capsule.EncodeSecurityWith(nil, must(capsule.EncodeSeal(capsule.SealTypeRFC3161, tok(noSig[:], vecSigned, cmstest.TokenOptions{}, tsa))))), reqs = append(reqs, req{c.signers[i].s.hash(), &c.signers[i], c.signers[i].s})
c, capsule.VerdictNoSignature, capsule.VerdictSealed) }
if err := errors.Join(errs...); err != nil { 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)
}
// 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 := "no antes de la fecha de apertura"
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)
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 nil, err
} }
return f, nil 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)}}})
// 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)}}})
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{}, 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})
}
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",
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, 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")))))
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}}, 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)
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, cmstest.TokenOptions{}, 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, cmstest.TokenOptions{}, tsa.Signer)))),
sig: capsule.VerdictSignatureUnchecked, seal: capsule.VerdictSealed, sealTSA: tsa, sealTime: vecSigned})
}
func sha256Of(b []byte) []byte {
h := sha256.Sum256(b)
return h[:]
} }
// locatorVectors makes the vector of the extension datekeys.capsule: a .dkc of // locatorVectors makes the vector of the extension datekeys.capsule: a .dkc of

File diff suppressed because one or more lines are too long
Loading…
Cancel
Save

Powered by TurnKey Linux.