signing

package
v0.0.0-...-8df018f Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 2, 2026 License: Apache-2.0 Imports: 12 Imported by: 0

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

Constants

This section is empty.

Variables

View Source
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

func CanonicalMessage(router, method string, body []byte, unixTs int64) []byte

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 Failure

type Failure struct {
	Reason  Reason
	Message string
	Err     error
}

Failure is the error type the Validator returns on any rejection.

func (*Failure) Error

func (f *Failure) Error() string

func (*Failure) Unwrap

func (f *Failure) Unwrap() error

type Headers

type Headers interface {
	Get(name string) string
}

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.

func (*MemoryStore) Get

func (m *MemoryStore) Get(_ context.Context, sessionID string) (ed25519.PublicKey, error)

Get implements PublicKeyStore.

func (*MemoryStore) Put

func (m *MemoryStore) Put(sessionID string, key ed25519.PublicKey)

Put inserts or replaces a session's public key.

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 New

func New(opts Options) *Validator

New returns a Validator. Store is mandatory.

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL