auth

package
v0.2.2 Latest Latest
Warning

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

Go to latest
Published: Apr 19, 2026 License: MIT Imports: 32 Imported by: 0

Documentation

Overview

Package auth handles Ed25519 keypair generation, storage, and challenge signing.

Package auth provides secure secret storage for cryptographic keys.

SecretStore is the abstraction layer for storing secrets in the system keychain (macOS Keychain, Linux SecretService) or an encrypted file fallback.

Index

Constants

View Source
const (
	// ServiceName is the keychain/keyring service identifier.
	ServiceName = "pocketmux"

	// SecretKeyEd25519Private is the key name for the Ed25519 private key.
	SecretKeyEd25519Private = "ed25519-private-key"

	// SecretKeySharedSecretPrefix is the prefix for shared secret key names.
	// The full key is "shared-secret-<deviceId>".
	SecretKeySharedSecretPrefix = "shared-secret-"
)
View Source
const (
	// BackendAuto probes the system keyring, falling back to encrypted file.
	BackendAuto = "auto"

	// BackendKeyring forces the system keyring backend. Errors if unavailable.
	BackendKeyring = "keyring"

	// BackendFile forces the encrypted file backend.
	BackendFile = "file"
)
View Source
const MaxMobileNameLen = 64

MaxMobileNameLen is the maximum allowed length for a mobile device name. This limit is enforced during both initial pairing and name-update flows.

View Source
const PairTimeout = 5 * time.Minute

PairTimeout is the default timeout for waiting for mobile to complete pairing.

Variables

View Source
var ErrSecretNotFound = errors.New("secret not found")

ErrSecretNotFound is returned when a secret does not exist in the store.

Functions

func AddPairedDevice

func AddPairedDevice(path string, device PairedDevice, store SecretStore) error

AddPairedDevice stores a paired device, replacing any existing pairing. The shared secret is stored in the SecretStore; metadata is written to the JSON file. Single-pairing mode: only one device can be paired at a time.

func BuildQRPayload

func BuildQRPayload(pairingCode string, x25519PubKeyBase64 string, hostDeviceID string, serverURL string) (string, error)

BuildQRPayload creates a pipe-delimited payload for the pairing QR code. Format: pairingCode|x25519PubKey|deviceId[|serverUrl] The server URL is omitted when it matches the production default to minimize QR code size. The mobile app falls back to the default when absent.

func CheckHMACRejection added in v0.0.3

func CheckHMACRejection(statusCode int, body []byte, serverURL string) error

CheckHMACRejection inspects an HTTP response for HMAC rejection. Returns a *HMACRejectedError if the response indicates a permanent HMAC failure, or nil if the error is unrelated to HMAC validation.

func DeleteDevice

func DeleteDevice(identity *Identity, serverURL string, client *http.Client, hmacSecret string) error

DeleteDevice calls DELETE /auth/device on the signaling server to remove the host device record and its pairing. Used by `pmux uninstall`.

func DeletePairing

func DeletePairing(identity *Identity, serverURL string, client *http.Client, hmacSecret string) error

DeletePairing calls DELETE /auth/pairing on the signaling server to remove the pairing record and notify the mobile device.

func ExchangeToken

func ExchangeToken(id *Identity, serverURL string, client *http.Client, hmacSecret string) (string, error)

ExchangeToken signs a challenge with the identity key and exchanges it for a JWT. serverURL should be the base URL of the signaling server (e.g., "https://signal.pmux.io").

func HasIdentity

func HasIdentity(keysDir string, store SecretStore) bool

HasIdentity checks whether an Ed25519 keypair exists. The private key is checked in the SecretStore and the public key on disk.

func IsHMACRejection added in v0.0.3

func IsHMACRejection(statusCode int, errorMsg string) bool

IsHMACRejection returns true if the HTTP status code and server error message indicate a permanent HMAC client signature failure.

func ProbeKeyring

func ProbeKeyring() (err error)

ProbeKeyring tests whether the system keyring is available by writing and deleting a probe value. Returns nil if the keyring is usable.

The probe is wrapped in a panic recovery because obfuscation tools (garble) can break D-Bus interface type assertions on Linux, causing the Secret Service backend to panic instead of returning an error.

func RemovePairedDevice

func RemovePairedDevice(path string, deviceID string, store SecretStore) error

RemovePairedDevice removes a device by ID from the stored paired devices list and deletes its shared secret from the SecretStore.

func SavePairedDevices

func SavePairedDevices(path string, devices []PairedDevice) error

SavePairedDevices writes the paired devices list to disk. Shared secrets are NOT written to the JSON file (stored in SecretStore instead). Uses temp-file-then-rename for crash-safe atomicity.

func SharedSecretKey

func SharedSecretKey(deviceID string) string

SharedSecretKey returns the full key name for a device's shared secret.

func SignRequest added in v0.0.2

func SignRequest(req *http.Request, secret string)

SignRequest attaches pmux-signature and pmux-timestamp headers to an HTTP request. No-op if secret is empty. Uses crypto/hmac, crypto/sha256, encoding/hex.

