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
// 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
}
// 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
}
// 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) {
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.