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.
945 lines
30 KiB
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
|
|
}
|