v0.16: a seal without accuracy proves nothing before the opening date

A valid seal is S4 only when its token carries accuracy and t plus the
accuracy is before round_time; otherwise S5, whose text gives the reason,
the first that holds: sealed after or too close, no accuracy under the BTSP
policy of ETSI EN 319 421 (0.4.0.2023.1.1), or no accuracy (spec v0.16,
29.7, 29.11). The line of a signer of F6 whose seal does not prove it says
so with the same reason. cms.Token gains HasAccuracy, Policy and BTSP;
Verdicts gain SealReason and SignerLine.Reason; EncryptFiles returns the
verdicts of the area it wrote in Result.Security, so that a writer warns of
a seal without accuracy (rule 19).

security_cms.json is made again: 143 cases, the seals about something else
with an accuracy of a second, and the new cases of 64 with seal_reason.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main
dev 7 hours ago
parent 4c7a48dc77
commit 7e3b8104a6

@ -56,15 +56,16 @@ func TestCMSVectors(t *testing.T) {
t.Fatalf("verdicts %s and %s, want %s and %s", v.Signature, v.Seal, c.Signature, c.Seal)
}
var signers, foreign []testkit.FixtureSignerResult
var holder, when string
var holder, when, reason string
if d := v.Detail; d != nil {
signers, foreign = vectorSignerResults(d.Signers), vectorSignerResults(d.Foreign)
if !d.SealTime.IsZero() {
holder, when = d.SealHolder, d.SealTime.UTC().Format(time.RFC3339Nano)
}
reason = string(d.SealReason)
}
if !reflect.DeepEqual(signers, c.Signers) || !reflect.DeepEqual(foreign, c.Foreign) || holder != c.SealHolder || when != c.SealTime {
t.Errorf("signers %+v foreign %+v seal %q %q; want %+v %+v %q %q", signers, foreign, holder, when, c.Signers, c.Foreign, c.SealHolder, c.SealTime)
if !reflect.DeepEqual(signers, c.Signers) || !reflect.DeepEqual(foreign, c.Foreign) || holder != c.SealHolder || when != c.SealTime || reason != c.SealReason {
t.Errorf("signers %+v foreign %+v seal %q %q %q; want %+v %+v %q %q %q", signers, foreign, holder, when, reason, c.Signers, c.Foreign, c.SealHolder, c.SealTime, c.SealReason)
}
if lines := v.Lines(); !slices.Equal(lines, c.Lines) {
t.Errorf("lines %q, want %q", lines, c.Lines)
@ -97,7 +98,7 @@ func TestCMSVectors(t *testing.T) {
func vectorSignerResults(lines []capsule.SignerLine) []testkit.FixtureSignerResult {
var out []testkit.FixtureSignerResult
for _, l := range lines {
r := testkit.FixtureSignerResult{Holder: l.Holder, Issuer: l.Issuer, Result: l.Result, Before: l.Before}
r := testkit.FixtureSignerResult{Holder: l.Holder, Issuer: l.Issuer, Result: l.Result, Before: l.Before, SealReason: string(l.Reason)}
if !l.SealTime.IsZero() {
r.SealTime = l.SealTime.UTC().Format(time.RFC3339Nano)
}

@ -429,7 +429,7 @@ func checkSignatureRecord(t *testing.T, s *testkit.FixtureSignature, security []
func signerResults(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}
r := testkit.FixtureSignerResult{Holder: l.Holder, Issuer: l.Issuer, Result: l.Result, Before: l.Before, SealReason: string(l.Reason)}
if !l.SealTime.IsZero() {
r.SealTime = l.SealTime.UTC().Format(time.RFC3339)
}

@ -135,6 +135,13 @@ type Result struct {
// PortableKey is the .dkk generated when NewPortableKey is set. Encode it
// with accesskey.Encode and treat it as a sensitive capability.
PortableKey *accesskey.AccessKey
// Security are the verdicts of the security area that EncryptFiles
// wrote, as a reader of this capsule finds them; zero for Encrypt. A
// valid seal whose reason is not ReasonNone, S5 or the line of a signer
// of F6, will not prove that it came before the opening date: the writer
// warns of it, and offers to ask another authority (spec v0.16, §62.1
// rule 19).
Security Verdicts
}
// Encrypt writes a format 2 .dkc for the content read from src (spec §61 and
@ -188,6 +195,8 @@ type sealer struct {
unlock time.Time
credentials []age.Recipient
portable *age.X25519Identity
// verdicts are those of the security area that security wrote last.
verdicts Verdicts
}
// newSealer validates the options that do not depend on the content, with

@ -188,7 +188,7 @@ func EncryptFiles(dst io.Writer, sources []Source, opts EncryptOptions) (*Result
if err != nil {
return nil, err
}
res.Head = h
res.Head, res.Security = h, s.verdicts
return res, nil
}
@ -204,9 +204,11 @@ func (s *sealer) security(c *Control, head []byte) ([]byte, error) {
sc := &SecurityContext{HeadDigest: HeadDigest(head), RoundTime: s.unlock}
if o.AuthorKey == nil && o.CMSSigner == nil && o.Sealer == nil {
security := EncodeSecurity()
if v := EvaluateSecurityIn(security, sc); v != (Verdicts{Signature: VerdictNoSignature, Seal: VerdictNoSeal}) {
v := EvaluateSecurityIn(security, sc)
if v != (Verdicts{Signature: VerdictNoSignature, Seal: VerdictNoSeal}) {
return nil, fmt.Errorf("capsule: self-check: the reader finds the verdicts %s and %s in this security area", v.Signature, v.Seal)
}
s.verdicts = v
return security, nil
}
var err error
@ -264,11 +266,14 @@ func (s *sealer) security(c *Control, head []byte) ([]byte, error) {
}
v := EvaluateSecurityIn(security, sc)
// A seal that proves nothing before the round time (S5) is still a seal
// that verifies: the writer's clock and the authority's may differ.
// that verifies: the writer's clock and the authority's may differ, or
// the token may carry no accuracy. Result.Security lets the caller warn
// of it (§62.1 rule 19).
sealOK := v.Seal == wantSeal || wantSeal == VerdictSealed && v.Seal == VerdictSealedLate
if v.Signature != wantSig || !sealOK || v.AuthorKey != key {
return nil, fmt.Errorf("capsule: self-check: the reader finds the verdicts %s and %s in this security area, not %s and %s%s", v.Signature, v.Seal, wantSig, wantSeal, detailText(v.Detail))
}
s.verdicts = v
return security, nil
}

@ -10,6 +10,7 @@ import (
"g.activething.com/go/DateKeys/codec"
"g.activething.com/go/DateKeys/codec/bech32"
"g.activething.com/go/DateKeys/extension"
"g.activething.com/go/DateKeys/internal/cms"
"g.activething.com/go/DateKeys/internal/pathrule"
)
@ -153,15 +154,64 @@ const (
VerdictSignedComplete Verdict = "F6"
// VerdictSealInvalid (S3): a seal that does not verify.
VerdictSealInvalid Verdict = "S3"
// VerdictSealed (S4): a valid seal with t + accuracy < round_time.
// VerdictSealed (S4): a valid seal with accuracy and t + accuracy <
// round_time (spec v0.16, §29.11).
VerdictSealed Verdict = "S4"
// VerdictSealedLate (S5): a valid seal without margin before round_time.
// VerdictSealedLate (S5): a valid seal that does not prove that it came
// before round_time. Detail.SealReason says why.
VerdictSealedLate Verdict = "S5"
)
// SealReason is why a valid seal does not prove that it came before the
// opening date (spec v0.16, §29.7): the reason of S5, and of the line of a
// signer of F6 that does not say «antes de la fecha de apertura».
type SealReason string
const (
// ReasonNone: the seal proves it (S4, or the line of a signer that says
// so).
ReasonNone SealReason = ""
// ReasonLate: t plus the accuracy, 0 without one, is not before
// round_time.
ReasonLate SealReason = "late"
// ReasonNoAccuracyBTSP: the token carries no accuracy, and its policy is
// the BTSP of ETSI EN 319 421, which requires it.
ReasonNoAccuracyBTSP SealReason = "no accuracy, BTSP"
// ReasonNoAccuracy: the token carries no accuracy.
ReasonNoAccuracy SealReason = "no accuracy"
)
// Text is the reason as the texts of §29.7 write it, "" for ReasonNone.
func (r SealReason) Text() string {
switch r {
case ReasonLate:
return "se selló después de esa fecha o demasiado cerca de ella"
case ReasonNoAccuracyBTSP:
return "el sello no dice la precisión que exige su política"
case ReasonNoAccuracy:
return "el sello no dice su precisión"
}
return ""
}
// sealReason is the reason of a token that verifies, the first that holds
// (spec v0.16, §29.7): late, then without accuracy under BTSP, then without
// accuracy; ReasonNone when it proves that it came before roundTime.
func sealReason(tok *cms.Token, roundTime time.Time) SealReason {
switch {
case roundTime.IsZero() || !tok.GenTime.Add(tok.Accuracy).Before(roundTime):
return ReasonLate
case !tok.HasAccuracy && tok.BTSP():
return ReasonNoAccuracyBTSP
case !tok.HasAccuracy:
return ReasonNoAccuracy
}
return ReasonNone
}
// Text returns the text of the verdict that the official SDK shows, in
// Spanish (spec §29.7), and "" for S0, which shows nothing, and for the
// verdicts whose text names a key, which Verdicts.Lines writes.
// verdicts whose text names a key or a reason, which Verdicts.Lines writes.
func (v Verdict) Text() string {
switch v {
case VerdictUnreadable:
@ -180,8 +230,6 @@ func (v Verdict) Text() string {
return "Faltan firmas o sellos que la propia cápsula exige: trátala como no firmada."
case VerdictSealInvalid:
return "El sello no corresponde a este contenido."
case VerdictSealedLate:
return "Sellado después de la fecha de apertura: no prueba nada anterior."
}
return ""
}
@ -204,9 +252,11 @@ type Detail struct {
// the SignerInfo of other certificates, which never count.
Signers, Foreign []SignerLine
// SealHolder and SealTime are the holder of the certificate of the
// authority of a valid seal, as §29.7 writes it, and t.
// authority of a valid seal, as §29.7 writes it, and t. SealReason is
// why it does not prove that it came before round_time (S5).
SealHolder string
SealTime time.Time
SealReason SealReason
}
// SignerLine is a signer of an alg 2 signature.
@ -222,10 +272,13 @@ type SignerLine struct {
Result string
// SealHolder is the holder of the certificate of the authority of its
// seal, and SealTime t, both zero without a seal that verifies. Before is
// true when t plus the accuracy of the seal is before round_time.
// true when the seal proves that it came before round_time: it carries
// accuracy and t plus the accuracy is before round_time (spec v0.16,
// §29.11); Reason says why not, for a valid signer.
SealHolder string
SealTime time.Time
Before bool
Reason SealReason
}
// resultText is the result of a signer in the texts of §29.7.
@ -295,7 +348,7 @@ func (v Verdicts) Lines() []string {
". DateKeys no comprueba quién lo emitió: para eso, exporta la firma a un validador oficial."
before := false
for _, s := range v.Detail.Signers {
when := "no antes de la fecha de apertura"
when := "sin acreditar que fuera antes de la fecha de apertura: " + s.Reason.Text()
if s.Before {
when, before = "antes de la fecha de apertura", true
}
@ -316,6 +369,8 @@ func (v Verdicts) Lines() []string {
case v.Seal == VerdictSealed && v.Detail != nil:
lines = append(lines, "Según un sello a nombre de "+quoted(v.Detail.SealHolder)+", existía el "+instant(v.Detail.SealTime)+
", antes de que la cápsula pudiera abrirse. DateKeys no comprueba quién emitió el sello.")
case v.Seal == VerdictSealedLate && v.Detail != nil:
lines = append(lines, "No acredita que se sellara antes de la fecha de apertura: "+v.Detail.SealReason.Text()+".")
case t != "":
lines = append(lines, t)
}

@ -194,7 +194,8 @@ func signerLine(s *cms.SignerInfo, msg []byte, roundTime time.Time) SignerLine {
return l
}
l.Result, l.SealTime, l.SealHolder = "valid", tok.GenTime, holderText(tok.TSA.Holder(), tok.TSA.Hash)
l.Before = !roundTime.IsZero() && tok.GenTime.Add(tok.Accuracy).Before(roundTime)
l.Reason = sealReason(tok, roundTime)
l.Before = l.Reason == ReasonNone
return l
}
@ -220,8 +221,9 @@ func evaluateSeal(v *Verdicts, s *seal, signature []byte, c *SecurityContext) {
v.Detail = &Detail{}
}
v.Detail.SealHolder, v.Detail.SealTime = holderText(tok.TSA.Holder(), tok.TSA.Hash), tok.GenTime
v.Detail.SealReason = sealReason(tok, c.RoundTime)
v.Seal = VerdictSealedLate
if !c.RoundTime.IsZero() && tok.GenTime.Add(tok.Accuracy).Before(c.RoundTime) {
if v.Detail.SealReason == ReasonNone {
v.Seal = VerdictSealed
}
}

@ -102,7 +102,9 @@ func TestEvaluateCMS(t *testing.T) {
// A seal after the round time proves nothing before it, and then no line
// warns of who issued it.
late := capsule.EvaluateSecurityIn(cmsArea(t, c, []cmstest.Signer{ana}, []cmstest.Signer{ana}, tsa, roundTime.Add(time.Hour), nil), c)
if late.Signature != capsule.VerdictSignedComplete || len(late.Lines()) != 2 || !strings.Contains(late.Lines()[1], "no antes de la fecha de apertura") {
if late.Signature != capsule.VerdictSignedComplete || len(late.Lines()) != 2 ||
!strings.HasSuffix(late.Lines()[1], ", sin acreditar que fuera antes de la fecha de apertura: se selló después de esa fecha o demasiado cerca de ella.") ||
late.Detail.Signers[0].Reason != capsule.ReasonLate {
t.Errorf("a late seal: %+v %q", late, late.Lines())
}
@ -289,7 +291,8 @@ func TestEvaluateSeal(t *testing.T) {
return a
}
v := capsule.EvaluateSecurityIn(area(cmstest.Token(subject[:], signedAt, cmstest.TokenOptions{}, tsa)), c)
second := cmstest.TokenOptions{Accuracy: time.Second}
v := capsule.EvaluateSecurityIn(area(cmstest.Token(subject[:], signedAt, second, tsa)), c)
if v.Signature != capsule.VerdictSignedOther || v.Seal != capsule.VerdictSealed || v.Detail == nil || v.Detail.SealHolder != "Autoridad de Sellado" {
t.Fatalf("a valid seal: %+v", v)
}
@ -298,11 +301,42 @@ func TestEvaluateSeal(t *testing.T) {
}
// Sealed with its own signature part: without key 2 the subject differs.
noSig := capsule.SealSubject(c.ControlCommit, c.HeadDigest, capsule.SigPart(nil))
a, _ := capsule.EncodeSecurityWith(nil, mustSeal(t, capsule.SealTypeRFC3161, cmstest.Token(noSig[:], signedAt, cmstest.TokenOptions{}, tsa)))
a, _ := capsule.EncodeSecurityWith(nil, mustSeal(t, capsule.SealTypeRFC3161, cmstest.Token(noSig[:], signedAt, second, tsa)))
if v := capsule.EvaluateSecurityIn(a, c); v.Signature != capsule.VerdictNoSignature || v.Seal != capsule.VerdictSealed {
t.Errorf("a seal without a signature: %+v", v)
}
// Spec v0.16, §29.7 and §29.11: a valid seal proves that it came before
// the round time only with accuracy; without it, S5 and its reason, the
// first that holds.
for name, tc := range map[string]struct {
o cmstest.TokenOptions
when time.Time
seal capsule.Verdict
reason capsule.SealReason
}{
"no accuracy, years before": {cmstest.TokenOptions{}, signedAt, capsule.VerdictSealedLate, capsule.ReasonNoAccuracy},
"no accuracy under BTSP": {cmstest.TokenOptions{Policy: cmstest.BTSPPolicy}, signedAt, capsule.VerdictSealedLate, capsule.ReasonNoAccuracyBTSP},
"no accuracy, after the round time": {cmstest.TokenOptions{Policy: cmstest.BTSPPolicy}, roundTime, capsule.VerdictSealedLate, capsule.ReasonLate},
"an accuracy of 0 seconds": {cmstest.TokenOptions{AccuracyRaw: cmstest.Seq(cmstest.Int(0))}, roundTime.Add(-time.Microsecond), capsule.VerdictSealed, capsule.ReasonNone},
"an empty accuracy": {cmstest.TokenOptions{AccuracyRaw: cmstest.Seq()}, signedAt, capsule.VerdictSealed, capsule.ReasonNone},
"BTSP with accuracy": {cmstest.TokenOptions{Policy: cmstest.BTSPPolicy, Accuracy: time.Second}, signedAt, capsule.VerdictSealed, capsule.ReasonNone},
"an accuracy of 0 at the round time": {cmstest.TokenOptions{AccuracyRaw: cmstest.Seq(cmstest.Int(0))}, roundTime, capsule.VerdictSealedLate, capsule.ReasonLate},
} {
v := capsule.EvaluateSecurityIn(area(cmstest.Token(subject[:], tc.when, tc.o, tsa)), c)
if v.Seal != tc.seal || v.Detail == nil || v.Detail.SealReason != tc.reason {
t.Errorf("%s: %+v", name, v)
continue
}
last := v.Lines()[len(v.Lines())-1]
if tc.seal == capsule.VerdictSealedLate && last != "No acredita que se sellara antes de la fecha de apertura: "+tc.reason.Text()+"." {
t.Errorf("%s: line %q", name, last)
}
}
if capsule.ReasonNone.Text() != "" || capsule.VerdictSealedLate.Text() != "" {
t.Error("S5 has no text of its own: Lines writes it with its reason")
}
for name, tc := range map[string]struct {
token []byte
ctx *capsule.SecurityContext

@ -216,14 +216,20 @@ func (c *cmsSigner) Sign(message []byte) ([]byte, error) {
return cmstest.Signature(message, opts, c.signers...), nil
}
// sealer is a Sealer that asks the authority tsa.
// sealer is a Sealer that asks the authority tsa, whose tokens carry an
// accuracy of a second unless noAccuracy.
type sealer struct {
tsa cmstest.Signer
when time.Time
tsa cmstest.Signer
when time.Time
noAccuracy bool
}
func (s sealer) Seal(subject [32]byte) ([]byte, error) {
return cmstest.Token(subject[:], s.when, cmstest.TokenOptions{}, s.tsa), nil
o := cmstest.TokenOptions{Accuracy: time.Second}
if s.noAccuracy {
o = cmstest.TokenOptions{}
}
return cmstest.Token(subject[:], s.when, o, s.tsa), nil
}
// Spec v0.11 §29.10, §29.11, §62.1 rules 19 and 21: EncryptFiles gives
@ -268,7 +274,7 @@ func TestEncryptFilesCMSAndSeal(t *testing.T) {
// A seal over the signature of an author key.
key, _ := authorkey.Generate()
opts.CMSSigner, opts.AuthorKey, opts.Sealer = nil, key, sealer{tsa, when}
opts.CMSSigner, opts.AuthorKey, opts.Sealer = nil, key, sealer{tsa: tsa, when: when}
dkc.Reset()
if _, err := capsule.EncryptFiles(&dkc, []capsule.Source{source("nota.txt", "Hola.\n")}, opts); err != nil {
t.Fatal(err)
@ -280,12 +286,25 @@ func TestEncryptFilesCMSAndSeal(t *testing.T) {
// And a seal over a capsule without a signature.
opts.AuthorKey = nil
dkc.Reset()
if _, err := capsule.EncryptFiles(&dkc, []capsule.Source{source("nota.txt", "Hola.\n")}, opts); err != nil {
res, err := capsule.EncryptFiles(&dkc, []capsule.Source{source("nota.txt", "Hola.\n")}, opts)
if err != nil {
t.Fatal(err)
}
if o = openSigned(t, dkc.Bytes(), nil); o.Verdicts.Signature != capsule.VerdictNoSignature || o.Verdicts.Seal != capsule.VerdictSealed {
t.Errorf("verdicts %+v", o.Verdicts)
if o = openSigned(t, dkc.Bytes(), nil); o.Verdicts.Signature != capsule.VerdictNoSignature || o.Verdicts.Seal != capsule.VerdictSealed || res.Security.Seal != capsule.VerdictSealed {
t.Errorf("verdicts %+v, written %+v", o.Verdicts, res.Security)
}
// A seal without accuracy is written, and Result.Security says that it
// proves nothing before the opening date, so that the writer warns of it
// (spec v0.16, §62.1 rule 19).
opts.Sealer = sealer{tsa: tsa, when: when, noAccuracy: true}
dkc.Reset()
if res, err = capsule.EncryptFiles(&dkc, []capsule.Source{source("nota.txt", "Hola.\n")}, opts); err != nil {
t.Fatal(err)
}
if res.Security.Seal != capsule.VerdictSealedLate || res.Security.Detail.SealReason != capsule.ReasonNoAccuracy {
t.Errorf("a seal without accuracy: written %+v", res.Security)
}
opts.Sealer = sealer{tsa: tsa, when: when}
// The exclusions.
opts.AuthorKey, opts.CMSSigner = key, signer

@ -86,6 +86,9 @@ var (
oidSigTimeStamp = oid("1.2.840.113549.1.9.16.2.14")
oidTSTInfo = oid("1.2.840.113549.1.9.16.1.4")
oidRIOCSP = oid("1.3.6.1.5.5.7.16.2")
// oidBTSP is the best practices time-stamp policy of ETSI EN 319 421,
// whose tokens carry accuracy (spec v0.16, §29.11).
oidBTSP = oid("0.4.0.2023.1.1")
oidSHA256 = oid("2.16.840.1.101.3.4.2.1")
oidSHA384 = oid("2.16.840.1.101.3.4.2.2")

@ -595,6 +595,9 @@ type TokenOptions struct {
AccuracyRaw []byte
// Version is the version of the TSTInfo, 1 by default.
Version int
// Policy is the policy of the TSTInfo, 1.2.3.4 by default; BTSPPolicy
// is that of ETSI EN 319 421.
Policy asn1.ObjectIdentifier
// Imprint, when not nil, is written as the hashed message instead of the
// hash of the subject, of any length.
Imprint []byte
@ -619,6 +622,10 @@ type TokenOptions struct {
CMS Options
}
// BTSPPolicy is the best practices time-stamp policy of ETSI EN 319 421,
// 0.4.0.2023.1.1, whose tokens carry accuracy.
var BTSPPolicy = asn1.ObjectIdentifier{0, 4, 0, 2023, 1, 1}
// AccuracyOf is the Accuracy element of d: its seconds, millis and micros,
// each only when it is not zero.
func AccuracyOf(d time.Duration) []byte {
@ -651,7 +658,10 @@ func TSTInfo(subject []byte, genTime time.Time, o TokenOptions) []byte {
if o.GenTimeRaw != nil {
gt = o.GenTimeRaw
}
info := [][]byte{Int(int64(o.Version)), OID(asn1.ObjectIdentifier{1, 2, 3, 4}), Seq(HashAlg(o.Hash), Octets(imprint)), Int(42), gt}
if o.Policy == nil {
o.Policy = asn1.ObjectIdentifier{1, 2, 3, 4}
}
info := [][]byte{Int(int64(o.Version)), OID(o.Policy), Seq(HashAlg(o.Hash), Octets(imprint)), Int(42), gt}
switch {
case o.AccuracyRaw != nil:
info = append(info, o.AccuracyRaw)

@ -264,10 +264,15 @@ func hashBytes(h crypto.Hash, b []byte) []byte {
// Token is a time-stamp token of RFC 3161 read with the profile of spec
// §29.11.
type Token struct {
// GenTime is t, and Accuracy the precision of the token, zero when it
// has none.
GenTime time.Time
Accuracy time.Duration
// GenTime is t, and Accuracy the precision of the token, zero in the
// fields it does not carry. HasAccuracy reports whether it carries the
// field at all: without it, the token does not say its precision (spec
// v0.16, §29.11).
GenTime time.Time
Accuracy time.Duration
HasAccuracy bool
// Policy is the content of the object identifier of its policy.
Policy []byte
// ImprintAlg is the hash of the messageImprint, and Imprint the hash.
ImprintAlg algID
Imprint []byte
@ -344,12 +349,17 @@ func parseTSTInfo(b []byte) (*Token, []byte, []byte, error) {
if err != nil {
return bad("genTime: " + err.Error())
}
t := &Token{GenTime: gen}
policy, err := der.Content(f[1])
if err != nil {
return bad("policy")
}
t := &Token{GenTime: gen, Policy: policy}
rest := f[5:]
if len(rest) > 0 && rest[0][0] == 0x30 {
if t.Accuracy, err = parseAccuracy(rest[0]); err != nil {
return bad(err.Error())
}
t.HasAccuracy = true
rest = rest[1:]
}
if len(rest) > 0 && rest[0][0] == 0x01 {
@ -421,6 +431,12 @@ func parseAccuracy(b []byte) (time.Duration, error) {
// seal of seal_type 2 requires (spec §29.11).
func (t *Token) ImprintIsSHA256() bool { return bytes.Equal(t.ImprintAlg.OID, oidSHA256) }
// BTSP reports whether the policy of the token is the best practices
// time-stamp policy of ETSI EN 319 421 (0.4.0.2023.1.1), compared by the
// bytes of its DER, which requires accuracy in every token (spec v0.16,
// §29.11).
func (t *Token) BTSP() bool { return bytes.Equal(t.Policy, oidBTSP) }
// Check verifies the token over subject, the bytes that it seals: the
// message-digest is the hash of the TSTInfo, the signature of the TSA
// verifies, the messageImprint is the hash of subject, of any length, and the

@ -3,7 +3,7 @@ package testkit
// CMSVectorFile is testdata/vectors/security_cms.json: SECURITY_CBOR areas
// with an author signature of alg 2 (CMS with certificates) or a time seal
// of seal_type 2 (RFC 3161), each with the context of its capsule and the
// verdicts, the results and the lines that spec v0.12 §29.7, §29.10 and
// verdicts, the results and the lines that spec v0.16 §29.7, §29.10 and
// §29.11 give. The certificates and the tokens are made once, with test
// keys, and the file is frozen: a second implementation reads them and must
// reach the same verdicts and write the same lines, byte for byte.
@ -36,6 +36,9 @@ type CMSVectorCase struct {
Foreign []FixtureSignerResult `json:"foreign_signers,omitempty"`
SealHolder string `json:"seal_holder,omitempty"`
SealTime string `json:"seal_time,omitempty"`
// SealReason is the reason of S5 (spec v0.16, §29.7), as
// FixtureSignerResult writes it.
SealReason string `json:"seal_reason,omitempty"`
// Lines are the verdicts as the official SDK shows them (§29.7):
// Verdicts.Lines.
Lines []string `json:"lines"`

@ -157,6 +157,10 @@ type FixtureSignerResult struct {
Result string `json:"result"`
SealTime string `json:"seal_time,omitempty"`
Before bool `json:"before_round_time"`
// SealReason is why a valid seal does not prove that it came before the
// round time (spec v0.16, §29.7): "late", "no accuracy" or "no
// accuracy, BTSP"; empty when it does.
SealReason string `json:"seal_reason,omitempty"`
}
// FixtureSeal describes the seal of seal_type 2 of a format 3 fixture:

@ -55,13 +55,13 @@ var (
)
// 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.
// SpecVersion. Its verdicts are those of v0.16, with the profile of the
// certificate of §29.10, the texts of §29.7 and the accuracy of §29.11.
const cmsVectorSpec = testkit.SpecVersion
func hex32(b [32]byte) string { return hex.EncodeToString(b[:]) }
// The texts of the verdicts, copied from the table of spec v0.12 §29.7: the
// The texts of the verdicts, copied from the table of spec v0.16 §29.7: the
// lines of each case are checked against them, not against Verdicts.Lines.
const (
textF0 = "Sin firma de autor."
@ -71,9 +71,16 @@ const (
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."
textS5 = "No acredita que se sellara antes de la fecha de apertura: "
)
// reasonTexts are the reasons of S5 in the texts of §29.7 (v0.16).
var reasonTexts = map[capsule.SealReason]string{
capsule.ReasonLate: "se selló después de esa fecha o demasiado cerca de ella",
capsule.ReasonNoAccuracyBTSP: "el sello no dice la precisión que exige su política",
capsule.ReasonNoAccuracy: "el sello no dice su precisión",
}
// resultTexts are the results of a signer in the lines of §29.7.
var resultTexts = map[string]string{
"valid": "válida", "invalid": "inválida", "not verifiable": "no verificable", "without seal": "sin sello",
@ -100,14 +107,15 @@ func hashOfCert(s cmstest.Signer) string { return hex32(sha256.Sum256(s.Cert.R
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.
// authority of its seal, t, and whether its seal proves that it came before
// round_time, or why not (§29.7, §29.11).
type want struct {
s vsigner
result string
tsa vsigner
t time.Time
before bool
reason capsule.SealReason
}
// vcase is a case of security_cms.json with what spec v0.12 gives for it.
@ -122,10 +130,12 @@ type vcase struct {
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
// sealReason the reason of S5, and authorKey the key of a valid signature
// of alg 1 (F4).
sealTSA vsigner
sealTime time.Time
sealReason capsule.SealReason
authorKey string
}
// cmsGen builds the cases over a common context and checks each against
@ -211,6 +221,7 @@ func (g *cmsGen) add(c vcase) {
if !d.SealTime.IsZero() {
rec.SealHolder, rec.SealTime = d.SealHolder, d.SealTime.UTC().Format(time.RFC3339Nano)
}
rec.SealReason = string(d.SealReason)
}
if err := c.check(v, rec); err != nil {
g.errs = append(g.errs, fmt.Errorf("%s: %w", c.name, err))
@ -228,7 +239,7 @@ func (g *cmsGen) add(c vcase) {
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}
r := testkit.FixtureSignerResult{Holder: l.Holder, Issuer: l.Issuer, Result: l.Result, Before: l.Before, SealReason: string(l.Reason)}
if !l.SealTime.IsZero() {
r.SealTime = l.SealTime.UTC().Format(time.RFC3339Nano)
}
@ -241,6 +252,9 @@ func (w want) record() testkit.FixtureSignerResult {
r := testkit.FixtureSignerResult{Holder: w.s.holder, Issuer: w.s.issuer, Result: w.result}
if w.result == "valid" {
r.SealTime, r.Before = w.t.UTC().Format(time.RFC3339Nano), w.before
if !w.before {
r.SealReason = string(w.reason)
}
}
return r
}
@ -288,6 +302,9 @@ func (c vcase) check(v capsule.Verdicts, rec testkit.CMSVectorCase) error {
if rec.SealHolder != sealHolder || rec.SealTime != sealTime {
return fmt.Errorf("the seal of %q at %s, want %q at %s", rec.SealHolder, rec.SealTime, sealHolder, sealTime)
}
if want := c.sealReason; c.seal != capsule.VerdictSealedLate && want != capsule.ReasonNone || rec.SealReason != string(want) {
return fmt.Errorf("the reason of the seal %q, want %q", rec.SealReason, want)
}
// The lines: the signature, the foreign signers apart, and the seal.
q := func(s string) string { return "«" + s + "»" }
at := func(t time.Time) string { return t.UTC().Format(time.RFC3339Nano) }
@ -308,7 +325,7 @@ func (c vcase) check(v capsule.Verdicts, rec testkit.CMSVectorCase) error {
before := false
for _, r := range reqs {
names = append(names, q(r.s.holder))
when := "no antes de la fecha de apertura"
when := "sin acreditar que fuera antes de la fecha de apertura: " + reasonTexts[r.w.reason]
if r.w.before {
when, before = "antes de la fecha de apertura", true
}
@ -334,7 +351,7 @@ func (c vcase) check(v capsule.Verdicts, rec testkit.CMSVectorCase) error {
case capsule.VerdictSealInvalid:
lines = append(lines, textS3)
case capsule.VerdictSealedLate:
lines = append(lines, textS5)
lines = append(lines, textS5+reasonTexts[c.sealReason]+".")
case capsule.VerdictSealed:
lines = append(lines, "Según un sello a nombre de "+q(c.sealTSA.holder)+", existía el "+at(c.sealTime)+", antes de que la cápsula pudiera abrirse. DateKeys no comprueba quién emitió el sello.")
default:
@ -405,11 +422,19 @@ func (g *cmsGen) signatureCases() {
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)}}})
sig: capsule.VerdictSignedComplete, seal: capsule.VerdictNoSeal, signers: []want{{s: ana, result: "valid", tsa: tsa, t: vecRound.Add(time.Hour), reason: capsule.ReasonLate}}})
// t + accuracy equal to round_time is not before it (§29.7).
edge := cmstest.Options{Token: sealedBy(tsa, vecRound.Add(-time.Second), cmstest.TokenOptions{Accuracy: time.Second})}
g.add(vcase{name: "alg 2: t plus the accuracy of the seal equals the round time: F6, not before the opening date", area: g.area(g.signed(only(ana), edge, ana), nil),
sig: capsule.VerdictSignedComplete, seal: capsule.VerdictNoSeal, signers: []want{{s: ana, result: "valid", tsa: tsa, t: vecRound.Add(-time.Second)}}})
sig: capsule.VerdictSignedComplete, seal: capsule.VerdictNoSeal, signers: []want{{s: ana, result: "valid", tsa: tsa, t: vecRound.Add(-time.Second), reason: capsule.ReasonLate}}})
// Spec v0.16, §29.7: a seal without accuracy does not prove that it came
// before the opening date, and its line gives the reason.
bare := cmstest.Options{Token: sealedBy(tsa, vecSigned, cmstest.TokenOptions{})}
g.add(vcase{name: "alg 2: a seal without accuracy: F6, not proven before the opening date", area: g.area(g.signed(only(ana), bare, ana), nil),
sig: capsule.VerdictSignedComplete, seal: capsule.VerdictNoSeal, signers: []want{{s: ana, result: "valid", tsa: tsa, t: vecSigned, reason: capsule.ReasonNoAccuracy}}})
btsp := cmstest.Options{Token: sealedBy(tsa, vecSigned, cmstest.TokenOptions{Policy: cmstest.BTSPPolicy})}
g.add(vcase{name: "alg 2: a seal of the BTSP policy without accuracy: F6, not proven before the opening date, by its policy", area: g.area(g.signed(only(ana), btsp, ana), nil),
sig: capsule.VerdictSignedComplete, seal: capsule.VerdictNoSeal, signers: []want{{s: ana, result: "valid", tsa: tsa, t: vecSigned, reason: capsule.ReasonNoAccuracyBTSP}}})
g.add(vcase{name: "alg 2: a signer who is not required shows apart and does not count: F6", area: g.area(g.signed(only(ana), sealed, ana, otro), nil),
sig: capsule.VerdictSignedComplete, seal: capsule.VerdictNoSeal, signers: []want{valid(ana, tsa)}, foreign: []want{valid(otro, tsa)}})
g.add(vcase{name: "alg 2: a required signer is absent: F5", area: g.area(g.signed(both, sealed, ana), nil),
@ -465,7 +490,7 @@ func (g *cmsGen) signatureCases() {
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)))),
g.add(vcase{name: "alg 2: a valid seal of seal_type 2 in key 3 beside it: F5 and S4", area: g.area(key2, g.must(capsule.EncodeSeal(capsule.SealTypeRFC3161, cmstest.Token(subject[:], vecSigned, cmstest.TokenOptions{Accuracy: time.Second}, tsa.Signer)))),
sig: capsule.VerdictSignedIncomplete, seal: capsule.VerdictSealed, signers: []want{valid(ana, tsa)}, sealTSA: tsa, sealTime: vecSigned})
other := *g.ctx
other.HeadDigest[5] ^= 9
@ -703,28 +728,41 @@ func (g *cmsGen) sealCases() {
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)
late := func(name string, token []byte, s vsigner, t time.Time, reason capsule.SealReason) {
g.add(vcase{name: name, area: sealArea(token), sig: capsule.VerdictSignedOther, seal: capsule.VerdictSealedLate, authorKey: pub, sealTSA: s, sealTime: t, sealReason: reason})
}
// The tokens of the cases about something else carry an accuracy of a
// second: without it, a valid seal proves nothing before the round time
// (spec v0.16, §29.11).
second := cmstest.TokenOptions{Accuracy: time.Second}
sealedAt("seal: before the round time: S4", tok(vecSigned, second, tsa), tsa, vecSigned, capsule.VerdictSealed)
late("seal: after the round time, without accuracy: S5, sealed after", tok(vecRound.Add(time.Minute), cmstest.TokenOptions{}, tsa), tsa, vecRound.Add(time.Minute), capsule.ReasonLate)
early := vecRound.Add(-time.Second)
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)
late("seal: t plus the accuracy past the round time: S5", tok(early, cmstest.TokenOptions{Accuracy: 2 * time.Second}, tsa), tsa, early, capsule.ReasonLate)
late("seal: t plus the accuracy equal to the round time: S5", tok(early, cmstest.TokenOptions{Accuracy: time.Second}, tsa), tsa, early, capsule.ReasonLate)
late("seal: without accuracy, years before the round time: S5, it does not say its precision", tok(vecSigned, cmstest.TokenOptions{}, tsa), tsa, vecSigned, capsule.ReasonNoAccuracy)
late("seal: of the BTSP policy of ETSI, without accuracy: S5, it does not say the precision its policy requires", tok(vecSigned, cmstest.TokenOptions{Policy: cmstest.BTSPPolicy}, tsa), tsa, vecSigned, capsule.ReasonNoAccuracyBTSP)
late("seal: of the BTSP policy, without accuracy, after the round time: S5, sealed after", tok(vecRound, cmstest.TokenOptions{Policy: cmstest.BTSPPolicy}, tsa), tsa, vecRound, capsule.ReasonLate)
sealedAt("seal: of the BTSP policy, with accuracy: S4", tok(vecSigned, cmstest.TokenOptions{Policy: cmstest.BTSPPolicy, Accuracy: time.Second}, tsa), tsa, vecSigned, capsule.VerdictSealed)
sealedAt("seal: an accuracy of 0 seconds, a microsecond before the round time: S4", tok(vecRound.Add(-time.Microsecond), cmstest.TokenOptions{AccuracyRaw: cmstest.Seq(cmstest.Int(0))}, tsa), tsa, vecRound.Add(-time.Microsecond), capsule.VerdictSealed)
sealedAt("seal: an empty accuracy, a precision of 0: S4", tok(vecSigned, cmstest.TokenOptions{AccuracyRaw: cmstest.Seq()}, tsa), tsa, vecSigned, capsule.VerdictSealed)
sealedAt("seal: t plus an accuracy of 999 ms and 999 µs, a microsecond before the round time: S4",
tok(early, cmstest.TokenOptions{Accuracy: 999*time.Millisecond + 999*time.Microsecond}, tsa), tsa, early, capsule.VerdictSealed)
sealedAt("seal: an accuracy of seconds, millis and micros: S4", tok(vecSigned, cmstest.TokenOptions{Accuracy: time.Second + 5*time.Millisecond + 7*time.Microsecond}, tsa), tsa, vecSigned, capsule.VerdictSealed)
fraction := vecSigned.Add(250 * time.Millisecond)
sealedAt("seal: a genTime with a fraction of a second: S4, t with its fraction", tok(fraction, 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)
sealedAt("seal: a genTime with a fraction of a second: S4, t with its fraction", tok(fraction, second, tsa), tsa, fraction, capsule.VerdictSealed)
sealedAt("seal: the certificate of the authority twice: S4", tok(vecSigned, cmstest.TokenOptions{TSATwice: true, Accuracy: time.Second}, tsa), tsa, vecSigned, capsule.VerdictSealed)
sealedAt("seal: a CRL in the token decides nothing: S4", tok(vecSigned, cmstest.TokenOptions{CRL: cmstest.Seq(cmstest.Seq(cmstest.Int(1)), cmstest.Seq(cmstest.OID(cmstest.OIDECDSA256)), cmstest.BitString([]byte{0})), Accuracy: time.Second}, tsa), tsa, vecSigned, capsule.VerdictSealed)
sealedAt("seal: signing-certificate-v2 in the token: S4", tok(vecSigned, cmstest.TokenOptions{SigCertV2: true, Accuracy: time.Second}, tsa), tsa, vecSigned, capsule.VerdictSealed)
tsaName := cmstest.TLV(0xa0, cmstest.TLV(0xa4, cmstest.Name(cmstest.ATV(cmstest.OIDCommonName, cmstest.UTF8("Autoridad de Sellado de prueba")))))
exts := cmstest.TLV(0xa1, cmstest.Extension([]int{1, 2, 3, 4}, false, cmstest.Null()))
sealedAt("seal: ordering TRUE, a nonce, a tsa and extensions: S4", tok(vecSigned, cmstest.TokenOptions{After: [][]byte{cmstest.Bool(true), cmstest.Int(99), tsaName, exts}}, tsa), tsa, vecSigned, capsule.VerdictSealed)
sealedAt("seal: ordering TRUE, a nonce, a tsa and extensions: S4", tok(vecSigned, cmstest.TokenOptions{After: [][]byte{cmstest.Bool(true), cmstest.Int(99), tsaName, exts}, Accuracy: time.Second}, tsa), tsa, vecSigned, capsule.VerdictSealed)
rsaTSA := named(cmstest.NewRSA("Autoridad RSA de prueba", 2048, certFrom, certTo), "Autoridad RSA de prueba")
sealedAt("seal: an authority of RSA with RSASSA-PSS and SHA-512: S4", tok(vecSigned, cmstest.TokenOptions{CMS: cmstest.Options{PSS: true, Hash: crypto.SHA512}}, rsaTSA), rsaTSA, vecSigned, capsule.VerdictSealed)
sealedAt("seal: an authority of RSA with RSASSA-PSS and SHA-512: S4", tok(vecSigned, cmstest.TokenOptions{CMS: cmstest.Options{PSS: true, Hash: crypto.SHA512}, Accuracy: time.Second}, rsaTSA), rsaTSA, vecSigned, capsule.VerdictSealed)
// The case of §76, change 1: a name that lines up a text of its own.
spaced := cmstest.NewECDSA("TSA"+strings.Repeat(" ", 50)+"Firmado con la clave que guardaste como Banco", elliptic.P256(), certFrom, certTo)
vspaced := vsigner{spaced, hashOfCert(spaced), ""}
sealedAt("seal: an authority named with 50 spaces and the text of F3: S4, by its SHA-256", tok(vecSigned, cmstest.TokenOptions{}, vspaced), vspaced, vecSigned, capsule.VerdictSealed)
sealedAt("seal: an authority named with 50 spaces and the text of F3: S4, by its SHA-256", tok(vecSigned, second, vspaced), vspaced, vecSigned, capsule.VerdictSealed)
// S3: it reads, and does not verify (§29.11, step 3).
seal("seal: over another subject: S3", cmstest.Token([]byte("other"), vecSigned, cmstest.TokenOptions{}, tsa.Signer), capsule.VerdictSealInvalid)
@ -773,11 +811,11 @@ func (g *cmsGen) sealCases() {
// 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)))),
g.add(vcase{name: "seal: over a capsule without a signature: F0 and S4", area: g.area(nil, g.must(capsule.EncodeSeal(capsule.SealTypeRFC3161, cmstest.Token(noSig[:], vecSigned, second, tsa.Signer)))),
sig: capsule.VerdictNoSignature, seal: capsule.VerdictSealed, sealTSA: tsa, sealTime: vecSigned})
unknown := g.must(capsule.EncodeAuthorSignature(capsule.AlgTest, []byte{1}, []byte{1}))
beside := capsule.SealSubject(g.ctx.ControlCommit, g.ctx.HeadDigest, capsule.SigPart(unknown))
g.add(vcase{name: "seal: beside a signature of alg 4294967295, which it seals all the same: F1 and S4", area: g.area(unknown, g.must(capsule.EncodeSeal(capsule.SealTypeRFC3161, cmstest.Token(beside[:], vecSigned, cmstest.TokenOptions{}, tsa.Signer)))),
g.add(vcase{name: "seal: beside a signature of alg 4294967295, which it seals all the same: F1 and S4", area: g.area(unknown, g.must(capsule.EncodeSeal(capsule.SealTypeRFC3161, cmstest.Token(beside[:], vecSigned, second, tsa.Signer)))),
sig: capsule.VerdictSignatureUnchecked, seal: capsule.VerdictSealed, sealTSA: tsa, sealTime: vecSigned})
}

@ -189,7 +189,7 @@ func commitmentsOf(f *testkit.DKCFixture, head []byte) (cc, hd [32]byte, err err
func signerResults(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}
r := testkit.FixtureSignerResult{Holder: l.Holder, Issuer: l.Issuer, Result: l.Result, Before: l.Before, SealReason: string(l.Reason)}
if !l.SealTime.IsZero() {
r.SealTime = l.SealTime.UTC().Format(time.RFC3339)
}

21
testdata/README.md vendored

@ -598,9 +598,11 @@ in `security_cms.json`.
## `vectors/security_cms.json`
Security areas with an author signature of `alg` 2, a CMS signature with
certificates, or a time seal of `seal_type` 2, an RFC 3161 token: 135 cases,
certificates, or a time seal of `seal_type` 2, an RFC 3161 token: 143 cases,
each with the context of its capsule, the verdicts, the result of each signer
and the lines of v0.12, §29.7, §29.10 and §29.11. They complete
and the lines of v0.16, §29.7, §29.10 and §29.11. Made again for v0.16, when a
seal without `accuracy` stopped proving that it came before the round time:
the cases about something else carry an accuracy of a second. They complete
`security.json`, whose areas have no valid signature or seal of these kinds.
The file is frozen: the certificates and the tokens are made once, with test
keys, so a second implementation reads them and must reach the same verdicts
@ -621,10 +623,13 @@ and write the same lines. Delete the file to make it again.
count. Each has the `holder` and the `issuer` as §29.7 shows them, its
`result` (`valid`, `invalid`, `absent`, `without seal`, `invalid seal`,
`out of validity` or `not verifiable`), the `seal_time` of its CAdES-T when
it has one, and `before_round_time`, whether that time plus its accuracy
precedes the round time.
it has one, and `before_round_time`, whether its seal proves that it came
before the round time: it carries `accuracy` and that time plus its accuracy
precedes the round time (v0.16). When it does not, `seal_reason` says why:
`late`, `no accuracy`, or `no accuracy, BTSP` for a token of the ETSI
policy 0.4.0.2023.1.1, which requires it; the first that holds.
- `seal_holder` and `seal_time`: the authority and the time of a valid seal
of key 3.
of key 3, and `seal_reason` the reason of S5, as for a signer.
- `lines`: the verdicts as the official SDK shows them (§29.7), byte for byte:
the names between « and », the line of each signer with its authority, the
warning that DateKeys does not check who issued the seals, and the times in
@ -644,7 +649,11 @@ table, RSASSA-PSS with and without `trailerField`, an attribute with an arc of
each string type and against each rule, `givenName` and `surname` before a
`commonName` with its NIF included; and, over an `alg` 1 signature, the seals
S1 to S5 at the edges of the token: its accuracy, its `genTime`, `ordering`,
a field after the last, the imprint, `crls` and the authority.
a field after the last, the imprint, `crls` and the authority. For v0.16: a
token without `accuracy` years before the round time and after it, one of the
BTSP policy without it and with it, an `accuracy` of 0 seconds and an empty
one, which are a precision of 0, and a signer of `alg` 2 whose seal carries
none, also under BTSP.
## `vectors/locator.json`

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

Powered by TurnKey Linux.