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.
728 lines
21 KiB
728 lines
21 KiB
package capsule
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
|
|
datekeys "g.activething.com/go/DateKeys"
|
|
"g.activething.com/go/DateKeys/codec"
|
|
"g.activething.com/go/DateKeys/extension"
|
|
"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 §29.2, §62.1 rule 13).
|
|
AreaLen = 512
|
|
// 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 this version can reach (spec §29.7). It implements no alg and
|
|
// no seal_type.
|
|
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"
|
|
// 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"
|
|
)
|
|
|
|
// Text returns the text of the verdict that the official SDK shows, in
|
|
// Spanish (spec §29.7), and "" for S0, which shows nothing.
|
|
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 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."
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// Verdicts are the verdicts of the security area of a format 3 capsule.
|
|
type Verdicts struct {
|
|
Signature, Seal Verdict
|
|
}
|
|
|
|
// 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()}
|
|
if t := v.Seal.Text(); 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. This version defines no alg and no seal_type: only
|
|
// a generator of test vectors writes them (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 (spec §29.3,
|
|
// §29.7). 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 {
|
|
x := Verdicts{VerdictUnreadable, VerdictUnreadable}
|
|
if tag, version, err := codec.Peek(b); err != nil || tag != SecurityTypeTag || version != SecurityVersion {
|
|
return x
|
|
}
|
|
var w securityWire
|
|
if err := codec.Unmarshal(b, w.decode, w.encode); err != nil {
|
|
return x
|
|
}
|
|
v := Verdicts{Signature: VerdictNoSignature, Seal: VerdictNoSeal}
|
|
if w.signature != nil {
|
|
// A content that does not decode and an alg this version does not
|
|
// implement give the same verdict, and this version implements none.
|
|
v.Signature = VerdictSignatureUnchecked
|
|
}
|
|
if w.seal != nil {
|
|
if _, err := decodeSeal(w.seal); err != nil {
|
|
v.Seal = VerdictSealUnreadable
|
|
} else {
|
|
v.Seal = VerdictSealUnsupported
|
|
}
|
|
}
|
|
return v
|
|
}
|
|
|
|
// 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 for a
|
|
// generator of test vectors: this version defines no alg.
|
|
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 for a generator of
|
|
// test vectors: this version defines no seal_type.
|
|
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) {
|
|
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
|
|
}
|
|
if err := extension.CheckCriticalIn(extension.Head, h.Critical, reg); err != nil {
|
|
return nil, fmt.Errorf("capsule: head: %w", 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
|
|
}
|