database

package
v0.0.0-...-14162ad Latest Latest
Warning

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

Go to latest
Published: May 4, 2026 License: MIT Imports: 23 Imported by: 0

Documentation

Overview

Package database provides a pure-Go SQLite-backed credential store.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Decrypt

func Decrypt(key, ciphertext []byte) ([]byte, error)

Decrypt decrypts ciphertext produced by Encrypt using AES-256-GCM. The key must be exactly 32 bytes.

func DecryptEntry

func DecryptEntry(masterKey, encryptedData, salt []byte) ([]byte, error)

DecryptEntry decrypts data produced by EncryptEntry.

func DefaultDBPath

func DefaultDBPath() (string, error)

DefaultDBPath returns the platform-appropriate path for the sesh database.

  • macOS: ~/Library/Application Support/sesh/passwords.db
  • Linux: $XDG_DATA_HOME/sesh/passwords.db (falls back to ~/.local/share/sesh/passwords.db; a relative $XDG_DATA_HOME is ignored per the XDG Base Directory spec)
  • Windows: %APPDATA%/sesh/passwords.db (falls back to ~/AppData/Roaming/sesh/passwords.db)

func DeriveKey

func DeriveKey(password, salt []byte, params Argon2idParams) []byte

DeriveKey uses Argon2id to derive an encryption key from a password and salt.

func Encrypt

func Encrypt(key, plaintext []byte) ([]byte, error)

Encrypt encrypts plaintext using AES-256-GCM with the provided key. The returned ciphertext is nonce || encrypted_data || tag. The key must be exactly 32 bytes; shorter keys are rejected rather than accepted as AES-128 or AES-192.

func EncryptEntry

func EncryptEntry(masterKey, plaintext []byte) (encryptedData, salt []byte, err error)

EncryptEntry encrypts plaintext for storage, generating a per-entry salt and deriving a per-entry key from the master key material + salt. Returns (encryptedData, salt, error).

func GenerateEncryptionKey

func GenerateEncryptionKey() ([]byte, error)

GenerateEncryptionKey creates a new random 256-bit encryption key.

func GenerateSalt

func GenerateSalt(length int) ([]byte, error)

GenerateSalt produces a cryptographically random salt of the given length.

Types

type Argon2idParams

type Argon2idParams struct {
	Time    uint32 `json:"time"`    // iterations
	Memory  uint32 `json:"memory"`  // KiB
	Threads uint8  `json:"threads"` // parallelism
	KeyLen  uint32 `json:"key_len"` // derived key length in bytes
}

Argon2idParams holds the tuning parameters for Argon2id key derivation.

func DefaultArgon2idParams

func DefaultArgon2idParams() Argon2idParams

DefaultArgon2idParams returns production-grade Argon2id parameters. Time=3, Memory=64 MiB, Threads=4, KeyLen=32 (AES-256).

func UnmarshalArgon2idParams

func UnmarshalArgon2idParams(data string) (Argon2idParams, error)

UnmarshalArgon2idParams deserialises Argon2id parameters from JSON.

func (Argon2idParams) MarshalParams

func (p Argon2idParams) MarshalParams() string

MarshalParams serialises Argon2id parameters to JSON for storage in key_metadata. The struct is composed of fixed-width integers, so json.Marshal cannot fail for any valid Argon2idParams value — a non-nil error here indicates a programming bug (e.g. someone added an unmarshalable field).

type AuditEntry

type AuditEntry struct {
	CreatedAt time.Time
	EventType string
	EntryID   string // nullable — empty for auth events
	Detail    string
	ID        int64
}

AuditEntry represents a row in the audit_log table.

type EntryType

type EntryType string

EntryType classifies what kind of credential is stored.

const (
	EntryTypePassword EntryType = "password"
	EntryTypeAPIKey   EntryType = "api_key"
	EntryTypeTOTP     EntryType = "totp"
	EntryTypeNote     EntryType = "secure_note"
	EntryTypeMFA      EntryType = "mfa_serial"
)

type KeyMetadata

type KeyMetadata struct {
	CreatedAt time.Time
	Algorithm string // "argon2id", "pbkdf2"
	Params    string // JSON: time, memory, threads (argon2id) or iterations (pbkdf2)
	Salt      []byte
	Version   int
	Active    bool
}

