niko_trust/internal/transport/envelope.go
Niko Marmeladkov 9d66003689
Initial commit: signed-object trust relay, verifier, and docs
- server: relay storing signed objects (PUT/GET), per-IP rate limiting,
  per-subject quota (1000), one-response-per-request, pagination,
  /v1/healthz /v1/readyz /v1/metrics
- verify: signature-verifying trust evaluator; every object is checked via
  env.Verify(), approvals via VerifyApprovalResponse, revocations via
  VerifyRevocationOf; k-of-n approval quorum
- docs: TRUST-MODEL.md and API.md describing issuer-anchored signatures and
  the endpoint/status-code contract
- tests: server, verify, and ratelimit packages
2026-08-12 22:36:49 +03:00

87 lines
3.3 KiB
Go

// Package transport defines the JSON wire form of TCE objects, as specified in
// PROTOCOL.md section 10. It is the only layer in the tree that speaks JSON:
// the codec packages (internal/tce, internal/protocol) never import
// encoding/json, because signatures are computed over the binary TCE bytes and
// never over JSON (non-negotiable #8).
//
// A verifier must decode the `tce` field, verify the signature over those exact
// bytes, and read any field it needs from those bytes. The `object` view is a
// convenience only and must never be trusted or re-encoded for verification.
package transport
import (
"encoding/json"
"fmt"
"git.n1ko.dev/Niko/niko_trust/internal/protocol"
"git.n1ko.dev/Niko/niko_trust/internal/tce"
)
// Envelope is the JSON transport form of one signed object.
//
// `tce` and `signature` are authoritative (base64 standard, with padding).
// `object_id` is the lowercase hex of SHA-256(tce); a server must recompute it
// and ignore any supplied value. `object` is a decoded convenience view.
type Envelope struct {
TCE []byte `json:"tce"`
Signature []byte `json:"signature"`
Object json.RawMessage `json:"object,omitempty"`
ObjectID string `json:"object_id,omitempty"`
}
// ParseEnvelope decodes a JSON envelope.
func ParseEnvelope(b []byte) (*Envelope, error) {
var e Envelope
if err := json.Unmarshal(b, &e); err != nil {
return nil, fmt.Errorf("transport: bad envelope: %w", err)
}
return &e, nil
}
// Marshal returns the canonical JSON form of the envelope.
func (e *Envelope) Marshal() ([]byte, error) {
return json.Marshal(e)
}
// ContentID computes the content address of the envelope's TCE bytes (the
// authoritative object_id).
func (e *Envelope) ContentID() string {
return tce.ComputeID(e.TCE).String()
}
// BuildView decodes the TCE bytes and produces the `object` convenience view,
// returning the object type name.
func (e *Envelope) BuildView() (objectType string, view json.RawMessage, err error) {
return BuildView(e.TCE)
}
// Verify performs structural decode and signature verification of the
// envelope, returning the object type name and a verify error if any. Callers
// must treat a non-nil error as total rejection. The signature is checked over
// the exact TCE bytes.
func (e *Envelope) Verify() (objectType string, err error) {
typ, obj, decErr := DecodeObject(e.TCE)
if decErr != nil {
return "", decErr
}
switch obj.(type) {
case *protocol.Identity:
_, err = protocol.VerifyIdentity(e.TCE, e.Signature)
case *protocol.Claim:
_, err = protocol.VerifyClaim(e.TCE, e.Signature)
case *protocol.Revocation:
_, err = protocol.VerifyRevocation(e.TCE, e.Signature)
case *protocol.ApprovalRequest:
_, err = protocol.VerifyApprovalRequest(e.TCE, e.Signature)
case *protocol.ApprovalResponse:
// A response is only meaningful bound to its request; callers verify it
// through VerifyApprovalResponse with the request. Standalone we only
// confirm the response itself is structurally signed.
_, err = protocol.VerifyApprovalResponseStandalone(e.TCE, e.Signature)
case *protocol.AuthAssertion:
// Audience is bound by the server from request context; standalone we
// cannot check it, so we only confirm the assertion is signed.
_, err = protocol.VerifyAuthAssertionSignature(e.TCE, e.Signature)
}
return typ, err
}