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/capsule/format3.go

945 lines
30 KiB

package capsule
import (
"encoding/binary"
"fmt"
"strings"
"time"
datekeys "g.activething.com/go/DateKeys"
"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"
)
// The content of a format 3 capsule (spec §29.2): the plaintext of
// PAYLOAD_AGE is BODY followed by its padding, and BODY is a frame of 12
// bytes, the security area, the head and the files.
const (
// BodyFrameSize is the size of the frame of BODY: AREA_LEN, SECURITY_LEN
// and HEAD_LEN, three unsigned 32-bit big-endian integers.
BodyFrameSize = 12
// AreaUnit is the unit of AREA_LEN, and MaxAreaLen its maximum.
AreaUnit = 512
MaxAreaLen = 128 * AreaUnit
// AreaLen is the size of the security area that writers of this version
// write, always, whatever the capsule holds (spec v0.11, §29.2, §62.1
// rule 13): 32 KiB. Writers of v0.10 wrote AreaUnit, 512 bytes.
AreaLen = 64 * AreaUnit
// LargeAreaLen is the area of a capsule whose creator expressly asked for
// a larger one because the signatures do not fit in AreaLen: 64 KiB.
LargeAreaLen = MaxAreaLen
// MaxHeadLen is the maximum of HEAD_LEN, 16 MiB.
MaxHeadLen = 16 << 20
SecurityTypeTag = "datekeys-security"
SecurityVersion = 1
HeadTypeTag = "datekeys-head"
HeadVersion = 1
// SaltSize is the size of the salt of the head (spec §29.4).
SaltSize = 32
// Limits of the head, fixed with format 3 (spec §29.4).
MaxCommentLen = pathrule.MaxCommentLen
MaxAuthorLen = pathrule.MaxAuthorLen
MaxFiles = 65535
MaxPathLen = pathrule.MaxPathLen
// MaxMTime is 9999-12-31T23:59:59Z in seconds since 1970-01-01 UTC.
MaxMTime = 253402300799
// maxSecurityItem bounds the byte strings of keys 2 and 3 of security.
maxSecurityItem = 65536
// maxAlg bounds alg and seal_type.
maxAlg = 1<<32 - 1
)
// BodyFrame is the frame of BODY (spec §29.2).
type BodyFrame struct {
AreaLen, SecurityLen, HeadLen uint32
}
// Bytes returns the 12 bytes of the frame.
func (f BodyFrame) Bytes() [BodyFrameSize]byte {
var b [BodyFrameSize]byte
binary.BigEndian.PutUint32(b[0:], f.AreaLen)
binary.BigEndian.PutUint32(b[4:], f.SecurityLen)
binary.BigEndian.PutUint32(b[8:], f.HeadLen)
return b
}
// ContentLength is C, the length of CONTENT in a BODY of length l whose frame
// is f. ParseBodyFrame has checked that it does not underflow.
func (f BodyFrame) ContentLength(l uint64) uint64 {
return l - BodyFrameSize - uint64(f.AreaLen) - uint64(f.HeadLen)
}
// ParseBodyFrame decodes the frame of BODY from its first 12 bytes and checks
// it against L, the length of BODY (spec §29.2, §63 step 17.2). Every
// violation is ErrIntegrity. The plaintext of PAYLOAD_AGE is at least 256
// bytes, so the 12 bytes exist even when L is shorter than the frame.
func ParseBodyFrame(b []byte, l uint64) (BodyFrame, error) {
if len(b) != BodyFrameSize {
return BodyFrame{}, fmt.Errorf("capsule: BODY: frame of %d bytes, want %d", len(b), BodyFrameSize)
}
if l < BodyFrameSize {
return BodyFrame{}, fmt.Errorf("capsule: BODY: L = %d is shorter than the frame of %d bytes: %w", l, BodyFrameSize, datekeys.ErrIntegrity)
}
f := BodyFrame{
AreaLen: binary.BigEndian.Uint32(b[0:]),
SecurityLen: binary.BigEndian.Uint32(b[4:]),
HeadLen: binary.BigEndian.Uint32(b[8:]),
}
switch {
case f.AreaLen < AreaUnit || f.AreaLen > MaxAreaLen || f.AreaLen%AreaUnit != 0:
return f, fmt.Errorf("capsule: BODY: AREA_LEN %d is not a multiple of %d from %d to %d: %w", f.AreaLen, AreaUnit, AreaUnit, MaxAreaLen, datekeys.ErrIntegrity)
case f.SecurityLen < 1 || f.SecurityLen > f.AreaLen:
return f, fmt.Errorf("capsule: BODY: SECURITY_LEN %d is not from 1 to AREA_LEN = %d: %w", f.SecurityLen, f.AreaLen, datekeys.ErrIntegrity)
case f.HeadLen < 1 || f.HeadLen > MaxHeadLen:
return f, fmt.Errorf("capsule: BODY: HEAD_LEN %d is not from 1 to %d: %w", f.HeadLen, MaxHeadLen, datekeys.ErrIntegrity)
case BodyFrameSize+uint64(f.AreaLen)+uint64(f.HeadLen) > l:
return f, fmt.Errorf("capsule: BODY: the frame, the area of %d bytes and the head of %d bytes exceed L = %d: %w", f.AreaLen, f.HeadLen, l, datekeys.ErrIntegrity)
}
return f, nil
}
// CheckArea checks that the bytes of the security area after SECURITY_CBOR,
// its first securityLen bytes, are zero (spec §29.2): ErrIntegrity if not.
func CheckArea(area []byte, securityLen uint32) error {
for i, c := range area[securityLen:] {
if c != 0 {
return fmt.Errorf("capsule: BODY: byte %d of the security area is not zero: %w", int(securityLen)+i, datekeys.ErrIntegrity)
}
}
return nil
}
// Verdict is the result of evaluating the signature or the seal of the
// security area (spec §29.7). A verdict never prevents opening.
type Verdict string
// The verdicts of a reader (spec v0.11, §29.7). A reader of v0.10 reaches
// X, F0, F1, S0, S1 and S2 only.
const (
// VerdictUnreadable (X): security fails its layer 2 or 3; it stands for
// both the signature and the seal.
VerdictUnreadable Verdict = "X"
// VerdictNoSignature (F0): no key 2.
VerdictNoSignature Verdict = "F0"
// VerdictSignatureUnchecked (F1): a signature that does not decode, breaks
// its schema or has an alg this reader does not implement.
VerdictSignatureUnchecked Verdict = "F1"
// VerdictSignatureInvalid (F2): a signature present that does not verify.
VerdictSignatureInvalid Verdict = "F2"
// VerdictSignedSaved (F3): a valid signature of alg 1 with a key the
// person saved, whose label Verdicts.AuthorLabel holds.
VerdictSignedSaved Verdict = "F3"
// VerdictSignedOther (F4): a valid signature of alg 1 with another key,
// which Verdicts.AuthorKey holds. It does not prove who holds it.
VerdictSignedOther Verdict = "F4"
// VerdictNoSeal (S0): no key 3; nothing is shown about the date.
VerdictNoSeal Verdict = "S0"
// VerdictSealUnsupported (S1): a seal_type this reader does not implement.
VerdictSealUnsupported Verdict = "S1"
// VerdictSealUnreadable (S2): a seal that does not decode or breaks its
// schema.
VerdictSealUnreadable Verdict = "S2"
// VerdictSignedIncomplete (F5): a signature of alg 2 with a required
// signer absent, not verifiable, without a seal or with an invalid one or
// out of validity, or with a key 3 (spec v0.11, §29.10).
VerdictSignedIncomplete Verdict = "F5"
// VerdictSignedComplete (F6): a signature of alg 2 with every required
// signer valid and sealed. Verdicts.Detail names them.
VerdictSignedComplete Verdict = "F6"
// VerdictSealInvalid (S3): a seal that does not verify.
VerdictSealInvalid Verdict = "S3"
// 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 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 or a reason, which Verdicts.Lines writes.
func (v Verdict) Text() string {
switch v {
case VerdictUnreadable:
return "No se han podido comprobar la firma ni el sello: trátala como no firmada y sin fecha probada."
case VerdictNoSignature:
return "Sin firma de autor."
case VerdictSignatureUnchecked:
return "No se ha comprobado ninguna firma: trátala como no firmada."
case VerdictSignatureInvalid:
return "La firma no corresponde a este contenido."
case VerdictSealUnsupported:
return "Lleva un sello de tiempo que esta versión no sabe comprobar: aquí no prueba nada."
case VerdictSealUnreadable:
return "El sello de tiempo es ilegible: no prueba nada."
case VerdictSignedIncomplete:
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."
}
return ""
}
// Verdicts are the verdicts of the security area of a format 3 capsule.
type Verdicts struct {
Signature, Seal Verdict
// AuthorKey is the public key of a valid signature of alg 1 (F3, F4).
AuthorKey [32]byte
// AuthorLabel is the label of the saved key that signed (F3).
AuthorLabel string
// Detail names the signers of an alg 2 signature and the authority of a
// valid seal; nil otherwise. A pointer, so that Verdicts stays comparable.
Detail *Detail
}
// Detail is what the texts of F6, S4 and S5 name (spec v0.11, §29.7, §29.10).
type Detail struct {
// Signers are the required signers, in the order of SIGNERS, and Foreign
// 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. 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.
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 a name of a certificate.
Holder string
// 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
// 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 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.
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
// an inconsistency: whoever made the capsule claims a file that is newer than
// the proof that it existed.
func (v Verdicts) SealedAt() (time.Time, bool) {
var best time.Time
take := func(t time.Time) {
if !t.IsZero() && (best.IsZero() || t.Before(best)) {
best = t
}
}
if v.Detail != nil {
if v.Seal == VerdictSealed || v.Seal == VerdictSealedLate {
take(v.Detail.SealTime)
}
for _, s := range v.Detail.Signers {
if s.Result == "valid" {
take(s.SealTime)
}
}
}
return best, !best.IsZero()
}
// Lines are the verdicts as the official SDK shows them, in order: X alone,
// or the signature and then the seal, when it shows something.
func (v Verdicts) Lines() []string {
if v.Signature == VerdictUnreadable {
return []string{VerdictUnreadable.Text()}
}
lines := []string{v.Signature.Text()}
switch v.Signature {
case VerdictSignedSaved:
lines[0] = "Firmado con la clave que guardaste como " + v.AuthorLabel + "."
case VerdictSignedOther:
key, _ := bech32.Encode("dkauthor", v.AuthorKey[:])
lines[0] = "Firmado con la clave " + key + ". No prueba quién la tiene."
}
if v.Signature == VerdictSignedComplete && v.Detail != nil {
names := make([]string, len(v.Detail.Signers))
for i, s := range v.Detail.Signers {
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 := "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
}
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.", 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 "+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)
}
return lines
}
// securityWire is the outer map of SECURITY_CBOR: keys 2 and 3 hold
// separately encoded CBOR (spec §29.3).
type securityWire struct {
signature, seal []byte // nil when absent
}
func (w *securityWire) encode(e *codec.Encoder) {
n := 2
if w.signature != nil {
n++
}
if w.seal != nil {
n++
}
e.Map(n)
e.Uint(0)
e.Text(SecurityTypeTag)
e.Uint(1)
e.Uint(SecurityVersion)
if w.signature != nil {
e.Uint(2)
e.Bstr(w.signature)
}
if w.seal != nil {
e.Uint(3)
e.Bstr(w.seal)
}
}
func (w *securityWire) decode(d *codec.Decoder) error {
pairs, err := d.Map(4)
if err != nil {
return err
}
var seen uint
for range pairs {
k, err := d.Key()
if err != nil {
return err
}
switch k {
case 0:
_, err = d.Text(len(SecurityTypeTag))
case 1:
_, err = d.Uint(SecurityVersion)
case 2:
w.signature, err = d.Bstr(1, maxSecurityItem)
case 3:
w.seal, err = d.Bstr(1, maxSecurityItem)
default:
return fmt.Errorf("key %d is not defined: %w", k, datekeys.ErrNonCanonicalCBOR)
}
if err != nil {
return fmt.Errorf("key %d: %w", k, err)
}
seen |= 1 << k
}
if err := required(seen, 2); err != nil {
return err
}
return d.EndMap()
}
// EncodeSecurity returns SECURITY_CBOR as a writer of this version writes it:
// empty, {0: "datekeys-security", 1: 1}, 22 bytes (spec §29.3).
func EncodeSecurity() []byte {
var e codec.Encoder
(&securityWire{}).encode(&e)
b, _ := e.Out()
return b
}
// EncodeSecurityWith returns SECURITY_CBOR with the given contents of keys 2
Docs at spec v0.11 and the draft v0.12: READMEs, SECURITY, traceability, CHANGELOG The session that closed v0.11 left the documentation at v0.10 (review of 2 October, G14). - README.md and README.es.md say the same again: the reference implements v0.11, tagged, and the branch v0.12 the draft; SpecVersion 0.11; the area of 32 KiB in the picture of BODY; the table of modules with authorkey, internal/cms, internal/der, locator, wordkey and the public note; and what the CLI does now: encrypt -sign shows the key and the code of AUTHOR_MESSAGE before it signs, decrypt -expect-author writes nothing unless the key of an F4 matches, the lines of the verdicts break behind a mark, and decrypt and inspect say when a public note is not shown. The security properties no longer say that no signature is checked. - SECURITY.md: the scope is v0.11 and the draft v0.12; the limits of a signature, a seal and a key of words; the standard library among the cryptographic dependencies. - docs/traceability.md at the draft v0.12: rows for 24.1, 29.8 to 29.12, 38.1 and 44.1, and rows 29.2, 29.3, 29.7, 62.1, 64, 67, 70, 72 and 76 up to date, with the code and the tests of each. The cases of spec 64 that security_cms.json and locator.json still lack are marked pending. - CHANGELOG.md: an entry for the draft v0.12: the review and its fixes, the draft, the CMS reader with its own profile, the addresses of the locator, the CLI, the new test data, and what is pending. - capsule/format3.go: the comments of EncodeSecurityWith, EncodeAuthorSignature and EncodeSeal no longer say that this version defines no alg and no seal_type. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
// and 3, nil when absent (spec §29.3). EncryptFiles writes it with what this
// version defines, a signature of alg 1 or 2 and a seal of seal_type 2; any
// other content only a generator of test vectors writes (spec §62.1 rule 13).
func EncodeSecurityWith(signature, seal []byte) ([]byte, error) {
var e codec.Encoder
(&securityWire{signature: signature, seal: seal}).encode(&e)
return e.Out()
}
// EvaluateSecurity reads SECURITY_CBOR and returns its verdicts without the
// capsule around it (spec §29.3, §29.7), as a reader of v0.10 does: it checks
// the structure, and any signature is F1. EvaluateSecurityIn checks the
// signature too. It never fails: security never decides the opening. For the
// signature and for the seal apart, the first row of the table of §29.7 that
// holds decides: alg and seal_type are read only from content that decodes
// and meets its schema.
func EvaluateSecurity(b []byte) Verdicts { return EvaluateSecurityIn(b, nil) }
// decodeSecurity decodes the outer map of SECURITY_CBOR, layers 2 and 3.
func decodeSecurity(b []byte) (*securityWire, bool) {
if tag, version, err := codec.Peek(b); err != nil || tag != SecurityTypeTag || version != SecurityVersion {
return nil, false
}
var w securityWire
if err := codec.Unmarshal(b, w.decode, w.encode); err != nil {
return nil, false
}
return &w, true
}
// authorSignature is the content of key 2 of security: {0: alg, 1: public
// key, 2: signature} (spec §29.3).
type authorSignature struct {
alg uint64
key, value []byte
}
func (a *authorSignature) encode(e *codec.Encoder) {
e.Map(3)
e.Uint(0)
e.Uint(a.alg)
e.Uint(1)
e.Bstr(a.key)
e.Uint(2)
e.Bstr(a.value)
}
func (a *authorSignature) decode(d *codec.Decoder) error {
return decodeItem(d, 3, func(k uint64) (err error) {
switch k {
case 0:
a.alg, err = decodeAlg(d)
case 1:
a.key, err = d.Bstr(0, maxSecurityItem)
case 2:
a.value, err = d.Bstr(0, maxSecurityItem)
default:
err = fmt.Errorf("key %d is not defined: %w", k, datekeys.ErrNonCanonicalCBOR)
}
return err
})
}
// decodeAuthorSignature decodes the content of key 2 of security.
func decodeAuthorSignature(b []byte) (*authorSignature, error) {
var a authorSignature
if err := codec.Unmarshal(b, a.decode, a.encode); err != nil {
return nil, err
}
return &a, nil
}
Docs at spec v0.11 and the draft v0.12: READMEs, SECURITY, traceability, CHANGELOG The session that closed v0.11 left the documentation at v0.10 (review of 2 October, G14). - README.md and README.es.md say the same again: the reference implements v0.11, tagged, and the branch v0.12 the draft; SpecVersion 0.11; the area of 32 KiB in the picture of BODY; the table of modules with authorkey, internal/cms, internal/der, locator, wordkey and the public note; and what the CLI does now: encrypt -sign shows the key and the code of AUTHOR_MESSAGE before it signs, decrypt -expect-author writes nothing unless the key of an F4 matches, the lines of the verdicts break behind a mark, and decrypt and inspect say when a public note is not shown. The security properties no longer say that no signature is checked. - SECURITY.md: the scope is v0.11 and the draft v0.12; the limits of a signature, a seal and a key of words; the standard library among the cryptographic dependencies. - docs/traceability.md at the draft v0.12: rows for 24.1, 29.8 to 29.12, 38.1 and 44.1, and rows 29.2, 29.3, 29.7, 62.1, 64, 67, 70, 72 and 76 up to date, with the code and the tests of each. The cases of spec 64 that security_cms.json and locator.json still lack are marked pending. - CHANGELOG.md: an entry for the draft v0.12: the review and its fixes, the draft, the CMS reader with its own profile, the addresses of the locator, the CLI, the new test data, and what is pending. - capsule/format3.go: the comments of EncodeSecurityWith, EncodeAuthorSignature and EncodeSeal no longer say that this version defines no alg and no seal_type. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
// EncodeAuthorSignature returns the content of key 2 of security, {0: alg,
// 1: key, 2: signature} (spec §29.3). This version defines alg 1, AlgEd25519,
// and alg 2, AlgCMS, whose key 1 holds SIGNERS: EncryptFiles writes them, and
// only a generator of test vectors writes another alg, such as AlgTest.
func EncodeAuthorSignature(alg uint64, key, signature []byte) ([]byte, error) {
var e codec.Encoder
(&authorSignature{alg, key, signature}).encode(&e)
return e.Out()
}
// seal is the content of key 3 of security: {0: seal_type, 1: token}.
type seal struct {
sealType uint64
token []byte
}
func (s *seal) encode(e *codec.Encoder) {
e.Map(2)
e.Uint(0)
e.Uint(s.sealType)
e.Uint(1)
e.Bstr(s.token)
}
func (s *seal) decode(d *codec.Decoder) error {
return decodeItem(d, 2, func(k uint64) (err error) {
switch k {
case 0:
s.sealType, err = decodeAlg(d)
case 1:
s.token, err = d.Bstr(0, maxSecurityItem)
default:
err = fmt.Errorf("key %d is not defined: %w", k, datekeys.ErrNonCanonicalCBOR)
}
return err
})
}
func decodeSeal(b []byte) (*seal, error) {
var s seal
if err := codec.Unmarshal(b, s.decode, s.encode); err != nil {
return nil, err
}
return &s, nil
}
Docs at spec v0.11 and the draft v0.12: READMEs, SECURITY, traceability, CHANGELOG The session that closed v0.11 left the documentation at v0.10 (review of 2 October, G14). - README.md and README.es.md say the same again: the reference implements v0.11, tagged, and the branch v0.12 the draft; SpecVersion 0.11; the area of 32 KiB in the picture of BODY; the table of modules with authorkey, internal/cms, internal/der, locator, wordkey and the public note; and what the CLI does now: encrypt -sign shows the key and the code of AUTHOR_MESSAGE before it signs, decrypt -expect-author writes nothing unless the key of an F4 matches, the lines of the verdicts break behind a mark, and decrypt and inspect say when a public note is not shown. The security properties no longer say that no signature is checked. - SECURITY.md: the scope is v0.11 and the draft v0.12; the limits of a signature, a seal and a key of words; the standard library among the cryptographic dependencies. - docs/traceability.md at the draft v0.12: rows for 24.1, 29.8 to 29.12, 38.1 and 44.1, and rows 29.2, 29.3, 29.7, 62.1, 64, 67, 70, 72 and 76 up to date, with the code and the tests of each. The cases of spec 64 that security_cms.json and locator.json still lack are marked pending. - CHANGELOG.md: an entry for the draft v0.12: the review and its fixes, the draft, the CMS reader with its own profile, the addresses of the locator, the CLI, the new test data, and what is pending. - capsule/format3.go: the comments of EncodeSecurityWith, EncodeAuthorSignature and EncodeSeal no longer say that this version defines no alg and no seal_type. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
// EncodeSeal returns the content of key 3 of security, {0: seal_type, 1:
// token} (spec §29.3). This version defines seal_type 2, SealTypeRFC3161,
// which EncryptFiles writes; only a generator of test vectors writes another
// seal_type, such as SealTypeTest.
func EncodeSeal(sealType uint64, token []byte) ([]byte, error) {
var e codec.Encoder
(&seal{sealType, token}).encode(&e)
return e.Out()
}
// decodeAlg reads alg or seal_type, from 1 to 2^32 - 1.
func decodeAlg(d *codec.Decoder) (uint64, error) {
v, err := d.Uint(maxAlg)
if err == nil && v == 0 {
err = fmt.Errorf("0 is not defined: %w", datekeys.ErrNonCanonicalCBOR)
}
return v, err
}
// decodeItem reads a map of exactly n required keys, 0 to n-1, with field.
func decodeItem(d *codec.Decoder, n int, field func(k uint64) error) error {
pairs, err := d.Map(n)
if err != nil {
return err
}
var seen uint
for range pairs {
k, err := d.Key()
if err != nil {
return err
}
if err := field(k); err != nil {
return fmt.Errorf("key %d: %w", k, err)
}
seen |= 1 << k
}
if err := required(seen, n); err != nil {
return err
}
return d.EndMap()
}
// Head is the head of a format 3 capsule (spec §29.4).
type Head struct {
Salt [SaltSize]byte
// Comment and Author are the comment and the declared author, "" when
// absent. The declared author is text of the creator and proves nothing.
Comment, Author string
// Files are the entries, in strictly ascending byte order of their paths.
Files []File
// Critical and Noncritical are the extensions of the head (keys 6 and 7).
Critical, Noncritical []extension.Extension
}
// File is an entry of the head: a file of CONTENT.
type File struct {
Path string
Size, Start, End uint64
SHA256 [32]byte
// MTime is the modification time of the file at its source, in seconds
// since 1970-01-01 UTC, when HasMTime. It is informative.
MTime uint64
HasMTime bool
}
// headWire is HEAD_CBOR as it is encoded.
type headWire struct {
h *Head
}
func (w *headWire) encode(e *codec.Encoder) {
h := w.h
n := 3 + nonEmpty(h.Critical) + nonEmpty(h.Noncritical)
if h.Comment != "" {
n++
}
if h.Author != "" {
n++
}
if len(h.Files) > 0 {
n++
}
e.Map(n)
e.Uint(0)
e.Text(HeadTypeTag)
e.Uint(1)
e.Uint(HeadVersion)
e.Uint(2)
e.Bstr(h.Salt[:])
if h.Comment != "" {
e.Uint(3)
e.Text(h.Comment)
}
if h.Author != "" {
e.Uint(4)
e.Text(h.Author)
}
if len(h.Files) > 0 {
e.Uint(5)
e.Array(len(h.Files))
for i := range h.Files {
encodeFile(e, &h.Files[i])
}
}
encodeExtensions(e, 6, h.Critical, h.Noncritical)
}
func encodeFile(e *codec.Encoder, f *File) {
n := 5
if f.HasMTime {
n++
}
e.Map(n)
e.Uint(0)
e.Text(f.Path)
e.Uint(1)
e.Uint(f.Size)
e.Uint(2)
e.Uint(f.Start)
e.Uint(3)
e.Uint(f.End)
e.Uint(4)
e.Bstr(f.SHA256[:])
if f.HasMTime {
e.Uint(5)
e.Uint(f.MTime)
}
}
// decode reads HEAD_CBOR with the rules of the third layer: the CDDL, R1 and
// R8 (spec §29.4, §29.5). The fourth layer comes after, in DecodeHead.
func (w *headWire) decode(d *codec.Decoder) error {
h := w.h
pairs, err := d.Map(8)
if err != nil {
return err
}
var seen uint
for range pairs {
k, err := d.Key()
if err != nil {
return err
}
switch k {
case 0:
_, err = d.Text(len(HeadTypeTag))
case 1:
_, err = d.Uint(HeadVersion)
case 2:
var salt []byte
if salt, err = d.Bstr(SaltSize, SaltSize); err == nil {
copy(h.Salt[:], salt)
}
case 3:
h.Comment, err = decodeText(d, MaxCommentLen, "comment")
case 4:
h.Author, err = decodeText(d, MaxAuthorLen, "declared author")
case 5:
h.Files, err = decodeFiles(d)
case 6:
h.Critical, err = extension.DecodeArray(d)
case 7:
h.Noncritical, err = extension.DecodeArray(d)
default:
return fmt.Errorf("key %d is not defined: %w", k, datekeys.ErrNonCanonicalCBOR)
}
if err != nil {
return fmt.Errorf("key %d: %w", k, err)
}
seen |= 1 << k
}
if err := required(seen, 3); err != nil {
return err
}
return d.EndMap()
}
// decodeText reads a text of 1 to max bytes.
func decodeText(d *codec.Decoder, max int, what string) (string, error) {
s, err := d.Text(max)
if err == nil && s == "" {
err = fmt.Errorf("empty %s: %w", what, datekeys.ErrNonCanonicalCBOR)
}
return s, err
}
// decodeFiles reads the array of entries: 1 to MaxFiles, each path of 1 to
// MaxPathLen bytes (R1), in strictly ascending byte order (R8).
func decodeFiles(d *codec.Decoder) ([]File, error) {
n, err := d.Array(MaxFiles)
if err != nil {
return nil, err
}
if n == 0 {
return nil, fmt.Errorf("empty array of files: %w", datekeys.ErrNonCanonicalCBOR)
}
files := make([]File, 0, min(n, 1024))
for i := range n {
var f File
if err := decodeItemOptional(d, 5, 6, func(k uint64) (err error) {
switch k {
case 0:
f.Path, err = d.Text(MaxPathLen)
if err == nil && f.Path == "" {
err = fmt.Errorf("R1: the path is empty: %w", datekeys.ErrNonCanonicalCBOR)
}
case 1:
f.Size, err = d.Uint(MaxPayloadLength)
case 2:
f.Start, err = d.Uint(MaxPayloadLength)
case 3:
f.End, err = d.Uint(MaxPayloadLength)
case 4:
var sum []byte
if sum, err = d.Bstr(32, 32); err == nil {
copy(f.SHA256[:], sum)
}
case 5:
f.MTime, err = d.Uint(MaxMTime)
f.HasMTime = err == nil
default:
err = fmt.Errorf("key %d is not defined: %w", k, datekeys.ErrNonCanonicalCBOR)
}
return err
}); err != nil {
return nil, fmt.Errorf("file %d: %w", i+1, err)
}
if i > 0 && f.Path <= files[i-1].Path {
return nil, fmt.Errorf("file %d: R8: the path is not after the path of file %d in byte order: %w", i+1, i, datekeys.ErrNonCanonicalCBOR)
}
files = append(files, f)
}
return files, nil
}
// decodeItemOptional reads a map whose keys 0 to required-1 are required and
// whose keys up to max-1 are optional.
func decodeItemOptional(d *codec.Decoder, need, max int, field func(k uint64) error) error {
pairs, err := d.Map(max)
if err != nil {
return err
}
var seen uint
for range pairs {
k, err := d.Key()
if err != nil {
return err
}
if err := field(k); err != nil {
return fmt.Errorf("key %d: %w", k, err)
}
seen |= 1 << k
}
if err := required(seen, need); err != nil {
return err
}
return d.EndMap()
}
// EncodeHead returns HEAD_CBOR for h. The files must already be in byte
// order of their paths. The writer checks the result with DecodeHead, the
// rules of the reader (spec §62.1 rule 17).
func EncodeHead(h *Head) ([]byte, error) {
w := headWire{h: &Head{Salt: h.Salt, Comment: h.Comment, Author: h.Author, Files: h.Files}}
var err error
if w.h.Critical, err = extension.Canonical(h.Critical); err != nil {
return nil, err
}
if w.h.Noncritical, err = extension.Canonical(h.Noncritical); err != nil {
return nil, err
}
if err := extension.CheckDisjoint(w.h.Critical, w.h.Noncritical); err != nil {
return nil, err
}
if len(h.Files) > MaxFiles {
return nil, fmt.Errorf("capsule: head: %d files, more than %d", len(h.Files), MaxFiles)
}
var e codec.Encoder
w.encode(&e)
return e.Out()
}
// DecodeHead validates and decodes HEAD_CBOR with the layers of spec §69.1
// (spec §29.4, §63 step 17.4): the type tag and the version (layer 2), the
// CDDL with R1 and R8 (layer 3), and then, in key order, the comment and the
// declared author (§29.6), the files with R2 to R6c, R10 and their layout,
// R7 and R9 over the tree (§29.5), all ErrHeadInvalid, and the critical
// extensions with reg (layer 4).
func DecodeHead(b []byte, reg extension.Registry) (*Head, error) {
Format 3, step 4: the writer EncryptFiles writes format 3 (spec 29.2 to 29.6, 61, 62, 62.1): the files of a list of Sources, each read twice, with the comment and the declared author. - Before anything is written: the paths and the texts are checked with the rules of the reader, in the words of a writer, naming the rule and the character, and the two paths of an R7 collision (rule 15); the comment has its CR LF and lone CR turned into LF (29.6); L is measured with a head whose salt and SHA-256 are zero, as long as the final one, and the first reading hashes each file, which must have exactly its Size. - The files go in the byte order of their paths (R8), whatever the order of the Sources; the mtime is kept only from 1970 to 9999, never clipped (rule 16); at least one file or a comment (rule 14). - The head, with a fresh salt, the control and the security area are decoded with the rules of the reader before sealing (rule 17), and the frame is checked against L. The area is 512 bytes with the empty security, whatever the options (rule 13). - The second reading writes each file into PAYLOAD_AGE and fails if its size or SHA-256 changed (rule 18). - Encrypt and EncryptFiles share the sealing; Encrypt writes format 2 only with the new TestVectors option (rule 1), and takes no head. The test data generators set it, and so does the CLI until step 5 moves it to EncryptFiles. - Result.Head is the head written. DecodeHead keeps the check of the critical extensions apart, so that the self-check decodes the head as the one of the control does. - The examples and the live test write with EncryptFiles. - The reader tests had a literal U+202E, now escaped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
h, err := decodeHead(b)
if err != nil {
return nil, err
}
if err := extension.CheckCriticalIn(extension.Head, h.Critical, reg); err != nil {
return nil, fmt.Errorf("capsule: head: %w", err)
}
return h, nil
}
// decodeHead is DecodeHead but for the critical extensions, whose knowledge
// depends on the reader: the self-check of a writer decodes with it, as it
// decodes the control with DecodeControl (spec §62.1 rule 17).
func decodeHead(b []byte) (*Head, error) {
if len(b) > MaxHeadLen {
return nil, fmt.Errorf("capsule: head: %d bytes, more than %d: %w", len(b), MaxHeadLen, datekeys.ErrIntegrity)
}
if err := codec.CheckSchema(b, HeadTypeTag, HeadVersion); err != nil {
return nil, fmt.Errorf("capsule: head: %w", err)
}
h := &Head{}
w := headWire{h: h}
if err := codec.Unmarshal(b, w.decode, w.encode); err != nil {
return nil, fmt.Errorf("capsule: head: %w", err)
}
if err := extension.CheckDisjoint(h.Critical, h.Noncritical); err != nil {
return nil, fmt.Errorf("capsule: head: %w", err)
}
if err := checkHeadFields(h); err != nil {
return nil, err
}
return h, nil
}
// checkHeadFields applies the rules of the fourth layer with a code of their
// own, ErrHeadInvalid: keys 3, 4 and 5, in that order.
func checkHeadFields(h *Head) error {
invalid := func(what string, err error) error {
return fmt.Errorf("capsule: head: %s%v: %w", what, err, datekeys.ErrHeadInvalid)
}
if h.Comment != "" {
if err := pathrule.CheckComment(h.Comment); err != nil {
return invalid("comment: ", err)
}
}
if h.Author != "" {
if err := pathrule.CheckAuthor(h.Author); err != nil {
return invalid("declared author: ", err)
}
}
var end uint64
paths := make([]string, len(h.Files))
for i := range h.Files {
f := &h.Files[i]
if err := pathrule.CheckPath(f.Path); err != nil {
return invalid(fmt.Sprintf("file %d: ", i+1), err)
}
switch {
case f.Start != end:
return invalid(fmt.Sprintf("file %d: ", i+1), fmt.Errorf("start %d is not %d, the end of the file before", f.Start, end))
case f.End < f.Start || f.End-f.Start != f.Size:
return invalid(fmt.Sprintf("file %d: ", i+1), fmt.Errorf("from start %d to end %d is not the size %d", f.Start, f.End, f.Size))
}
end = f.End
paths[i] = f.Path
}
if err := pathrule.CheckTree(paths); err != nil {
return invalid("", err)
}
return nil
}
// CheckHeadEnd checks that the files fill CONTENT, of c bytes: the last one
// ends at C, or C is 0 without files (spec §29.4, §63 step 17.5).
// ErrIntegrity if not.
func CheckHeadEnd(h *Head, c uint64) error {
var end uint64
if n := len(h.Files); n > 0 {
end = h.Files[n-1].End
}
if end != c {
return fmt.Errorf("capsule: BODY: the files end at byte %d of a content of %d bytes: %w", end, c, datekeys.ErrIntegrity)
}
return nil
}

Powered by TurnKey Linux.