Authentication
Back to Home
The auth package (forge.lthn.ai/core/go-crypt/auth) implements OpenPGP challenge-response authentication with support for both online (HTTP) and air-gapped (file-based courier) transport. It was ported from dAppServer's mod-auth/lethean.service.ts.
Overview
Authentication uses PGP key pairs for identity verification. Users register with a username and password, which generates a PGP keypair and stores credentials via an io.Medium storage backend (disk, memory, or any custom implementation).
Two authentication flows are supported:
- Online — Challenge-response over HTTP
- Air-gapped (Courier) — Challenge-response via files exchanged on a Medium, suitable for air-gapped systems
Authentication Flow
Online Flow
Client Server
| |
|-- Register(username, password) --> | Generate PGP keypair, store credentials
| |
|-- CreateChallenge(userID) -------> | Generate nonce, encrypt with user's public key
|<-- Challenge (encrypted nonce) --- |
| |
| Decrypt nonce, sign with |
| private key |
| |
|-- ValidateResponse(signed) ------> | Verify signature, create session
|<-- Session (token, 24hr TTL) ----- |
Air-Gapped (Courier) Flow
The same cryptographic operations, but challenge and response are exchanged via files on a Medium rather than over HTTP:
Server Client
| |
|-- WriteChallengeFile(path) --------> | Write encrypted challenge to file
| | (transport file via USB, etc.)
| |
| | Decrypt, sign, write response to file
| |
|<-- ReadResponseFile(path) ---------- | Read response, validate, create session
Types
User
type User struct {
PublicKey string `json:"public_key"`
KeyID string `json:"key_id"`
Fingerprint string `json:"fingerprint"`
PasswordHash string `json:"password_hash"` // LTHN hash
Created time.Time `json:"created"`
LastLogin time.Time `json:"last_login"`
}
Challenge
type Challenge struct {
Nonce []byte `json:"nonce"`
Encrypted string `json:"encrypted"` // PGP-encrypted nonce (armored)
ExpiresAt time.Time `json:"expires_at"`
}
Challenges have a default TTL of 5 minutes. After expiry, the challenge is rejected.
Session
type Session struct {
Token string `json:"token"`
UserID string `json:"user_id"`
ExpiresAt time.Time `json:"expires_at"`
}
Sessions have a default TTL of 24 hours. Tokens are 32-byte cryptographically random hex strings.
Creating an Authenticator
import (
"forge.lthn.ai/core/go-crypt/auth"
"forge.lthn.ai/core/go/pkg/io"
)
// Using a disk-based Medium
medium := io.NewDiskMedium("/path/to/data")
authenticator := auth.New(medium)
// With custom TTLs
authenticator := auth.New(medium,
auth.WithChallengeTTL(10 * time.Minute),
auth.WithSessionTTL(48 * time.Hour),
)
Storage Layout
All user data is persisted through the io.Medium interface:
users/
{userID}.pub PGP public key (armored)
{userID}.key PGP private key (armored, password-encrypted)
{userID}.rev Revocation certificate (placeholder)
{userID}.json User metadata (encrypted with user's public key)
{userID}.lthn LTHN password hash
The userID is derived from the username using the LTHN hash algorithm (crypt/lthn).
API Reference
Registration
func (a *Authenticator) Register(username, password string) (*User, error)
Creates a new user account. Generates a PGP keypair (protected by the given password), hashes the username with LTHN to produce a userID, and persists all artifacts via the Medium.
user, err := authenticator.Register("alice", "strong-password")
if err != nil {
log.Fatal(err)
}
fmt.Println(user.KeyID) // LTHN hash of "alice"
fmt.Println(user.Fingerprint) // LTHN hash of the public key
Challenge-Response (Online)
func (a *Authenticator) CreateChallenge(userID string) (*Challenge, error)
func (a *Authenticator) ValidateResponse(userID string, signedNonce []byte) (*Session, error)
// Server: create challenge
challenge, err := authenticator.CreateChallenge(userID)
// Send challenge.Encrypted to client
// Client: decrypt nonce, sign it with private key
// ...
// Server: validate the signed response
session, err := authenticator.ValidateResponse(userID, signedNonce)
fmt.Println(session.Token) // Hex session token
fmt.Println(session.ExpiresAt) // 24 hours from now
Challenge-Response (Air-Gapped)
func (a *Authenticator) WriteChallengeFile(userID, path string) error
func (a *Authenticator) ReadResponseFile(userID, path string) (*Session, error)
// Server: write challenge to a file (for transport to air-gapped client)
err := authenticator.WriteChallengeFile(userID, "challenge.json")
// Client: (on air-gapped machine) decrypt, sign, write response to file
// ...
// Server: read response file and validate
session, err := authenticator.ReadResponseFile(userID, "response.pgp")
Password-Based Login
func (a *Authenticator) Login(userID, password string) (*Session, error)
A convenience method that bypasses the PGP challenge-response flow. Verifies the password against the stored LTHN hash and creates a session on success.
session, err := authenticator.Login(userID, "strong-password")
Session Management
func (a *Authenticator) ValidateSession(token string) (*Session, error)
func (a *Authenticator) RefreshSession(token string) (*Session, error)
func (a *Authenticator) RevokeSession(token string) error
// Check if a session is valid
session, err := authenticator.ValidateSession(token)
// Extend a session's expiry
session, err = authenticator.RefreshSession(token)
// Invalidate a session immediately
err = authenticator.RevokeSession(token)
User Deletion
func (a *Authenticator) DeleteUser(userID string) error
Removes a user and all associated keys from storage. The "server" user is protected and cannot be deleted, mirroring the original TypeScript implementation's safeguard.
err := authenticator.DeleteUser(userID)
// Revokes all active sessions for this user
Default Parameters
| Parameter | Value | Configurable Via |
|---|---|---|
| Challenge TTL | 5 minutes | auth.WithChallengeTTL() |
| Session TTL | 24 hours | auth.WithSessionTTL() |
| Nonce size | 32 bytes | Not configurable |
| Session token | 32 bytes (hex-encoded) | Not configurable |
| Protected users | "server" |
Not configurable |
See Also
- Home — Package overview and quick start
- Encryption-and-Hashing — Symmetric encryption and PGP operations used by the auth package
- Trust-Engine — Agent trust tiers and capability-based access control