- 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
232 lines
6 KiB
Go
232 lines
6 KiB
Go
package transport
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
|
|
"git.n1ko.dev/Niko/niko_trust/pkg/identity"
|
|
"git.n1ko.dev/Niko/niko_trust/pkg/protocol"
|
|
"git.n1ko.dev/Niko/niko_trust/pkg/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"
|
|
case *protocol.DelegationClaim:
|
|
return "delegation"
|
|
case *protocol.KeyRotationRequest:
|
|
return "key_rotation_request"
|
|
case *protocol.KeyRotationConfirm:
|
|
return "key_rotation_confirm"
|
|
}
|
|
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
|
|
case tce.TagDelegation:
|
|
o, e := protocol.DecodeDelegationClaim(b)
|
|
return "delegation", o, e
|
|
case tce.TagKeyRotation:
|
|
o, e := protocol.DecodeKeyRotationRequest(b)
|
|
return "key_rotation_request", o, e
|
|
case tce.TagKeyRotationConf:
|
|
o, e := protocol.DecodeKeyRotationConfirm(b)
|
|
return "key_rotation_confirm", 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,
|
|
}
|
|
case *protocol.DelegationClaim:
|
|
predicates := make(map[string]any, len(o.Predicates))
|
|
for k, val := range o.Predicates {
|
|
predicates[k] = valueView(val)
|
|
}
|
|
v = map[string]any{
|
|
"type": "delegation",
|
|
"version": 1,
|
|
"granter": addrOf(o.Granter),
|
|
"grantee": addrOf(o.Grantee),
|
|
"predicates": predicates,
|
|
"max_depth": o.MaxDepth,
|
|
"created_at": o.CreatedAt,
|
|
"expires_at": o.ExpiresAt,
|
|
"serial": o.Serial,
|
|
"nonce": hexStr(o.Nonce),
|
|
}
|
|
case *protocol.KeyRotationRequest:
|
|
v = map[string]any{
|
|
"type": "key_rotation_request",
|
|
"version": 1,
|
|
"successor": addrOf(o.Successor),
|
|
"predecessor": addrOf(o.Predecessor),
|
|
"created_at": o.CreatedAt,
|
|
"expires_at": o.ExpiresAt,
|
|
}
|
|
case *protocol.KeyRotationConfirm:
|
|
v = map[string]any{
|
|
"type": "key_rotation_confirm",
|
|
"version": 1,
|
|
"rotation_hash": o.RotationHash.String(),
|
|
"created_at": o.CreatedAt,
|
|
"nonce": hexStr(o.Nonce),
|
|
}
|
|
}
|
|
raw, err := json.Marshal(v)
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
return typ, raw, nil
|
|
}
|