Spec v0.12 draft and the verdicts of a certificate (not approved)

The draft v0.12 fixes what the review of the implementation of v0.11
found, without changing any format: the names of certificates in the
verdicts, the seal of each signer in the lines of F6 with the warning that
nobody checks who issued it, the holder by givenName and surname before
the commonName that carries the NIF, a profile of the certificate field by
field, identifiers by their bytes, repeated elements of a SET OF, the
edge cases of the token, the addresses and the padding of the locator, and
the errata of 44.1, 55.2, 64, 67 and 76. Section 76 lists each change with
its case. The CDDL fixes the sizes of the locator.

The reader shows the names of certificates between quotes, refuses one of
more than 64 code points or with two spaces in a row, names the authority
of each seal of F6 and adds the warning when a line says before the date,
and writes the result of a foreign signer in Spanish. The records of
format3_signed_cms and format3_sealed follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v0.12
dev 6 days ago
parent 839e1173e0
commit 840c87e751

@ -213,19 +213,40 @@ type Detail struct {
type SignerLine struct {
// Holder is the name of the certificate as §29.7 shows it: the subject,
// or the SHA-256 of the certificate in hexadecimal when it does not meet
// the rules of the declared author.
// the rules of a name of a certificate.
Holder string
// Issuer is the issuer that the certificate says.
// Issuer is the issuer that the certificate says, with the same rules.
Issuer string
// Result is "valid", "invalid", "absent", "not verifiable", "without
// seal", "invalid seal" or "out of validity".
Result string
// SealTime is t, zero without a seal that verifies. Before is true when t
// plus the accuracy of the seal is before round_time.
SealTime time.Time
Before bool
// 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.
SealHolder string
SealTime time.Time
Before bool
}
// resultText is the result of a signer in the texts of §29.7.
var resultText = map[string]string{
"valid": "válida",
"invalid": "inválida",
"absent": "ausente",
"not verifiable": "no verificable",
"without seal": "sin sello",
"invalid seal": "con el sello inválido",
"out of validity": "con el certificado fuera de validez",
}
// quoted puts a name of a certificate between « and », as the texts of §29.7
// write it, so that where it starts and where it ends is in view.
func quoted(name string) string { return "«" + name + "»" }
// instant writes t in UTC as §29.7 shows it: RFC 3339, with the fraction of
// the seal when it has one.
func instant(t time.Time) string { return t.UTC().Format(time.RFC3339Nano) }
// SealedAt returns the earliest instant that a valid seal gives, the seal of
// key 3 or that of a required signer of an alg 2 signature, and false when
// there is none (spec v0.11, §29.7). A reader shows an mtime later than it as
@ -268,26 +289,32 @@ func (v Verdicts) Lines() []string {
if v.Signature == VerdictSignedComplete && v.Detail != nil {
names := make([]string, len(v.Detail.Signers))
for i, s := range v.Detail.Signers {
names[i] = s.Holder
names[i] = quoted(s.Holder)
}
lines[0] = "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."
before := false
for _, s := range v.Detail.Signers {
when := "no antes de la fecha de apertura"
if s.Before {
when = "antes de la fecha de apertura"
when, before = "antes de la fecha de apertura", true
}
lines = append(lines, fmt.Sprintf(" %s (emisor según su certificado: %s), sellado el %s, %s.", s.Holder, s.Issuer, s.SealTime.UTC().Format(time.RFC3339), when))
lines = append(lines, fmt.Sprintf(" %s (emisor según su certificado: %s), sellado por %s el %s, %s.", quoted(s.Holder), quoted(s.Issuer), quoted(s.SealHolder), instant(s.SealTime), when))
}
// §29.7: whoever says that a capsule was signed before the date says
// that it does not check who issued the seal.
if before {
lines = append(lines, " DateKeys no comprueba quién emitió los sellos.")
}
}
if v.Detail != nil {
for _, s := range v.Detail.Foreign {
lines = append(lines, fmt.Sprintf(" Otro firmante, %s: %s. No cuenta.", s.Holder, s.Result))
lines = append(lines, fmt.Sprintf(" Otro firmante, %s: %s. No cuenta.", quoted(s.Holder), resultText[s.Result]))
}
}
switch t := v.Seal.Text(); {
case v.Seal == VerdictSealed && v.Detail != nil:
lines = append(lines, "Según un sello a nombre de "+v.Detail.SealHolder+", existía el "+v.Detail.SealTime.UTC().Format(time.RFC3339)+
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 t != "":
lines = append(lines, t)

@ -5,6 +5,7 @@ import (
"crypto/sha256"
"encoding/hex"
"errors"
"strings"
"time"
"unicode/utf8"
@ -85,10 +86,17 @@ func decodeSigners(b []byte) ([][32]byte, error) {
return out, nil
}
// holderText is how §29.7 shows a name: the name, when it meets the rules of
// the declared author, and the SHA-256 of the certificate otherwise.
// MaxNameLen is the most code points of a name of a certificate that §29.7
// shows, the upper bound of a commonName in X.520.
const MaxNameLen = 64
// holderText is how §29.7 shows a name of a certificate: the name, when it
// meets the rules of the declared author, has at most MaxNameLen code points
// and no two spaces in a row, and the SHA-256 given otherwise. A name cannot
// then line up, with spaces, a text of its own where a terminal breaks the
// line.
func holderText(name string, hash [32]byte) string {
if name != "" && utf8.ValidString(name) && len(name) <= MaxAuthorLen && pathrule.CheckAuthor(name) == nil {
if name != "" && utf8.ValidString(name) && utf8.RuneCountInString(name) <= MaxNameLen && !strings.Contains(name, " ") && pathrule.CheckAuthor(name) == nil {
return name
}
return hex.EncodeToString(hash[:])
@ -185,8 +193,8 @@ func signerLine(s *cms.SignerInfo, msg []byte, roundTime time.Time) SignerLine {
l.Result = "out of validity"
return l
}
l.Result, l.SealTime = "valid", tok.GenTime
l.Before = !roundTime.IsZero() && tok.Accuracy >= 0 && tok.GenTime.Add(tok.Accuracy).Before(roundTime)
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)
return l
}
@ -213,7 +221,7 @@ func evaluateSeal(v *Verdicts, s *seal, signature []byte, c *SecurityContext) {
}
v.Detail.SealHolder, v.Detail.SealTime = holderText(tok.TSA.Holder(), tok.TSA.Hash), tok.GenTime
v.Seal = VerdictSealedLate
if !c.RoundTime.IsZero() && tok.Accuracy >= 0 && tok.GenTime.Add(tok.Accuracy).Before(c.RoundTime) {
if !c.RoundTime.IsZero() && tok.GenTime.Add(tok.Accuracy).Before(c.RoundTime) {
v.Seal = VerdictSealed
}
}

@ -4,6 +4,7 @@ import (
"crypto"
"crypto/elliptic"
"crypto/sha256"
"slices"
"strings"
"testing"
"time"
@ -26,6 +27,16 @@ func testContext() *capsule.SecurityContext {
return c
}
// quotedNames are the holders of the required signers of v as the text of F6
// writes them.
func quotedNames(v capsule.Verdicts) string {
var names []string
for _, s := range v.Detail.Signers {
names = append(names, "«"+s.Holder+"»")
}
return strings.Join(names, ", ")
}
// cmsArea builds the SECURITY_CBOR of a capsule with an alg 2 signature by
// the signers, who all must sign, sealing each signature with tsa at when.
func cmsArea(t *testing.T, c *capsule.SecurityContext, required []cmstest.Signer, signers []cmstest.Signer, tsa cmstest.Signer, when time.Time, seal []byte) []byte {
@ -70,21 +81,32 @@ func TestEvaluateCMS(t *testing.T) {
if v.Signature != capsule.VerdictSignedComplete || v.Seal != capsule.VerdictNoSeal || v.Detail == nil || len(v.Detail.Signers) != 2 {
t.Fatalf("a complete co-signature: %+v", v)
}
// The names between « and », the authority of each seal, and, since the
// lines say "before the date", that DateKeys does not check who issued the
// seals (spec §29.7).
lines := v.Lines()
if !strings.HasPrefix(lines[0], "Firmado con un certificado a nombre de ") || !strings.Contains(lines[0], "Ana López") || !strings.Contains(lines[0], "Luis Gómez") ||
len(lines) != 3 || !strings.Contains(lines[1], "antes de la fecha de apertura") || strings.Contains(lines[1], "no antes") {
t.Errorf("lines %q", lines)
at := signedAt.UTC().Format(time.RFC3339Nano)
want := []string{
"Firmado con un certificado a nombre de " + quotedNames(v) + ". DateKeys no comprueba quién lo emitió: para eso, exporta la firma a un validador oficial.",
" «" + v.Detail.Signers[0].Holder + "» (emisor según su certificado: «" + v.Detail.Signers[0].Issuer + "»), sellado por «TSA de prueba» el " + at + ", antes de la fecha de apertura.",
" «" + v.Detail.Signers[1].Holder + "» (emisor según su certificado: «" + v.Detail.Signers[1].Issuer + "»), sellado por «TSA de prueba» el " + at + ", antes de la fecha de apertura.",
" DateKeys no comprueba quién emitió los sellos.",
}
if !slices.Equal(lines, want) || !strings.Contains(lines[0], "«Ana López»") || !strings.Contains(lines[0], "«Luis Gómez»") {
t.Errorf("lines %q, want %q", lines, want)
}
// A seal after the round time proves nothing before it.
// 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 || !strings.Contains(late.Lines()[1], "no antes de la fecha de apertura") {
if late.Signature != capsule.VerdictSignedComplete || len(late.Lines()) != 2 || !strings.Contains(late.Lines()[1], "no antes de la fecha de apertura") {
t.Errorf("a late seal: %+v %q", late, late.Lines())
}
// A signer who is not required shows apart and does not count.
// A signer who is not required shows apart, with its result in Spanish,
// and does not count.
f := capsule.EvaluateSecurityIn(cmsArea(t, c, []cmstest.Signer{ana}, []cmstest.Signer{ana, otro}, tsa, signedAt, nil), c)
if f.Signature != capsule.VerdictSignedComplete || len(f.Detail.Foreign) != 1 || f.Detail.Foreign[0].Holder != "Otro" || !strings.Contains(f.Lines()[len(f.Lines())-1], "No cuenta") {
if f.Signature != capsule.VerdictSignedComplete || len(f.Detail.Foreign) != 1 || f.Detail.Foreign[0].Holder != "Otro" || f.Lines()[len(f.Lines())-1] != " Otro firmante, «Otro»: válida. No cuenta." {
t.Errorf("a foreign signer: %+v %q", f, f.Lines())
}

File diff suppressed because it is too large Load Diff

@ -38,7 +38,13 @@
timestamp each), the RFC 3161 seal, a fixed area of 32 KiB, the key of
words, the public note and the capsule extension of the .dkk. Its §76
records each change with its reproducible case.
- `datekeys.cddl`: the CBOR schemas of the v0.11 draft, the three control
- `DateKeys_Protocol_Specification_v0.12.md`: the draft v0.12, work in
progress and not approved; this branch implements it. It changes no
format: it fixes what the review of the implementation of v0.11 found, the
names of certificates and the warning of the seal in the verdicts, a
profile of the certificate field by field, the addresses and the padding
of the locator, and errata. Its §76 records each change with its case.
- `datekeys.cddl`: the CBOR schemas of the v0.12 draft, the three control
versions and the security and head objects of format 3 included, with the
encoding rules CDDL cannot express. Those of v0.9 and v0.8.2 are at the tags
`spec-v0.9` and `spec-v0.8.2`.

@ -222,13 +222,13 @@ capsule-locator = {
1 => bstr .size 32, ; I_SOBRE, identity of the envelope
2 => bstr .size (1..1024), ; age header of the envelope, up to its MAC
3 => bstr .size 32, ; SHA-256 of the rest
4 => uint, ; size of the rest in bytes
4 => 0..max-safe-uint, ; size of the rest in bytes
5 => bstr .size 32, ; capsule_digest
? 6 => bstr, ; zero padding
? 6 => bstr .size (1..), ; zero padding, at least one byte
}
capsule-address = {
0 => tstr .size (1..1024), ; https or ipfs URI
? 1 => uint, ; offset of the rest in that resource; 0 if absent
? 1 => 1..max-safe-uint, ; offset of the rest in that resource; 0 is written by leaving it out
}
; Spec section 29.4. HEAD_CBOR of format 3, at most 16 MiB. Always version 1

@ -60,7 +60,7 @@
"seal": "S4",
"lines": [
"Firmado con la clave dkauthor1jtf6s2c7ywr6scx40jhye32sj82rjp8a6cj7f54jsh36rd96vaqqcg54xg. No prueba quién la tiene.",
"Según un sello a nombre de Autoridad de Sellado de prueba, existía el 2023-08-23T15:09:27Z, antes de que la cápsula pudiera abrirse. DateKeys no comprueba quién emitió el sello."
"Según un sello a nombre de «Autoridad de Sellado de prueba», existía el 2023-08-23T15:09:27Z, antes de que la cápsula pudiera abrirse. DateKeys no comprueba quién emitió el sello."
],
"author_key": "dkauthor1jtf6s2c7ywr6scx40jhye32sj82rjp8a6cj7f54jsh36rd96vaqqcg54xg"
},

@ -59,9 +59,10 @@
"signature": "F6",
"seal": "S0",
"lines": [
"Firmado con un certificado a nombre de Luis Gómez, Ana López. DateKeys no comprueba quién lo emitió: para eso, exporta la firma a un validador oficial.",
" Luis Gómez (emisor según su certificado: Luis Gómez), sellado el 2023-08-23T15:09:27Z, antes de la fecha de apertura.",
" Ana López (emisor según su certificado: Ana López), sellado el 2023-08-23T15:09:27Z, antes de la fecha de apertura."
"Firmado con un certificado a nombre de «Luis Gómez», «Ana López». DateKeys no comprueba quién lo emitió: para eso, exporta la firma a un validador oficial.",
" «Luis Gómez» (emisor según su certificado: «Luis Gómez»), sellado por «Autoridad de Sellado de prueba» el 2023-08-23T15:09:27Z, antes de la fecha de apertura.",
" «Ana López» (emisor según su certificado: «Ana López»), sellado por «Autoridad de Sellado de prueba» el 2023-08-23T15:09:27Z, antes de la fecha de apertura.",
" DateKeys no comprueba quién emitió los sellos."
]
},
"signature": {

Loading…
Cancel
Save

Powered by TurnKey Linux.