cryptid

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Apr 26, 2026 License: MIT Imports: 15 Imported by: 0

README

cryptid

Shared cryptographic identity library for browser-based applications. ECDSA P-256 keypair as identity, with no passwords, no accounts, no external dependencies.

Usage

Go consumers:

go get codeberg.org/palaemon/cryptid

JS consumers:

cd js && npm install  # or copy cryptid.js directly

Go Server Library

import "codeberg.org/palaemon/cryptid"

Core: VerifySignedJSON, VerifySignature, DeriveUserID, CanonicalJSON, ParsePublicKey

Sessions: NewSessionManagerCreateSession, VerifySession, RequireSession, RequireAdmin

Challenge-Response: NewChallengeStoreNewChallenge, Consume

Middleware: RequireSignature, RequireSignedHeader

Key Rotation: VerifyRotationRotationResult{OldUserID, NewUserID, Proof}

Username Attestation: ParseAttestationAttestationRequest{Username, UserID}

JS Client Library

import { Identity, getIdentity } from './cryptid.js'
import { Session } from './session.js'
import { IdentityTransfer } from './transfer.js'
import { createSignedFetch } from './fetch.js'

Identity: generate, sign, getUserID, passphrase encrypt/decrypt, key rotation

Sessions: establish via inline signing or challenge-response

Transfer: QR-compatible export/import with expiration

Fetch: auto-signing HTTP client (POST: inline sign, GET: session cookie or signed header)

Wire Format

All signed payloads include "v": 1. Signature field: {"data": "<base64 sig>", "publicKey": "<base64 SPKI>"}.

UserID = hex(SHA-256(base64_SPKI_public_key)).

Testing

make test

Or individually:

go test -v ./...
node --test js/*.test.js

Regenerate interop test vectors:

node testdata/interop/generate.js > testdata/interop/vectors.json

License

MIT — see LICENSE.

Documentation

Index

Constants

View Source
const (
	HeaderUserID    = "X-User-Id"
	HeaderPublicKey = "X-Public-Key"
)

Variables

This section is empty.

Functions

func CanonicalJSON

func CanonicalJSON(v any) (string, error)

func DeriveUserID

func DeriveUserID(publicKeyBase64 string) string

DeriveUserID returns hex(SHA-256(publicKeyBase64)) — hashes the base64 string, not raw bytes.

func ParsePublicKey

func ParsePublicKey(base64Key string) (*ecdsa.PublicKey, error)

ParsePublicKey decodes a base64 SPKI DER-encoded ECDSA public key.

func RequireSignature

func RequireSignature(next http.HandlerFunc) http.HandlerFunc

RequireSignature reads the request body, verifies the embedded ECDSA signature, sets X-User-Id and X-Public-Key headers, and replaces r.Body with the stripped (signature-removed) payload. Returns 401 on any failure.

func RequireSignedHeader

func RequireSignedHeader(next http.HandlerFunc) http.HandlerFunc

RequireSignedHeader verifies the X-Cryptid-Auth header, which must contain a base64-encoded signed JSON payload of the form:

{"method":"GET","path":"/api/foo","timestamp":<unix_ms>,"v":1,"signature":{"data":"...","publicKey":"..."}}

The signature covers all fields except "signature" itself. Method and path must match the actual request. Timestamp must be within 60 seconds of now. Sets X-User-Id and X-Public-Key on success. Returns 401 on any failure.

func VerifySignature

func VerifySignature(message, signatureBase64, publicKeyBase64 string) (bool, error)

VerifySignature verifies an ECDSA P-256 signature in raw r||s format (64 bytes, 32 each). This matches the Web Crypto API's output format.

func VerifySignedJSON

func VerifySignedJSON(body []byte) (cleanData map[string]any, pubKeyBase64 string, userID string, err error)

VerifySignedJSON extracts the signature field from a JSON body, re-canonicalizes the remaining payload, verifies the ECDSA signature, and returns the clean data, public key, and derived userID.

Expected body shape:

{...fields, "signature": {"data": "<base64 r||s>", "publicKey": "<base64 SPKI>"}}

Types

type AttestationRequest

type AttestationRequest struct {
	Username string
	UserID   string
	PubKey   string
}

AttestationRequest holds the verified data from a username attestation request.

func ParseAttestation

func ParseAttestation(body []byte) (*AttestationRequest, error)

ParseAttestation verifies a signed attestation request. The body must be a JSON object signed by the user's key, containing a username field.

type ChallengeConfig

type ChallengeConfig struct {
	// MaxAge is how long challenges remain valid. Zero defaults to 60s.
	MaxAge time.Duration
	// MaxStored limits in-memory challenges before cleanup. Zero defaults to 1000.
	MaxStored int
}

ChallengeConfig configures the challenge store.

type ChallengeStore

type ChallengeStore struct {
	// contains filtered or unexported fields
}

ChallengeStore provides replay-safe single-use challenge generation. Safe for concurrent use.

func NewChallengeStore

func NewChallengeStore(cfg ChallengeConfig) *ChallengeStore

NewChallengeStore returns a ChallengeStore with defaults applied (MaxAge=60s, MaxStored=1000).

func (*ChallengeStore) Consume

func (cs *ChallengeStore) Consume(challenge string) bool

func (*ChallengeStore) NewChallenge

func (cs *ChallengeStore) NewChallenge() (string, error)

type RotationResult

type RotationResult struct {
	OldUserID    string
	NewUserID    string
	OldPublicKey string
	NewPublicKey string
	Proof        []byte
}

RotationResult holds the verified data from a key rotation request.

func VerifyRotation

func VerifyRotation(body []byte) (*RotationResult, error)

VerifyRotation verifies a signed key rotation request. The body must be a JSON object signed by the old key, containing a newPublicKey field.

type SessionConfig

type SessionConfig struct {
	// CookiePrefix is prepended to the cookie name. Empty uses no prefix.
	CookiePrefix string
	// CookiePath sets the cookie path. Empty defaults to "/".
	CookiePath string
	// MaxAge sets the session duration. Empty defaults to 24h.
	MaxAge time.Duration
	// Secure sets the Secure flag on cookies.
	Secure bool
}

SessionConfig holds configuration for a SessionManager.

type SessionManager

type SessionManager struct {
	// contains filtered or unexported fields
}

SessionManager creates and verifies stateless signed session cookies.

func NewSessionManager

func NewSessionManager(cfg SessionConfig) *SessionManager

NewSessionManager returns a SessionManager with defaults applied.

func (*SessionManager) CreateSession

func (sm *SessionManager) CreateSession(w http.ResponseWriter, r *http.Request) (string, error)

CreateSession reads a signed JSON body from r, verifies it, checks expiry, and sets an HttpOnly session cookie on w. Returns the derived userID.

func (*SessionManager) RequireAdmin

func (sm *SessionManager) RequireAdmin(isAdmin func(string) (bool, error), next http.HandlerFunc) http.HandlerFunc

RequireAdmin verifies the session cookie, then calls isAdmin. Sets X-User-Id. Returns 401 for bad session, 403 if isAdmin returns false.

func (*SessionManager) RequireSession

func (sm *SessionManager) RequireSession(next http.HandlerFunc) http.HandlerFunc

RequireSession verifies the session cookie and sets X-User-Id. Returns 401 on failure.

func (*SessionManager) VerifySession

func (sm *SessionManager) VerifySession(r *http.Request) (string, error)

VerifySession reads the session cookie from r, decodes it, re-verifies the signature, and checks that the session has not expired. Returns the userID.

Jump to

Keyboard shortcuts

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