// 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 }