KeyMetadata stores key derivation parameters for a given key version. This table is readable without decryption so the store can derive the decryption key before reading any password entries.

type KeySource

type KeySource interface {
	// GetEncryptionKey returns the master encryption key.
	// The caller must zero the returned slice after use.
	GetEncryptionKey() ([]byte, error)

	// StoreEncryptionKey persists a new master encryption key.
	StoreEncryptionKey(key []byte) error

	// RequiresUserInput reports whether retrieving the key
	// needs interactive input (e.g. a master password prompt).
	RequiresUserInput() bool

	// Name returns a human-readable name for this key source.
	Name() string
}

KeySource abstracts where the master encryption key comes from. The keychain source is implemented now; a master-password source can be added later without changing the store.

type KeychainSource

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

KeychainSource retrieves and stores the master encryption key in the OS keychain.

func NewKeychainSource

func NewKeychainSource(kc keychainKeyProvider, account string) *KeychainSource

NewKeychainSource creates a KeychainSource that stores the encryption key under the given account in the OS keychain.

func (*KeychainSource) GetEncryptionKey

func (s *KeychainSource) GetEncryptionKey() ([]byte, error)

GetEncryptionKey reads the master key from the keychain. The stored value is a hex-encoded string of the raw 32-byte key. The keychain backend passes values through `security -i`, whose tokenizer splits on whitespace and control bytes — random binary keys regularly contain those bytes and fail to store, so we keep the at-rest form in ASCII-safe hex.

func (*KeychainSource) Name

func (s *KeychainSource) Name() string

func (*KeychainSource) RequiresUserInput

func (s *KeychainSource) RequiresUserInput() bool

func (*KeychainSource) StoreEncryptionKey

func (s *KeychainSource) StoreEncryptionKey(key []byte) error

StoreEncryptionKey persists the master key in the keychain. The raw 32-byte key is hex-encoded before storage so the resulting string is guaranteed to contain only [0-9a-f] — safe for the keychain backend's `security -i` text protocol. See GetEncryptionKey for context.

type MasterPasswordSource

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

MasterPasswordSource derives the encryption key from a user-supplied passphrase via Argon2id. The KDF salt and a verification blob are stored in a sidecar file alongside the DB — no keychain involvement.

func NewMasterPasswordSource

func NewMasterPasswordSource(dataDir string, prompt PasswordPromptFunc, opts ...Option) *MasterPasswordSource

NewMasterPasswordSource creates a MasterPasswordSource whose sidecar lives at <dataDir>/passwords.key — the canonical layout used by the CLI. Callers that need a non-canonical sidecar path (e.g., rotation staging) should use NewMasterPasswordSourceAtPath directly.

func NewMasterPasswordSourceAtPath

func NewMasterPasswordSourceAtPath(sidecarPath string, prompt PasswordPromptFunc, opts ...Option) *MasterPasswordSource

NewMasterPasswordSourceAtPath creates a MasterPasswordSource that reads and writes its sidecar at the given absolute path. Useful when staging a rotation: the source instance can point at passwords.key while a target instance points at passwords.key.new, both running concurrently without colliding.

func (*MasterPasswordSource) Close

func (s *MasterPasswordSource) Close()

Close zeroes and releases the cached key. Safe to call multiple times and safe to call concurrently with GetEncryptionKey. Bumping cacheEpoch fences any in-flight derivation from re-populating the cache after Close returns.

func (*MasterPasswordSource) GetEncryptionKey

func (s *MasterPasswordSource) GetEncryptionKey() ([]byte, error)

GetEncryptionKey prompts for the master password and derives the encryption key. On first run (no sidecar), it prompts twice for confirmation and creates the sidecar. On subsequent runs, it verifies the password against the stored verification blob. The derived key is cached for the lifetime of this source so repeated Get/Set operations within one invocation do not re-prompt.

Per the KeySource contract the caller is free to zero the returned slice — the cache holds a private copy. Call Close() to clear the cached key when done.

func (*MasterPasswordSource) Name

func (s *MasterPasswordSource) Name() string

func (*MasterPasswordSource) RequiresUserInput

func (s *MasterPasswordSource) RequiresUserInput() bool