func SignWebSocketHeaders added in v0.0.2

func SignWebSocketHeaders(wsURL string, secret string) http.Header

SignWebSocketHeaders returns HTTP headers with HMAC signature for a WebSocket URL. Returns nil if secret is empty. Extracts path from the URL.

func TruncateMobileName

func TruncateMobileName(name string) string

TruncateMobileName truncates a mobile device name to MaxMobileNameLen runes. It operates on Unicode code points to avoid splitting multi-byte characters.

func UpdatePairedDeviceName

func UpdatePairedDeviceName(path string, store SecretStore, deviceID, name string) (bool, error)

UpdatePairedDeviceName updates the name of a paired device if it matches the given device ID and the name has actually changed. Returns true if the name was updated, false otherwise.

func ValidateDeviceID

func ValidateDeviceID(id string) error

ValidateDeviceID checks that a device ID matches the expected format produced by deriveDeviceID: exactly 32 lowercase hex characters (SHA-256 first 16 bytes). Returns an error describing the validation failure, or nil if valid.

Types

type FileSecretStore

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

FileSecretStore stores secrets in an encrypted file on disk. Encryption uses XChaCha20-Poly1305 with a key derived via Argon2id from the machine's unique identifier.

func NewFileSecretStore

func NewFileSecretStore(keysDir string, logger *slog.Logger) *FileSecretStore

NewFileSecretStore creates an encrypted file-backed secret store. The secrets file is stored in the given directory as "secrets.enc".

func (*FileSecretStore) Backend

func (f *FileSecretStore) Backend() string

Backend returns "encrypted-file".

func (*FileSecretStore) Delete

func (f *FileSecretStore) Delete(key string) error

Delete removes a secret from the encrypted file.

func (*FileSecretStore) Get

func (f *FileSecretStore) Get(key string) ([]byte, error)

Get retrieves a secret from the encrypted file.

func (*FileSecretStore) Set

func (f *FileSecretStore) Set(key string, data []byte) error

Set stores a secret in the encrypted file.

type HMACRejectedError added in v0.0.3

type HMACRejectedError struct {
	ServerURL string // signaling server base URL
	ServerMsg string // raw error from server (e.g., "missing client signature")
}

HMACRejectedError indicates the signaling server rejected the agent's client signature. This is a permanent failure — the agent binary was built without the correct HMAC secret and retrying will not help.

func (*HMACRejectedError) Error added in v0.0.3

func (e *HMACRejectedError) Error() string

type Identity

type Identity struct {
	PrivateKey       ed25519.PrivateKey
	Ed25519PublicKey ed25519.PublicKey
	DeviceID         string // hex-encoded SHA-256 fingerprint of public key (first 16 bytes)
}

Identity holds an Ed25519 keypair and the derived device ID.

func GenerateIdentity

func GenerateIdentity(keysDir string, store SecretStore) (*Identity, error)

GenerateIdentity creates a new Ed25519 keypair, stores the private key in the SecretStore, and saves the public key to keysDir on disk.

func LoadIdentity

func LoadIdentity(keysDir string, store SecretStore, logger *slog.Logger) (*Identity, error)

LoadIdentity loads an existing Ed25519 keypair. The private key is retrieved from the SecretStore and the public key is read from keysDir on disk.

func (*Identity) Ed25519PublicKeyBase64

func (id *Identity) Ed25519PublicKeyBase64() string

Ed25519PublicKeyBase64 returns the base64-encoded Ed25519 public key for server registration.

func (*Identity) SignChallenge

func (id *Identity) SignChallenge(deviceID string, timestamp string) string

SignChallenge signs the token exchange challenge: deviceId + timestamp. Returns the base64-encoded signature.

type KeyringSecretStore

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

KeyringSecretStore stores secrets in the system keychain/keyring. On macOS this uses Keychain, on Linux it uses the D-Bus Secret Service API (GNOME Keyring, KWallet, etc.).

func NewKeyringSecretStore

func NewKeyringSecretStore() *KeyringSecretStore

NewKeyringSecretStore creates a keyring-backed secret store.

func (*KeyringSecretStore) Backend

func (k *KeyringSecretStore) Backend() string

Backend returns the name of the keyring backend.

func (*KeyringSecretStore) Delete

func (k *KeyringSecretStore) Delete(key string) (err error)

Delete removes a secret from the system keyring. Returns nil if the secret does not exist.

Panics from the D-Bus backend are recovered and returned as errors.

func (*KeyringSecretStore) Get

func (k *KeyringSecretStore) Get(key string) (data []byte, err error)

Get retrieves a secret from the system keyring. The stored value is base64-decoded since go-keyring only supports strings.

Panics from the D-Bus backend are recovered and returned as errors.

func (*KeyringSecretStore) Set

func (k *KeyringSecretStore) Set(key string, data []byte) (err error)

Set stores a secret in the system keyring. The data is base64-encoded since go-keyring only supports strings.

Panics from the D-Bus backend are recovered and returned as errors.

type MemorySecretStore

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

MemorySecretStore is an in-memory SecretStore for testing.

func NewMemorySecretStore

