niko_trust/internal/transport/view.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

183 lines
4.5 KiB
Go

package transport
import (
"encoding/hex"
"encoding/json"
"git.n1ko.dev/Niko/niko_trust/internal/identity"
"git.n1ko.dev/Niko/niko_trust/internal/protocol"
"git.n1ko.dev/Niko/niko_trust/internal/tce"
)
func hexStr(b []byte) string { return hex.EncodeToString(b) }
// AddrOf renders a public key as its trust address text.
func AddrOf(pub []byte) string {
id, err := identity.FromPubKey(pub)
if err != nil {
return ""
}
return id.Address().String()
}
func addrOf(pub []byte) string { return AddrOf(pub) }
// valueView renders a TCE value for the JSON `object` convenience view.
func valueView(v tce.Value) any {
switch v.Tag() {
case tce.ValNull:
return nil
case tce.ValTrue:
return true
case tce.ValFalse:
return false
case tce.ValString:
s, _ := v.Str()
return s
case tce.ValNumber:
tok, _ := v.NumberToken()
return json.Number(tok)
}
return nil
}
func objectTypeOf(obj any) string {
switch obj.(type) {
case *protocol.Identity:
return "identity"
case *protocol.Claim:
return "claim"
case *protocol.Revocation:
return "revocation"
case *protocol.ApprovalRequest:
return "request"
case *protocol.ApprovalResponse:
return "response"
case *protocol.AuthAssertion:
return "auth"
}
return ""
}
// ObjectTypeName returns the type name of a decoded protocol object.
func ObjectTypeName(obj any) string { return objectTypeOf(obj) }
// DecodeObject strict-decodes TCE bytes into the typed protocol object and
// returns its type name.
func DecodeObject(b []byte) (string, any, error) {
d := tce.NewDecoder(b)
tag, err := d.Header()
if err != nil {
return "", nil, err
}
switch tag {
case tce.TagIdentity:
o, e := protocol.DecodeIdentity(b)
return "identity", o, e
case tce.TagClaim:
o, e := protocol.DecodeClaim(b)
return "claim", o, e
case tce.TagRevocation:
o, e := protocol.DecodeRevocation(b)
return "revocation", o, e
case tce.TagApprovalRequest:
o, e := protocol.DecodeApprovalRequest(b)
return "request", o, e
case tce.TagApprovalResponse:
o, e := protocol.DecodeApprovalResponse(b)
return "response", o, e
case tce.TagAuthAssertion:
o, e := protocol.DecodeAuthAssertion(b)
return "auth", o, e
default:
return "", nil, tce.ErrObjectTag
}
}
// BuildView decodes the TCE bytes and produces the `object` convenience view of
// PROTOCOL.md section 10. It fails if the bytes do not strict-decode.
func BuildView(b []byte) (objectType string, view json.RawMessage, err error) {
typ, obj, decErr := DecodeObject(b)
if decErr != nil {
return "", nil, decErr
}
var v any
switch o := obj.(type) {
case *protocol.Identity:
v = map[string]any{
"type": "identity",
"version": 1,
"identity": addrOf(o.PubKey),
"alias": o.Alias,
"created_at": o.CreatedAt,
}
case *protocol.Claim:
claims := make(map[string]any, len(o.Claims))
for k, val := range o.Claims {
claims[k] = valueView(val)
}
v = map[string]any{
"type": "claim",
"version": 1,
"issuer": addrOf(o.Issuer),
"subject": addrOf(o.Subject),
"claims": claims,
"created_at": o.CreatedAt,
"expires_at": o.ExpiresAt,
"serial": o.Serial,
"nonce": hexStr(o.Nonce),
}
case *protocol.Revocation:
v = map[string]any{
"type": "revocation",
"version": 1,
"issuer": addrOf(o.Issuer),
"claim_id": o.ClaimID.String(),
"reason": o.Reason,
"created_at": o.CreatedAt,
"nonce": hexStr(o.Nonce),
}
case *protocol.ApprovalRequest:
payload := make(map[string]any, len(o.Payload))
for k, val := range o.Payload {
payload[k] = valueView(val)
}
v = map[string]any{
"type": "request",
"version": 1,
"sender": addrOf(o.Sender),
"recipient": addrOf(o.Recipient),
"action": o.Action,
"payload": payload,
"message": o.Message,
"created_at": o.CreatedAt,
"expires_at": o.ExpiresAt,
"nonce": hexStr(o.Nonce),
}
case *protocol.ApprovalResponse:
v = map[string]any{
"type": "response",
"version": 1,
"request_hash": o.RequestHash.String(),
"responder": addrOf(o.Responder),
"decision": o.Decision.String(),
"created_at": o.CreatedAt,
"nonce": hexStr(o.Nonce),
}
case *protocol.AuthAssertion:
v = map[string]any{
"type": "auth",
"version": 1,
"identity": addrOf(o.PubKey),
"challenge": hexStr(o.Challenge),
"scope": o.Scope,
"audience": o.Audience,
"created_at": o.CreatedAt,
}
}
raw, err := json.Marshal(v)
if err != nil {
return "", nil, err
}
return typ, raw, nil
}