func (*MasterPasswordSource) StoreEncryptionKey

func (s *MasterPasswordSource) StoreEncryptionKey(_ []byte) error

StoreEncryptionKey is a no-op for the master password source — the key is derived from the password, not stored directly.

type Option

type Option func(*MasterPasswordSource)

Option configures a MasterPasswordSource. Use with NewMasterPasswordSource.

func WithMaxAttempts

func WithMaxAttempts(n int) Option

WithMaxAttempts sets the maximum number of password prompts the unlock loop will issue before giving up. Values < 1 are clamped to 1. Only wrong-password failures are retried; sidecar/IO errors fail immediately.

The prompt callback must produce fresh user input on each invocation — a callback that returns a constant value (e.g., one backed by an env var) will derive the same wrong key N times and waste ~N × Argon2id cycles before failing. The CLI gates this via resolvePasswordPrompt in main.go, which only marks a prompt interactive when it actually reads fresh bytes; direct callers must apply the same discipline.

Only affects unlock(); first-run create+confirm always runs once.

type PasswordEntry

type PasswordEntry struct {
	CreatedAt     time.Time
	UpdatedAt     time.Time
	ID            string
	Service       string
	Account       string
	EntryType     EntryType
	Metadata      string
	EncryptedData []byte
	Salt          []byte
	KeyVersion    int
}

PasswordEntry represents a row in the passwords table.

type PasswordPromptFunc

type PasswordPromptFunc func(prompt string) ([]byte, error)

PasswordPromptFunc is called to obtain the master password from the user. Implementations should not echo the input.

type Store

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

Store is a SQLite-backed credential store that satisfies keychain.Provider.

func Open

func Open(dbPath string, ks KeySource) (*Store, error)

Open creates or opens the SQLite database at dbPath, runs any pending migrations, and returns a ready-to-use Store.

func (*Store) Close

func (s *Store) Close() error

Close releases the database connection and clears any cached key material held by the key source.

func (*Store) DeleteEntry

func (s *Store) DeleteEntry(account, service string) error

func (*Store) GetActiveKeyMetadata

func (s *Store) GetActiveKeyMetadata() (*KeyMetadata, error)

GetActiveKeyMetadata returns the currently active key metadata.

func (*Store) GetMFASerialBytes

func (s *Store) GetMFASerialBytes(account, profile string) ([]byte, error)

func (*Store) GetSecret

func (s *Store) GetSecret(account, service string) ([]byte, error)

func (*Store) GetSecretString

func (s *Store) GetSecretString(account, service string) (string, error)

func (*Store) InitKeyMetadata

func (s *Store) InitKeyMetadata() error

InitKeyMetadata creates the initial key metadata entry if none exists. Called during store initialisation when using a keychain key source.

func (*Store) ListEntries

func (s *Store) ListEntries(service string) (_ []keychain.KeychainEntry, err error)

func (*Store) SearchEntries

func (s *Store) SearchEntries(query string) (_ []keychain.KeychainEntry, err error)

SearchEntries performs a full-text search across service, account, and metadata using the FTS5 index. Returns matching KeychainEntry rows. An empty or whitespace-only query returns no results (FTS5 would reject it).

func (*Store) SetDescription

func (s *Store) SetDescription(service, account, description string) error

func (*Store) SetDescriptionAt

func (s *Store) SetDescriptionAt(service, account, description string, updatedAt time.Time) (err error)

SetDescriptionAt sets the description and stamps updated_at with the given timestamp. Zero falls back to the current time. Implements keychain.TimestampedStore.

func (*Store) SetSecret

func (s *Store) SetSecret(account, service string, secret []byte) error

func (*Store) SetSecretAt

func (s *Store) SetSecretAt(account, service string, secret []byte, createdAt, updatedAt time.Time) error

SetSecretAt stores a secret with explicit create/update timestamps. A zero timestamp falls back to the current time, matching SetSecret's behavior. Implements keychain.TimestampedStore.

func (*Store) SetSecretString

func (s *Store) SetSecretString(account, service, secret string) error

func (*Store) StoreKeyMetadata

func (s *Store) StoreKeyMetadata(meta *KeyMetadata) error

StoreKeyMetadata records key derivation parameters for the given key version.

Jump to

Keyboard shortcuts

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