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

788 lines
48 KiB

package main
import (
"bytes"
"crypto"
"crypto/elliptic"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
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>
7 days ago
"math/big"
"os"
"path/filepath"
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>
7 days ago
"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 {
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>
7 days ago
for _, v := range []struct {
name string
gen func() (any, error)
}{
{"security_cms.json", securityCMSVectors},
{"locator.json", locatorVectors},
} {
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>
7 days ago
path := filepath.Join(dir, v.name)
if _, err := os.Stat(path); err == nil {
continue
}
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>
7 days ago
out, err := v.gen()
if err != nil {
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>
7 days ago
return fmt.Errorf("%s: %w", v.name, err)
}
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>
7 days ago
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.12, with the profile of the
// certificate of §29.10 and the texts of §29.7.
const cmsVectorSpec = testkit.SpecVersion
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>
7 days ago
func hex32(b [32]byte) string { return hex.EncodeToString(b[:]) }
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>
7 days ago
// The texts of the verdicts, copied from the table of spec v0.12 §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 = "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",
}
// 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 t plus the accuracy is before
// round_time.
type want struct {
s vsigner
result string
tsa vsigner
t time.Time
before bool
}
// 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),
// 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
}
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 {
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>
7 days ago
hashes = append(hashes, s.hash())
}
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>
7 days ago
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
}
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>
7 days ago
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)
}
}
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>
7 days ago
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))
}
}
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>
7 days ago
g.file.Cases = append(g.file.Cases, rec)
}
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>
7 days ago
// 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}
if !l.SealTime.IsZero() {
r.SealTime = l.SealTime.UTC().Format(time.RFC3339Nano)
}
out = append(out, r)
}
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>
7 days ago
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
}
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>
7 days ago
return r
}
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>
7 days ago
// 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})
}
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>
7 days ago
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 {
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>
7 days ago
signers = append(signers, r.w.record())
}
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>
7 days ago
}
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
}
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>
7 days ago
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+".")
}
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>
7 days ago
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.")
}
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>
7 days ago
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
}
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>
7 days ago
// 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
}
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>
7 days ago
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")
}
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>
7 days ago
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}})
{
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>
7 days ago
// §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))
}
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>
7 days ago
// 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[:]
}

Powered by TurnKey Linux.