func NewMemorySecretStore() *MemorySecretStore

NewMemorySecretStore creates a new in-memory secret store.

func (*MemorySecretStore) Backend

func (m *MemorySecretStore) Backend() string

Backend returns "memory".

func (*MemorySecretStore) Delete

func (m *MemorySecretStore) Delete(key string) error

Delete removes a secret from memory.

func (*MemorySecretStore) Get

func (m *MemorySecretStore) Get(key string) ([]byte, error)

Get retrieves a secret from memory.

func (*MemorySecretStore) Set

func (m *MemorySecretStore) Set(key string, data []byte) error

Set stores a secret in memory.

type PairCompleteMessage

type PairCompleteMessage struct {
	Type                  string `json:"type"`
	MobileDeviceID        string `json:"mobileDeviceId"`
	MobileX25519PublicKey string `json:"mobileX25519PublicKey"`
	MobileName            string `json:"mobileName,omitempty"`
}

PairCompleteMessage is the WebSocket message relayed when the mobile completes pairing.

func WaitForPairComplete

func WaitForPairComplete(ctx context.Context, serverURL string, jwt string, hmacSecret string) (*PairCompleteMessage, error)

WaitForPairComplete connects to the server WebSocket, authenticates, and waits for the pair_complete message. Returns the mobile's X25519 public key and device ID. The context can be used for timeout/cancellation.

type PairInitiateResponse

type PairInitiateResponse struct {
	PairingCode string `json:"pairingCode"`
	Error       string `json:"error,omitempty"`
}

PairInitiateResponse is the server response from POST /auth/pair/initiate.

func InitiatePairing

func InitiatePairing(id *Identity, x25519PubKeyBase64 string, serverURL string, client *http.Client, name string, hmacSecret string) (*PairInitiateResponse, error)

InitiatePairing calls the server to create a pairing session. The name parameter is an optional human-readable host name sent to the server so it can be displayed on paired mobile devices.

type PairedDevice

type PairedDevice struct {
	DeviceID     string    `json:"deviceId"`
	Name         string    `json:"name,omitempty"`
	SharedSecret string    `json:"-"` // base64-encoded X25519 shared secret (stored in SecretStore, not on disk)
	PairedAt     time.Time `json:"pairedAt"`
	// LastSeen is an int64 Unix timestamp (not time.Time) so that the zero
	// value 0 cleanly means "never seen" and omitempty suppresses it in JSON.
	// This matches the mobile side which stores lastSeen as a numeric timestamp.
	LastSeen int64 `json:"lastSeen,omitempty"`
}

PairedDevice stores information about a paired mobile device.

func LoadPairedDevice

func LoadPairedDevice(path string, store SecretStore) (*PairedDevice, error)

LoadPairedDevice returns the single paired device, or nil if none.

func LoadPairedDevices

func LoadPairedDevices(path string, store SecretStore) ([]PairedDevice, error)

LoadPairedDevices reads the paired devices list from disk and retrieves shared secrets from the SecretStore.

type SecretStore

type SecretStore interface {
	// Get retrieves a secret by key. Returns ErrSecretNotFound if the key does not exist.
	Get(key string) ([]byte, error)

	// Set stores a secret under the given key. Overwrites any existing value.
	Set(key string, data []byte) error

	// Delete removes a secret by key. Returns nil if the key does not exist.
	Delete(key string) error

	// Backend returns the name of the active storage backend
	// (e.g., "keychain", "secret-service", "encrypted-file", "memory").
	Backend() string
}

SecretStore provides secure storage for cryptographic secrets. Implementations may use the system keychain, encrypted files, or in-memory storage.

func NewSecretStore

func NewSecretStore(keysDir string, backendPref string, logger *slog.Logger) (SecretStore, error)

NewSecretStore creates a SecretStore based on the backend preference.

Supported values for backendPref:

  • "auto" (default): probe system keyring, fall back to encrypted file
  • "keyring": require system keyring, error if unavailable
  • "file": use encrypted file, skip keyring even if available

keysDir is the directory for the encrypted file fallback (e.g., ~/.config/pmux/keys/).

type X25519Keypair

type X25519Keypair struct {
	PrivateKey *ecdh.PrivateKey
	PublicKey  *ecdh.PublicKey
}

X25519Keypair holds an ephemeral X25519 keypair for key exchange during pairing.

func GenerateX25519Keypair

func GenerateX25519Keypair() (*X25519Keypair, error)

GenerateX25519Keypair creates a new ephemeral X25519 keypair for pairing.

func (*X25519Keypair) ComputeSharedSecret

func (kp *X25519Keypair) ComputeSharedSecret(peerPubKeyBase64 string) (string, error)

ComputeSharedSecret performs X25519 key exchange with the peer's public key. peerPubKeyBase64 is the base64-encoded X25519 public key from the peer. Returns the base64-encoded shared secret.

func (*X25519Keypair) PublicKeyBase64

func (kp *X25519Keypair) PublicKeyBase64() string

PublicKeyBase64 returns the X25519 public key as a base64-encoded string.

Jump to

Keyboard shortcuts

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