Documentation
¶
Overview ¶
Package signing implements Sov's per-request Ed25519 request-signing scheme. It is NOT an authentication verifier — it does not produce an authenticated user identity. It gates the request: a valid signature proves the caller holds the session keypair registered for X-Session. Identity (the "who is alice?" question) is the consumer's separate concern — typically a JWT middleware or a session lookup that runs before or after signing.
Wire shape (set by client per request):
X-Session: <opaque session id> X-Ts: <unix seconds> X-Sig: <hex Ed25519 signature>
Canonical signed payload (client and server must produce identical bytes):
v2\n<router>\n<method>\n<sha256_hex(body)>\n<unix_ts>\n
Session lifecycle:
- The consumer exposes a session/init RPC method (in their own router) that accepts a freshly generated client pubkey, allocates a session id, persists the pubkey in a PublicKeyStore, and returns the session id.
- Every subsequent RPC the client sends carries X-Session + X-Ts + X-Sig. The Validator looks the session up, recomputes the canonical message, and rejects on signature mismatch or stale ts.
Replay window is 30s by default and configurable. Signatures are HEX (not base64) to match the wire format used in production prior art.
Index ¶
- Variables
- func CanonicalMessage(router, method string, body []byte, unixTs int64) []byte
- func GatewayMiddleware(opts MiddlewareOptions) gateway.Middleware
- func UseSigning(store PublicKeyStore, skipMethods ...string) gateway.Option
- type Failure
- type Headers
- type MemoryStore
- type MiddlewareOptions
- type Options
- type PublicKeyStore
- type Reason
- type Validator
Constants ¶
This section is empty.
Variables ¶
var ErrSessionNotFound = errors.New("signing: session not found")
ErrSessionNotFound is what a PublicKeyStore returns when sessionID is unknown. The Validator turns this into SESSION_EXPIRED.
Functions ¶
func CanonicalMessage ¶
CanonicalMessage builds the byte string the client signed and the server must hash identically. Format:
v2\n<router>\n<method>\n<sha256_hex(body)>\n<unix_ts>\n
Newlines are unambiguous separators. Body hash is hex-encoded so the string is ASCII-safe even when the request body contains binary.
func GatewayMiddleware ¶
func GatewayMiddleware(opts MiddlewareOptions) gateway.Middleware
GatewayMiddleware returns the gateway.Middleware described above.
func UseSigning ¶
func UseSigning(store PublicKeyStore, skipMethods ...string) gateway.Option
UseSigning returns a gateway.Option that wires the signing middleware onto the gateway. One-call zero-trust setup:
gw := gateway.New(
gateway.WithRegistry(),
signing.UseSigning(signing.NewMemoryStore()),
)
The middleware installs ahead of the consumer middleware chain so signed-request verification runs before any handler-level work. The auth + authz middleware (which Gateway installs unconditionally inside New) still runs first — that order is fine because the auth middleware only acts on the bearer header, while signing acts on X-Session / X-Ts / X-Sig: orthogonal layers.
SkipMethods names "{Service}/{method}" pairs that bypass signing (typically "Session/init" — the very call that registers a new public key cannot itself be signed). Append more as your app requires.
Types ¶
type Headers ¶
Headers is the subset of inbound HTTP headers the validator reads. Pulled out as a tiny type so callers can adapt fiber/fasthttp/net/http or any other source.
type MemoryStore ¶
type MemoryStore struct {
// contains filtered or unexported fields
}
MemoryStore is a goroutine-safe in-memory PublicKeyStore.
func NewMemoryStore ¶
func NewMemoryStore() *MemoryStore
NewMemoryStore returns an empty MemoryStore.
func (*MemoryStore) Delete ¶
func (m *MemoryStore) Delete(sessionID string)
Delete removes a session.
type MiddlewareOptions ¶
type MiddlewareOptions struct {
Validator *Validator
SkipMethods []string
OnSuccess func(req *gateway.Request, sessionID string)
}
GatewayMiddleware is a gateway.Middleware that gates every business RPC with the Validator. Framework endpoints (/rpc/_*) bypass the check — the gateway owns those, and session/init RPCs need to land somewhere signature-free or new clients cannot get a session in the first place.
SkipMethods is the consumer-supplied list of "{Service}/{method}" pairs that bypass validation. Always include the session-init pair the consumer defines (e.g. "Session/init"). If empty, every business RPC requires a valid signature.
OnSuccess, if non-nil, is invoked with the verified sessionID after a successful validation. Typical use: stash the sessionID on the request so downstream handlers can resolve "who is this session's user?" without re-doing the lookup.
type Options ¶
type Options struct {
// Store is required; resolves session id → public key.
Store PublicKeyStore
// ReplayWindow is how far the timestamp may drift from now. Default 30s.
ReplayWindow time.Duration
// Now overrides time.Now for tests.
Now func() time.Time
}
Options configures a Validator.
type PublicKeyStore ¶
type PublicKeyStore interface {
Get(ctx context.Context, sessionID string) (ed25519.PublicKey, error)
}
PublicKeyStore looks up the Ed25519 public key registered for a session id. Implementations must be safe for concurrent use.
Consumers implement this on top of their durable store (Redis, Postgres, NATS KV) — this package ships an in-memory implementation useful for tests and single-process examples.
type Reason ¶
type Reason string
Reason is a stable code returned alongside a validation failure so callers can map it to an HTTP status / wire error code.
const ( ReasonMissingHeaders Reason = "MISSING_HEADERS" ReasonBadTimestamp Reason = "BAD_TIMESTAMP" ReasonExpired Reason = "TIMESTAMP_EXPIRED" ReasonBadSignatureFmt Reason = "BAD_SIGNATURE_FORMAT" ReasonSessionMissing Reason = "SESSION_EXPIRED" ReasonSessionLookup Reason = "SESSION_LOOKUP_FAILED" ReasonBadPublicKey Reason = "BAD_PUBLIC_KEY" ReasonInvalidSig Reason = "INVALID_SIGNATURE" )
type Validator ¶
type Validator struct {
// contains filtered or unexported fields
}
Validator validates per-request Ed25519 signatures.
func (*Validator) Validate ¶
func (v *Validator) Validate(ctx context.Context, hdr Headers, router, method string, body []byte) (sessionID string, err error)
Validate checks the X-Session / X-Ts / X-Sig headers against the canonical message derived from router / method / body. On success it returns the session id (so callers can use it for follow-up lookups like "who is this session's user?"). On failure it returns a *Failure.