// Package tce implements Trust Canonical Encoding version 1. // // TCE is the byte-exact representation over which every protocol signature is // computed. The specification is docs/PROTOCOL.md and the frozen vectors in // testdata/vectors/tce_vectors.json are normative: this package conforms to // them, never the other way round. // // The central property is that every value has exactly one encoding. The // encoder emits that encoding, and the decoder accepts only that encoding. // Anything else, including a longer spelling of the same value, is an error. // This is what makes a signature over TCE bytes meaningful: two byte strings // cannot denote the same object, so a signature cannot be transplanted from // one meaning to another. // // The decoder is strict and never repairs input. It does not skip fields it // does not understand, because a decoder that skipped an unknown field would // compute a different meaning for the same signed bytes than one that // understood it, while both saw a valid signature. // // This package handles encoding only. It performs no signing, no verification // and no policy evaluation, and it imports neither encoding/json nor any // storage or transport package (INV-8, INV-9). package tce import ( "errors" "fmt" ) // Framing. const ( // Magic is the domain separation prefix of every TCE object. The trailing // NUL terminates the ASCII portion so that a longer magic in a future // version cannot be a prefix of this one. Magic = "trust.n1ko.dev/tce/1\x00" // MagicLen is the length of Magic in bytes. MagicLen = len(Magic) // Version is the object version encoded by this implementation. Version = 1 ) // ObjectTag identifies the type of a TCE object. It is part of the signed // bytes, so a signature over one object type can never be replayed as // another. type ObjectTag byte // Object tags. Tag 0x00 is permanently reserved so that an all-zero buffer is // never a valid object. const ( TagReserved ObjectTag = 0x00 TagIdentity ObjectTag = 0x01 TagClaim ObjectTag = 0x02 TagRevocation ObjectTag = 0x03 TagApprovalRequest ObjectTag = 0x04 TagApprovalResponse ObjectTag = 0x05 TagAuthAssertion ObjectTag = 0x06 ) // String renders a tag for diagnostics. func (t ObjectTag) String() string { switch t { case TagIdentity: return "identity" case TagClaim: return "claim" case TagRevocation: return "revocation" case TagApprovalRequest: return "approval_request" case TagApprovalResponse: return "approval_response" case TagAuthAssertion: return "auth_assertion" default: return fmt.Sprintf("unknown(0x%02x)", byte(t)) } } // knownTag reports whether t is a defined object tag in version 1. func knownTag(t ObjectTag) bool { switch t { case TagIdentity, TagClaim, TagRevocation, TagApprovalRequest, TagApprovalResponse, TagAuthAssertion: return true default: return false } } // ValueTag identifies the type of a claim or payload value. type ValueTag byte // Value tags. False and true have distinct tags rather than one boolean tag // with a payload byte, so there is no invalid third spelling of a boolean. // // Tags 0x05 to 0x07 are reserved for future types and must be rejected in // version 1 rather than skipped. const ( ValNull ValueTag = 0x00 ValFalse ValueTag = 0x01 ValTrue ValueTag = 0x02 ValString ValueTag = 0x03 ValNumber ValueTag = 0x04 ValResBytes ValueTag = 0x05 ValResArray ValueTag = 0x06 ValResMap ValueTag = 0x07 ) // Sizes fixed by the specification. const ( AddressVersion = 0 PubKeySize = 32 NonceSize = 16 HashSize = 32 ChallengeSize = 32 SignatureSize = 64 ) // Field and object limits from PROTOCOL.md section 6.3. These are part of the // format: an object exceeding any of them is invalid everywhere, so a signer // cannot produce an object that some verifiers accept and others reject. const ( MaxUvarintBytes = 10 MaxKeyLen = 128 MaxStringValue = 512 MaxNumberToken = 52 MaxMapEntries = 32 MaxActionLen = 128 MaxMessageLen = 256 MaxReasonLen = 256 MaxAliasLen = 64 MaxScopeLen = 32 MaxAudienceLen = 128 MaxNumberIntDigs = 32 MaxNumberFracDig = 18 MaxNumberSource = 64 MaxIdentityTCE = 1024 MaxClaimTCE = 4096 MaxRevocTCE = 1024 MaxRequestTCE = 8192 MaxResponseTCE = 1024 MaxAuthTCE = 1024 // MaxObjectTCE bounds any object and is used to size read limits before // the object type is known. MaxObjectTCE = MaxRequestTCE ) // Timestamp bounds from PROTOCOL.md section 4.6. const ( MinTimestamp = 1_000_000_000 // 2001-09-09T01:46:40Z MaxTimestamp = 4_102_444_800 // 2100-01-01T00:00:00Z // MaxApprovalLifetime is the largest permitted gap between an approval // request's created_at and expires_at. MaxApprovalLifetime = 60 ) // Decision values for an ApprovalResponse. const ( DecisionDeny = 0 DecisionAllow = 1 ) // Errors returned by this package. // // The set is deliberately small and carries no attacker-controlled data, so // that error text cannot be used to exfiltrate input or to fingerprint a // parser state machine. var ( ErrMagic = errors.New("tce: bad magic") ErrObjectTag = errors.New("tce: unknown object tag") ErrVersion = errors.New("tce: unsupported object version") ErrTruncated = errors.New("tce: truncated input") ErrTrailing = errors.New("tce: trailing bytes after object") ErrUvarint = errors.New("tce: malformed uvarint") ErrNonMinimal = errors.New("tce: non-minimal uvarint") ErrOverflow = errors.New("tce: integer overflow") ErrTooLong = errors.New("tce: field exceeds maximum length") ErrObjectTooLarge = errors.New("tce: object exceeds maximum size") ErrUTF8 = errors.New("tce: invalid UTF-8") ErrControlChar = errors.New("tce: control character in string") ErrKeyGrammar = errors.New("tce: map key does not match grammar") ErrDuplicateKey = errors.New("tce: duplicate map key") ErrKeyOrder = errors.New("tce: map keys not in ascending order") ErrValueTag = errors.New("tce: unknown or reserved value tag") ErrNumberFormat = errors.New("tce: not a valid JSON number") ErrNumberRange = errors.New("tce: number out of representable range") ErrTimestamp = errors.New("tce: timestamp out of range") ErrFieldSize = errors.New("tce: fixed-size field has wrong length") ErrAddressVersion = errors.New("tce: unsupported address version") ErrEmptyMap = errors.New("tce: map requires at least one entry") ErrDecision = errors.New("tce: unknown decision value") ErrLifetime = errors.New("tce: approval lifetime out of bounds") ErrExpiry = errors.New("tce: expires_at must be after created_at") ) // fieldError annotates a sentinel error with the field that failed, without // including any input data. type fieldError struct { field string err error } func (e *fieldError) Error() string { return e.field + ": " + e.err.Error() } func (e *fieldError) Unwrap() error { return e.err } func fieldErr(field string, err error) error { if err == nil { return nil } return &fieldError{field: field, err: err} }