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

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
}

Powered by TurnKey Linux.