niko_trust/pkg/transport/envelope.go
Niko Marmeladkov 3bf13fa488 Public SDK packages, proxy-aware rate limits, service login recipe
- internal/{address,identity,protocol,tce,transport,verify} -> pkg/ so
  external Go projects can import the verified core; invariant tests
  updated for the new paths
- Config.TrustProxy: key rate limiting by X-Forwarded-For when the relay
  sits behind a reverse proxy (off by default, header never trusted
  otherwise)
- examples/service + examples/approve: complete passwordless login round
  trip (mint request -> wallet approves -> local verify), run live in CI
- docs/SERVICE-GUIDE.md: the integration recipe
2026-08-26 12:49:54 +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/pkg/protocol"
"git.n1ko.dev/Niko/niko_trust/pkg/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
}