security

package
v0.7.0 Latest Latest
Warning

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

Go to latest
Published: Apr 13, 2026 License: MIT Imports: 33 Imported by: 0

Documentation

Index

Constants

View Source
const (
	KDFAlgPBKDF2SHA256 = "pbkdf2-sha256"
	KDFAlgNone         = "none"
	WrapAlgAES256GCM   = "aes-256-gcm"
	WrapAlgKMSEnvelope = "kms-envelope"
)

KDF and wrap algorithm identifiers.

View Source
const (
	// KeySize is the size of AES-256 key in bytes.
	KeySize = 32
	// NonceSize is the size of GCM nonce in bytes.
	NonceSize = 12
	// SaltSize is the size of PBKDF2 salt in bytes.
	SaltSize = 16
	// Iterations is the PBKDF2 iteration count.
	Iterations = 100000
)
View Source
const (
	AlgorithmSecp256k1Keccak256 = "secp256k1-keccak256"
	AlgorithmEd25519            = "ed25519"
	AlgorithmMLDSA65            = "ml-dsa-65"
)

Algorithm identifier constants. These are the canonical source of truth for algorithm identifiers across the codebase.

View Source
const AlgorithmX25519MLKEM768 = "X25519-MLKEM768"

AlgorithmX25519MLKEM768 is the algorithm identifier for the hybrid X25519 + ML-KEM-768 key encapsulation mechanism (FIPS 203, NIST Level 3).

View Source
const EnvelopeVersion = 1

EnvelopeVersion is the current MasterKeyEnvelope format version.

View Source
const HybridSharedSecretSize = 64

HybridSharedSecretSize is the expected shared secret size from X25519-MLKEM768 (32 bytes ML-KEM-768 + 32 bytes X25519).

View Source
const PQSeedSize = mldsa65.SeedSize

PQSeedSize is the seed size for ML-DSA-65 key derivation (32 bytes).

View Source
const RecoveryMnemonicBits = 256

RecoveryMnemonicBits is the BIP39 entropy size that yields a 24-word mnemonic.

View Source
const SecurityConfigDefault = "default"

SecurityConfigDefault is the canonical row name for the primary salt/checksum pair used by bootstrap. Other callers may store additional rows under different names (e.g. per-context salts in session store tests).

Variables

View Source
var (
	ErrKeyNotFound      = errors.New("key not found")
	ErrNoEncryptionKeys = errors.New("no encryption keys available")
	ErrDecryptionFailed = errors.New("decryption failed")

	// Envelope errors
	ErrInvalidSlot     = errors.New("invalid KEK slot")
	ErrLastSlot        = errors.New("cannot remove last KEK slot")
	ErrUnwrapFailed    = errors.New("master key unwrap failed")
	ErrEnvelopeCorrupt = errors.New("master key envelope corrupted")
	ErrNoEnvelopeFile  = errors.New("envelope file not found")

	// KMS errors
	ErrKMSUnavailable     = errors.New("KMS service unavailable")
	ErrKMSAccessDenied    = errors.New("KMS access denied")
	ErrKMSKeyDisabled     = errors.New("KMS key is disabled")
	ErrKMSThrottled       = errors.New("KMS request throttled")
	ErrKMSInvalidKey      = errors.New("KMS invalid key")
	ErrPKCS11Module       = errors.New("PKCS#11 module error")
	ErrPKCS11Session      = errors.New("PKCS#11 session error")
	ErrKMSSlotUnavailable = errors.New("KMS KEK slot unavailable")
)
View Source
var Ed25519Scheme = &SignatureScheme{
	ID:            AlgorithmEd25519,
	Verify:        VerifyEd25519,
	SignatureSize: ed25519.SignatureSize,
	PublicKeySize: ed25519.PublicKeySize,
}

Ed25519Scheme describes the Ed25519 signature algorithm. Ed25519 is registered as a framework verification algorithm in Phase 2; it is not wired into production identity flows until Phase 3 (DID v2).

View Source
var MLDSA65Scheme = &SignatureScheme{
	ID:            AlgorithmMLDSA65,
	Verify:        VerifyMLDSA65,
	SignatureSize: mldsa65.SignatureSize,
	PublicKeySize: mldsa65.PublicKeySize,
}

MLDSA65Scheme describes the ML-DSA-65 (FIPS 204) post-quantum signature algorithm.

View Source
var Secp256k1Keccak256Scheme = &SignatureScheme{
	ID:            AlgorithmSecp256k1Keccak256,
	Verify:        VerifySecp256k1Keccak256,
	SignatureSize: 65,
	PublicKeySize: 33,
}

Secp256k1Keccak256Scheme describes the secp256k1+keccak256 signature algorithm.

Functions

func DeriveDBKey added in v0.7.0

func DeriveDBKey(mk []byte) []byte

DeriveDBKey derives the SQLCipher database encryption key from the Master Key. Uses HKDF-SHA256 with a domain-separated info label. Returns 32 raw bytes.

func DeriveDBKeyHex added in v0.7.0

func DeriveDBKeyHex(mk []byte) string

DeriveDBKeyHex returns DeriveDBKey(mk) hex-encoded for use with SQLCipher `PRAGMA key = "x'<hex>'"` (raw key mode).

func DeriveIdentityKey added in v0.7.0

func DeriveIdentityKey(mk []byte, generation uint32) ed25519.PrivateKey

DeriveIdentityKey derives an Ed25519 identity key from the Master Key using HKDF-SHA256 with a domain-separated info label. The generation parameter allows key rotation: generation 0 is the default, higher values produce different keys from the same MK.

Same MK + same generation always produces the same Ed25519 key (deterministic). MK recovery (via mnemonic) recovers the identity key.

func DeriveKEK added in v0.7.0

func DeriveKEK(secret string, slot *KEKSlot) ([]byte, error)

DeriveKEK derives a Key Encryption Key from a secret using the slot's KDF parameters. The caller is responsible for providing the correct secret (passphrase or mnemonic).

func DerivePQKeyFromSeed added in v0.7.0

func DerivePQKeyFromSeed(seed []byte) (*mldsa65.PublicKey, *mldsa65.PrivateKey)

DerivePQKeyFromSeed creates an ML-DSA-65 keypair from a 32-byte seed.

func DerivePQSigningKey added in v0.7.0

func DerivePQSigningKey(mk []byte, generation uint32) (*mldsa65.PublicKey, *mldsa65.PrivateKey)

DerivePQSigningKey derives an ML-DSA-65 keypair from the Master Key using HKDF-SHA256. The generation parameter allows key rotation from the same MK.

Domain separation: the info label "lango-pq-signing-mldsa65[:generation]" is independent from the Ed25519 identity key domain "lango-identity-ed25519".

func DerivePQSigningSeed added in v0.7.0

func DerivePQSigningSeed(mk []byte, generation uint32) []byte

DerivePQSigningSeed derives the 32-byte ML-DSA-65 seed from the Master Key using HKDF-SHA256. The caller should pass this to DerivePQKeyFromSeed to get the full keypair. Separated for bootstrap: store the seed, derive lazily.

func DeriveSessionKey added in v0.7.0

func DeriveSessionKey(sharedSecret []byte, initiatorDID, responderDID string) ([]byte, error)

DeriveSessionKey derives a 32-byte AES-256 session key from the hybrid KEM shared secret using HKDF-SHA256.

Parameters:

  • sharedSecret: 64 bytes from hybrid KEM (ML-KEM-768 SS || X25519 SS)
  • initiatorDID: DID of the handshake initiator (from selected signer)
  • responderDID: DID of the handshake responder (from selected signer)

The info parameter provides domain separation and binds the key to the specific peer pair. Both sides MUST use the same DID ordering (initiator first, responder second) to derive identical keys.

func EnvelopeFilePath added in v0.7.0

func EnvelopeFilePath(langoDir string) string

EnvelopeFilePath returns the absolute path of the envelope file for the given lango dir.

func GenerateMasterKey added in v0.7.0

func GenerateMasterKey() ([]byte, error)

GenerateMasterKey returns 32 cryptographically random bytes for use as a Master Key.

func GenerateRecoveryMnemonic added in v0.7.0

func GenerateRecoveryMnemonic() (string, error)

GenerateRecoveryMnemonic returns a fresh 24-word BIP39 mnemonic. The returned string contains all information needed to re-derive a KEK; callers MUST display it to the user and ensure it is recorded before persisting the associated envelope slot.

func HasEnvelopeFile added in v0.7.0

func HasEnvelopeFile(langoDir string) bool

HasEnvelopeFile reports whether an envelope file exists under langoDir. I/O errors (other than not-exist) are reported as "exists=false" to keep the signature simple; callers that need to distinguish should use LoadEnvelopeFile.

func HybridKEMScheme added in v0.7.0

func HybridKEMScheme() kem.Scheme

HybridKEMScheme returns the X25519-MLKEM768 hybrid KEM scheme from circl.

func IsTransient

func IsTransient(err error) bool

IsTransient reports whether err is a transient KMS error eligible for retry.

func KEMEncapsulate added in v0.7.0

func KEMEncapsulate(peerPubKeyBytes []byte) (ct, ss []byte, err error)

KEMEncapsulate takes a peer's serialized KEM public key and returns (ciphertext, sharedSecret). The shared secret is 64 bytes for X25519-MLKEM768 (32 bytes ML-KEM-768 || 32 bytes X25519).

func RetryMigration added in v0.7.0

func RetryMigration(ctx context.Context, client *ent.Client, mk []byte, passphrase string, oldSalt []byte) error

RetryMigration re-runs the data re-encryption phase using an already-unwrapped MK. Used by bootstrap when a previous migration crashed after envelope write but before data re-encryption completed. The caller is responsible for clearing PendingMigration and persisting the envelope afterwards.

func RetryRekey added in v0.7.0

func RetryRekey(db *sql.DB, mk []byte) error

RetryRekey retries `PRAGMA rekey` using the MK-derived raw key. Used when the previous migration crashed between data re-encryption and rekey.

func SignMLDSA65 added in v0.7.0

func SignMLDSA65(sk *mldsa65.PrivateKey, message []byte) ([]byte, error)

SignMLDSA65 signs a message with an ML-DSA-65 private key. Uses deterministic signing (randomized=false) for reproducibility.

func StoreEnvelopeFile added in v0.7.0

func StoreEnvelopeFile(langoDir string, env *MasterKeyEnvelope) error

StoreEnvelopeFile writes the envelope to <langoDir>/envelope.json atomically. The file is created with 0600 permissions. Uses write-to-temp-and-rename to avoid leaving a partial file on crash.

func UnwrapMasterKey added in v0.7.0

func UnwrapMasterKey(wrapped, nonce, kek []byte) ([]byte, error)

UnwrapMasterKey verifies the GCM tag and recovers the Master Key. Wraps failures in ErrUnwrapFailed so callers can match with errors.Is.

func ValidateMnemonic added in v0.7.0

func ValidateMnemonic(mnemonic string) error

ValidateMnemonic returns nil if the mnemonic is valid BIP39. It does NOT verify that the mnemonic matches any envelope slot.

func VerifyEd25519 added in v0.7.0

func VerifyEd25519(publicKey, message, signature []byte) error

VerifyEd25519 verifies an Ed25519 signature against a public key and message. Ed25519 handles hashing internally (SHA-512), so the message is passed as-is.

func VerifyMLDSA65 added in v0.7.0

func VerifyMLDSA65(publicKey, message, signature []byte) error

VerifyMLDSA65 verifies an ML-DSA-65 signature against a public key and message.

func VerifySecp256k1Keccak256 added in v0.7.0

func VerifySecp256k1Keccak256(publicKey, message, signature []byte) error

VerifySecp256k1Keccak256 verifies a secp256k1+keccak256 ECDSA signature. It hashes the message with Keccak256, recovers the public key from the 65-byte signature (R+S+V), and compares with the claimed compressed key.

func WrapMasterKey added in v0.7.0

func WrapMasterKey(mk, kek []byte) (wrapped, nonce []byte, err error)

WrapMasterKey wraps the Master Key with the given KEK using AES-256-GCM. Returns the ciphertext and nonce separately.

func ZeroBytes added in v0.7.0

func ZeroBytes(b []byte)

ZeroBytes overwrites every byte in b with 0x00. Exported so wallet, p2p, and other packages can share a single implementation instead of maintaining private copies.

Types

type CompositeCryptoProvider

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

CompositeCryptoProvider implements CryptoProvider with fallback logic. It tries the primary provider first (typically companion), then falls back to local.

func NewCompositeCryptoProvider

func NewCompositeCryptoProvider(primary CryptoProvider, fallback CryptoProvider, checker ConnectionChecker) *CompositeCryptoProvider

NewCompositeCryptoProvider creates a new CompositeCryptoProvider.

func (*CompositeCryptoProvider) Decrypt

func (c *CompositeCryptoProvider) Decrypt(ctx context.Context, keyID string, ciphertext []byte) ([]byte, error)

Decrypt implements CryptoProvider.

func (*CompositeCryptoProvider) Encrypt

func (c *CompositeCryptoProvider) Encrypt(ctx context.Context, keyID string, plaintext []byte) ([]byte, error)

Encrypt implements CryptoProvider.

func (*CompositeCryptoProvider) Sign

func (c *CompositeCryptoProvider) Sign(ctx context.Context, keyID string, payload []byte) ([]byte, error)

Sign implements CryptoProvider.

func (*CompositeCryptoProvider) UsedLocal

func (c *CompositeCryptoProvider) UsedLocal() bool

UsedLocal returns true if the last operation used the local fallback.

type ConnectionChecker

type ConnectionChecker interface {
	IsConnected() bool
}

ConnectionChecker provides connection status checking.

type CryptoProvider

type CryptoProvider interface {
	// Sign generates a signature for the given payload using the specified key ID.
	Sign(ctx context.Context, keyID string, payload []byte) ([]byte, error)
	// Encrypt encrypts the given plaintext using the specified key ID.
	Encrypt(ctx context.Context, keyID string, plaintext []byte) ([]byte, error)
	// Decrypt decrypts the given ciphertext using the specified key ID.
	Decrypt(ctx context.Context, keyID string, ciphertext []byte) ([]byte, error)
}

CryptoProvider defines the interface for cryptographic operations.

func NewKMSProvider

func NewKMSProvider(providerName KMSProviderName, kmsConfig config.KMSConfig) (CryptoProvider, error)

NewKMSProvider creates a CryptoProvider for the named KMS backend. Supported providers: "aws-kms", "gcp-kms", "azure-kv", "pkcs11". Build tags control which providers are compiled in; uncompiled providers return a descriptive error.

type DecryptRequest

type DecryptRequest struct {
	ID         string `json:"id"`
	KeyID      string `json:"keyId"`
	Ciphertext []byte `json:"ciphertext"`
}

DecryptRequest represents the payload for decryption.

type DecryptResponse

type DecryptResponse struct {
	ID        string `json:"id"`
	Plaintext []byte `json:"plaintext"`
	Error     string `json:"error,omitempty"`
}

DecryptResponse represents the payload for decryption response.

type EncryptRequest

type EncryptRequest struct {
	ID        string `json:"id"`
	KeyID     string `json:"keyId"`
	Plaintext []byte `json:"plaintext"`
}

EncryptRequest represents the payload for encryption.

type EncryptResponse

type EncryptResponse struct {
	ID         string `json:"id"`
	Ciphertext []byte `json:"ciphertext"`
	Error      string `json:"error,omitempty"`
}

EncryptResponse represents the payload for encryption response.

type KDFParams added in v0.7.0

type KDFParams struct {
	Iterations int `json:"iterations,omitempty"` // PBKDF2
	Memory     int `json:"memory,omitempty"`     // Argon2id (KiB)
	Time       int `json:"time,omitempty"`       // Argon2id
	Threads    int `json:"threads,omitempty"`    // Argon2id
}

KDFParams carries algorithm-specific KDF parameters.

func NewDefaultKDFParams added in v0.7.0

func NewDefaultKDFParams() KDFParams

NewDefaultKDFParams returns PBKDF2 parameters matching the current security baseline.

type KEKSlot added in v0.7.0

type KEKSlot struct {
	ID        string      `json:"id"`
	Type      KEKSlotType `json:"type"`
	KDFAlg    string      `json:"kdf_alg"`
	KDFParams KDFParams   `json:"kdf_params"`
	WrapAlg   string      `json:"wrap_alg"`
	Domain    string      `json:"domain"`
	Salt      []byte      `json:"salt"`
	WrappedMK []byte      `json:"wrapped_mk"`
	Nonce     []byte      `json:"nonce"`
	CreatedAt time.Time   `json:"created_at"`
	Label     string      `json:"label,omitempty"`

	// KMS-specific fields (populated only for KEKSlotHardware slots).
	KMSProvider string `json:"kms_provider,omitempty"` // "aws-kms", "gcp-kms", "azure-kv", "pkcs11"
	KMSKeyID    string `json:"kms_key_id,omitempty"`   // KMS key identifier
}

KEKSlot is a single envelope slot that can independently unwrap the Master Key.

type KEKSlotType added in v0.7.0

type KEKSlotType string

KEKSlotType identifies the key-derivation source for a KEK slot.

const (
	KEKSlotPassphrase   KEKSlotType = "passphrase"
	KEKSlotMnemonic     KEKSlotType = "mnemonic"
	KEKSlotRecoveryFile KEKSlotType = "recovery_file" // reserved for follow-up
	KEKSlotHardware     KEKSlotType = "hardware"      // reserved for follow-up
)

type KEMDecapsulator added in v0.7.0

type KEMDecapsulator func(ciphertext []byte) (sharedSecret []byte, err error)

KEMDecapsulator recovers a shared secret from a KEM ciphertext. Returned by GenerateEphemeralKEM as a closure capturing the private key. The caller MUST NOT persist this function — it holds an ephemeral private key that should be discarded after a single handshake.

func GenerateEphemeralKEM added in v0.7.0

func GenerateEphemeralKEM() (pubKeyBytes []byte, decap KEMDecapsulator, err error)

GenerateEphemeralKEM generates an ephemeral KEM keypair and returns the serialized public key and a decapsulator closure. The private key is captured inside the closure and never leaves the security package.

The caller MUST NOT persist the decapsulator — it is ephemeral per-handshake. After the handshake completes, the closure (and its captured private key) becomes unreferenced and is garbage collected.

type KMSError

type KMSError struct {
	Provider string
	Op       string
	KeyID    string
	Err      error
}

KMSError wraps a KMS operation error with context.

func (*KMSError) Error

func (e *KMSError) Error() string

func (*KMSError) Unwrap

func (e *KMSError) Unwrap() error

type KMSHealthChecker

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

KMSHealthChecker implements ConnectionChecker for KMS providers. It caches the connection status with a configurable probe interval.

func NewKMSHealthChecker

func NewKMSHealthChecker(provider CryptoProvider, testKeyID string, probeInterval time.Duration) *KMSHealthChecker

NewKMSHealthChecker creates a health checker that probes the KMS provider by attempting a small encrypt/decrypt roundtrip on probeInterval.

func (*KMSHealthChecker) IsConnected

func (h *KMSHealthChecker) IsConnected() bool

IsConnected implements ConnectionChecker. Returns the cached result if fresh, otherwise performs a synchronous probe.

type KMSProviderName

type KMSProviderName string

KMSProviderName identifies a supported KMS backend.

const (
	KMSProviderAWS    KMSProviderName = "aws-kms"
	KMSProviderGCP    KMSProviderName = "gcp-kms"
	KMSProviderAzure  KMSProviderName = "azure-kv"
	KMSProviderPKCS11 KMSProviderName = "pkcs11"
)

func (KMSProviderName) Valid

func (n KMSProviderName) Valid() bool

Valid reports whether n is a recognised KMS provider name.

type KeyInfo

type KeyInfo struct {
	ID          uuid.UUID
	Name        string
	RemoteKeyID string
	Type        KeyType
	CreatedAt   time.Time
	LastUsedAt  *time.Time
}

KeyInfo represents key metadata.

type KeyRegistry

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

KeyRegistry manages encryption/signing keys.

func NewKeyRegistry

func NewKeyRegistry(client *ent.Client) *KeyRegistry

NewKeyRegistry creates a new KeyRegistry.

func (*KeyRegistry) DeleteKey

func (r *KeyRegistry) DeleteKey(ctx context.Context, name string) error

DeleteKey removes a key by name.

func (*KeyRegistry) GetDefaultKey

func (r *KeyRegistry) GetDefaultKey(ctx context.Context) (*KeyInfo, error)

GetDefaultKey retrieves the default encryption key (most recently created).

func (*KeyRegistry) GetKey

func (r *KeyRegistry) GetKey(ctx context.Context, name string) (*KeyInfo, error)

GetKey retrieves a key by name.

func (*KeyRegistry) ListKeys

func (r *KeyRegistry) ListKeys(ctx context.Context) ([]*KeyInfo, error)

ListKeys returns all registered keys.

func (*KeyRegistry) RegisterKey

func (r *KeyRegistry) RegisterKey(ctx context.Context, name, remoteKeyID string, keyType KeyType) (*KeyInfo, error)

RegisterKey registers a new key.

func (*KeyRegistry) UpdateLastUsed

func (r *KeyRegistry) UpdateLastUsed(ctx context.Context, name string) error

UpdateLastUsed updates the last used timestamp for a key.

type KeyType

type KeyType string

KeyType represents the purpose of a key.

const (
	KeyTypeEncryption KeyType = "encryption"
	KeyTypeSigning    KeyType = "signing"
)

func (KeyType) Valid

func (t KeyType) Valid() bool

Valid reports whether t is a known key type.

func (KeyType) Values

func (t KeyType) Values() []KeyType

Values returns all known key types.

type LocalCryptoProvider

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

LocalCryptoProvider implements CryptoProvider using local AES-256-GCM encryption.

Two initialization modes are supported:

  • Envelope mode (preferred): the Master Key is unwrapped from a MasterKeyEnvelope slot and installed as keys["local"]. All data encryption uses the MK directly. This mode supports key rotation (change-passphrase), recovery mnemonics, and DB key derivation via DeriveDBKey.

  • Legacy mode: the key is derived directly from a passphrase via PBKDF2 and stored in keys["local"]. This mode is kept for backward compatibility and is replaced by envelope mode during migration.

In both modes, Encrypt/Decrypt/Sign use the same keys["local"] lookup, so consumers (SecretsStore, ConfigStore, tools) are unaware of the underlying source of the key.

func NewLocalCryptoProvider

func NewLocalCryptoProvider() *LocalCryptoProvider

NewLocalCryptoProvider creates a new LocalCryptoProvider.

func (*LocalCryptoProvider) CalculateChecksum

func (p *LocalCryptoProvider) CalculateChecksum(passphrase string, salt []byte) []byte

CalculateChecksum computes the checksum for a given passphrase and salt. Uses HMAC-SHA256 with salt as key to avoid length extension attacks. NOTE: Changing this algorithm requires migrating existing stored checksums.

func (*LocalCryptoProvider) Close added in v0.7.0

func (p *LocalCryptoProvider) Close()

Close zeroes all key material held by the provider. After Close, the provider is unusable and must not be referenced.

func (*LocalCryptoProvider) Decrypt

func (p *LocalCryptoProvider) Decrypt(ctx context.Context, keyID string, ciphertext []byte) ([]byte, error)

Decrypt decrypts data using AES-256-GCM.

func (*LocalCryptoProvider) Encrypt

func (p *LocalCryptoProvider) Encrypt(ctx context.Context, keyID string, plaintext []byte) ([]byte, error)

Encrypt encrypts data using AES-256-GCM.

func (*LocalCryptoProvider) Envelope added in v0.7.0

func (p *LocalCryptoProvider) Envelope() *MasterKeyEnvelope

Envelope returns the currently installed envelope, or nil in legacy mode. Callers MUST NOT mutate the returned envelope without acquiring exclusive access.

func (*LocalCryptoProvider) Initialize

func (p *LocalCryptoProvider) Initialize(passphrase string) error

Initialize sets up the provider with a passphrase using the legacy direct-key model. The passphrase is used to derive an encryption key via PBKDF2. Marks the provider as legacy — envelope-based bootstrap should use InitializeNewEnvelope or InitializeWithEnvelope instead.

func (*LocalCryptoProvider) InitializeNewEnvelope added in v0.7.0

func (p *LocalCryptoProvider) InitializeNewEnvelope(passphrase string) (*MasterKeyEnvelope, error)

InitializeNewEnvelope generates a fresh envelope from a passphrase and installs the Master Key as the local key. Returns the envelope so the caller can persist it via StoreEnvelopeFile.

func (*LocalCryptoProvider) InitializeWithEnvelope added in v0.7.0

func (p *LocalCryptoProvider) InitializeWithEnvelope(mk []byte, envelope *MasterKeyEnvelope) error

InitializeWithEnvelope installs an already-unwrapped Master Key as the local key. The provider takes ownership of the MK bytes and zeroes them in Close(). The envelope reference is kept for diagnostics (SlotCount, recovery status).

func (*LocalCryptoProvider) InitializeWithSalt

func (p *LocalCryptoProvider) InitializeWithSalt(passphrase string, salt []byte) error

InitializeWithSalt sets up the provider with existing salt (legacy direct-key model).

func (*LocalCryptoProvider) IsInitialized

func (p *LocalCryptoProvider) IsInitialized() bool

IsInitialized returns true if the provider has been initialized.

func (*LocalCryptoProvider) IsLegacy added in v0.7.0

func (p *LocalCryptoProvider) IsLegacy() bool

IsLegacy reports whether the provider was initialized via the legacy direct-passphrase-derived-key path.

func (*LocalCryptoProvider) MasterKey added in v0.7.0

func (p *LocalCryptoProvider) MasterKey() []byte

MasterKey returns a copy of the unwrapped Master Key, or nil in legacy mode. Callers MUST ZeroBytes the returned slice when done.

func (*LocalCryptoProvider) Salt

func (p *LocalCryptoProvider) Salt() []byte

Salt returns the current salt for persistence.

func (*LocalCryptoProvider) Sign

func (p *LocalCryptoProvider) Sign(ctx context.Context, keyID string, payload []byte) ([]byte, error)

Sign generates a signature using HMAC-SHA256 (local signing).

type MasterKeyEnvelope added in v0.7.0

type MasterKeyEnvelope struct {
	Version          int       `json:"version"`
	Slots            []KEKSlot `json:"slots"`
	PendingMigration bool      `json:"pending_migration,omitempty"`
	PendingRekey     bool      `json:"pending_rekey,omitempty"`
	CreatedAt        time.Time `json:"created_at"`
	UpdatedAt        time.Time `json:"updated_at"`
}

MasterKeyEnvelope holds the wrapped Master Key across one or more KEK slots. Persisted as JSON at <LangoDir>/envelope.json with 0600 permissions.

func LoadEnvelopeFile added in v0.7.0

func LoadEnvelopeFile(langoDir string) (*MasterKeyEnvelope, error)

LoadEnvelopeFile reads and parses <langoDir>/envelope.json. Returns (nil, nil) if the file does not exist (fresh install or legacy layout). Returns a wrapped ErrEnvelopeCorrupt on parse failure.

func MigrateToEnvelope added in v0.7.0

func MigrateToEnvelope(
	ctx context.Context,
	db *sql.DB,
	client *ent.Client,
	langoDir string,
	passphrase string,
	oldSalt, oldChecksum []byte,
	dbEncrypted bool,
) (*MasterKeyEnvelope, []byte, error)

MigrateToEnvelope performs a one-time legacy → envelope migration.

Flow (SQLCipher):

  1. Derive legacy key via PBKDF2(passphrase, oldSalt, …) and verify checksum.
  2. Generate a fresh MK and build an envelope with PendingMigration=true (and PendingRekey=true if the DB is SQLCipher-encrypted). Persist the envelope file BEFORE touching any data so a crash leaves a discoverable marker.
  3. PRAGMA wal_checkpoint(TRUNCATE) + VACUUM INTO <dbpath>.pre-migration for a WAL-safe backup.
  4. Re-encrypt every secret and config profile row inside a single ent transaction. Count-verify before/after.
  5. Clear PendingMigration and persist the envelope.
  6. PRAGMA rekey to MK-derived raw key (SQLCipher only).
  7. Clear PendingRekey and persist the envelope.

Returns (envelope, mk, error). The caller takes ownership of the raw MK and must ZeroBytes it when done.

func NewEnvelope added in v0.7.0

func NewEnvelope(passphrase string) (*MasterKeyEnvelope, []byte, error)

NewEnvelope generates a fresh Master Key, wraps it with a passphrase-derived KEK, and returns a new envelope alongside the raw MK. Callers MUST zero the returned MK with ZeroBytes when done.

func (*MasterKeyEnvelope) AddKMSSlot added in v0.7.0

func (e *MasterKeyEnvelope) AddKMSSlot(
	ctx context.Context,
	label string,
	mk []byte,
	provider CryptoProvider,
	kmsProviderName string,
	kmsKeyID string,
) error

AddKMSSlot wraps the MK using the CryptoProvider's Encrypt and stores the ciphertext as a KMS KEK slot. The KMS provider handles all wrapping internally (nonces, key material), so no local KDF or GCM nonce is needed.

func (*MasterKeyEnvelope) AddSlot added in v0.7.0

func (e *MasterKeyEnvelope) AddSlot(slotType KEKSlotType, label string, mk []byte, secret string, params KDFParams) error

AddSlot adds a new KEK slot that wraps the provided MK. The caller must hold the unwrapped MK in memory. The MK is not modified.

func (*MasterKeyEnvelope) ChangePassphraseSlot added in v0.7.0

func (e *MasterKeyEnvelope) ChangePassphraseSlot(mk []byte, newPassphrase string) error

ChangePassphraseSlot replaces the first passphrase slot (or adds one if missing) with a new KEK derived from newPassphrase. The MK is unchanged and all non-passphrase slots remain intact.

func (*MasterKeyEnvelope) HasSlotType added in v0.7.0

func (e *MasterKeyEnvelope) HasSlotType(slotType KEKSlotType) bool

HasSlotType reports whether the envelope contains at least one slot of the given type.

func (*MasterKeyEnvelope) RemoveSlot added in v0.7.0

func (e *MasterKeyEnvelope) RemoveSlot(id string) error

RemoveSlot removes the slot with the given ID. Returns ErrLastSlot if removal would leave zero slots.

func (*MasterKeyEnvelope) SlotCount added in v0.7.0

func (e *MasterKeyEnvelope) SlotCount() int

SlotCount returns the number of KEK slots.

func (*MasterKeyEnvelope) UnwrapFromKMS added in v0.7.0

func (e *MasterKeyEnvelope) UnwrapFromKMS(
	ctx context.Context,
	provider CryptoProvider,
	providerName string,
	keyID string,
) ([]byte, string, error)

UnwrapFromKMS attempts to unwrap the MK using a KMS provider with 2-tier matching.

Tier 1: exact match — slots where KMSProvider == providerName AND KMSKeyID == keyID. Tier 2: provider-only — slots where KMSProvider == providerName (any keyID).

The provider is a bare KMS CryptoProvider. CompositeCryptoProvider with local fallback MUST NOT be used — if KMS fails, the caller should fall through to the next credential path (mnemonic/passphrase), not attempt local decryption.

Returns (mk, slotID, nil) on success. Callers MUST ZeroBytes the returned MK when done.

func (*MasterKeyEnvelope) UnwrapFromMnemonic added in v0.7.0

func (e *MasterKeyEnvelope) UnwrapFromMnemonic(mnemonic string) ([]byte, string, error)

UnwrapFromMnemonic attempts each mnemonic slot until one unwraps the MK.

func (*MasterKeyEnvelope) UnwrapFromPassphrase added in v0.7.0

func (e *MasterKeyEnvelope) UnwrapFromPassphrase(passphrase string) ([]byte, string, error)

UnwrapFromPassphrase attempts each passphrase slot in order until one unwraps the MK. Returns (mk, slotID, nil) on success or (nil, "", ErrUnwrapFailed). Callers MUST ZeroBytes the returned MK when done.

type RPCProvider

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

RPCProvider implements CryptoProvider using an asynchronous RPC mechanism.

func NewRPCProvider

func NewRPCProvider() *RPCProvider

NewRPCProvider creates a new RPCProvider.

func (*RPCProvider) Decrypt

func (s *RPCProvider) Decrypt(ctx context.Context, keyID string, ciphertext []byte) ([]byte, error)

Decrypt implements the CryptoProvider interface.

func (*RPCProvider) Encrypt

func (s *RPCProvider) Encrypt(ctx context.Context, keyID string, plaintext []byte) ([]byte, error)

Encrypt implements the CryptoProvider interface.

func (*RPCProvider) HandleDecryptResponse

func (s *RPCProvider) HandleDecryptResponse(resp DecryptResponse) error

HandleDecryptResponse processes an incoming decrypt response.

func (*RPCProvider) HandleEncryptResponse

func (s *RPCProvider) HandleEncryptResponse(resp EncryptResponse) error

HandleEncryptResponse processes an incoming encrypt response.

func (*RPCProvider) HandleSignResponse

func (s *RPCProvider) HandleSignResponse(resp SignResponse) error

HandleSignResponse processes an incoming sign response.

func (*RPCProvider) SetSender

func (s *RPCProvider) SetSender(sender types.RPCSenderFunc)

SetSender configures the function used to send requests.

func (*RPCProvider) Sign

func (s *RPCProvider) Sign(ctx context.Context, keyID string, payload []byte) ([]byte, error)

Sign implements the CryptoProvider interface.

type RefStore

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

RefStore manages mapping between opaque reference tokens and secret plaintext values. It prevents AI agents from seeing actual secret values by substituting them with safe reference tokens.

func NewRefStore

func NewRefStore() *RefStore

NewRefStore creates a new RefStore.

func (*RefStore) Clear

func (r *RefStore) Clear()

Clear removes all stored references.

func (*RefStore) Names

func (r *RefStore) Names() map[string]string

Names returns a mapping of plaintext value (as string) to its reference name. This is used by the scanner to mask secrets in output, replacing them with tokens like [SECRET:name].

func (*RefStore) Resolve

func (r *RefStore) Resolve(token string) ([]byte, bool)

Resolve resolves a single reference token to its plaintext value. Returns the plaintext and true if found, or nil and false otherwise.

func (*RefStore) ResolveAll

func (r *RefStore) ResolveAll(input string) string

ResolveAll replaces all {{secret:...}} and {{decrypt:...}} tokens in the input string with their actual plaintext values.

func (*RefStore) Store

func (r *RefStore) Store(name string, value []byte) string

Store stores a secret value and returns its reference token in the format {{secret:name}}.

func (*RefStore) StoreDecrypted

func (r *RefStore) StoreDecrypted(id string, value []byte) string

StoreDecrypted stores a decrypted value and returns its reference token in the format {{decrypt:id}}.

func (*RefStore) Values

func (r *RefStore) Values() [][]byte

Values returns all stored plaintext values. This is useful for output scanning to detect accidental secret leakage.

type SecretInfo

type SecretInfo struct {
	ID          uuid.UUID
	Name        string
	KeyID       uuid.UUID
	KeyName     string
	CreatedAt   time.Time
	UpdatedAt   time.Time
	AccessCount int
}

SecretInfo represents secret metadata (without the actual value).

type SecretsStore

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

SecretsStore manages encrypted secrets.

func NewSecretsStore

func NewSecretsStore(client *ent.Client, registry *KeyRegistry, crypto CryptoProvider) *SecretsStore

NewSecretsStore creates a new SecretsStore.

func (*SecretsStore) Delete

func (s *SecretsStore) Delete(ctx context.Context, name string) error

Delete removes a secret by name.

func (*SecretsStore) Get

func (s *SecretsStore) Get(ctx context.Context, name string) ([]byte, error)

Get retrieves and decrypts a secret value.

func (*SecretsStore) List

func (s *SecretsStore) List(ctx context.Context) ([]*SecretInfo, error)

List returns metadata for all secrets.

func (*SecretsStore) Store

func (s *SecretsStore) Store(ctx context.Context, name string, value []byte) error

Store encrypts and stores a secret value.

type SecurityConfigStore added in v0.7.0

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

SecurityConfigStore is the single access point for the raw `security_config` table. It replaces the scattered `ensureSecurityTable`/`storeSalt`/`storeChecksum` helpers that previously lived in both `bootstrap` and `session/ent_store`.

Two calling styles are supported:

  • Default row (bootstrap): LoadSalt / StoreSalt / LoadChecksum / StoreChecksum operate on the row named SecurityConfigDefault.
  • Arbitrary name (session test suite, future multi-context): the same operations are exposed with a `Named` suffix accepting a row name.

func NewSecurityConfigStore added in v0.7.0

func NewSecurityConfigStore(db *sql.DB) *SecurityConfigStore

NewSecurityConfigStore wraps the given *sql.DB. EnsureTable must be called before any read/write methods.

func (*SecurityConfigStore) EnsureTable added in v0.7.0

func (s *SecurityConfigStore) EnsureTable() error

EnsureTable creates the security_config table if it does not exist and adds the checksum column on older installations that lacked it.

func (*SecurityConfigStore) IsFirstRun added in v0.7.0

func (s *SecurityConfigStore) IsFirstRun() (bool, error)

IsFirstRun reports whether the default row is missing (no salt stored yet).

func (*SecurityConfigStore) LoadChecksum added in v0.7.0

func (s *SecurityConfigStore) LoadChecksum() ([]byte, error)

LoadChecksum returns the stored HMAC-SHA256 checksum for the default row.

func (*SecurityConfigStore) LoadChecksumNamed added in v0.7.0

func (s *SecurityConfigStore) LoadChecksumNamed(name string) ([]byte, error)

LoadChecksumNamed returns the stored HMAC-SHA256 checksum for the named row, or nil.

func (*SecurityConfigStore) LoadSalt added in v0.7.0

func (s *SecurityConfigStore) LoadSalt() ([]byte, error)

LoadSalt returns the stored salt for the default row, or nil if absent.

func (*SecurityConfigStore) LoadSaltNamed added in v0.7.0

func (s *SecurityConfigStore) LoadSaltNamed(name string) ([]byte, error)

LoadSaltNamed returns the stored salt for the named row, or nil if absent.

func (*SecurityConfigStore) StoreChecksum added in v0.7.0

func (s *SecurityConfigStore) StoreChecksum(checksum []byte) error

StoreChecksum updates the default row's checksum column. The row must already exist.

func (*SecurityConfigStore) StoreChecksumNamed added in v0.7.0

func (s *SecurityConfigStore) StoreChecksumNamed(name string, checksum []byte) (int64, error)

StoreChecksumNamed updates the named row's checksum column. The row must already exist (StoreSaltNamed before StoreChecksumNamed). Returns the number of rows affected so callers can detect missing-row cases.

func (*SecurityConfigStore) StoreSalt added in v0.7.0

func (s *SecurityConfigStore) StoreSalt(salt []byte) error

StoreSalt upserts the default row's salt value.

func (*SecurityConfigStore) StoreSaltNamed added in v0.7.0

func (s *SecurityConfigStore) StoreSaltNamed(name string, salt []byte) error

StoreSaltNamed upserts the named row's salt value.

type SignRequest

type SignRequest struct {
	ID      string `json:"id"`
	KeyID   string `json:"keyId"`
	Payload []byte `json:"payload"`
}

SignRequest represents the payload sent to the signer provider.

type SignResponse

type SignResponse struct {
	ID        string `json:"id"`
	Signature []byte `json:"signature"`
	Error     string `json:"error,omitempty"`
}

SignResponse represents the payload received from the signer provider.

type SignatureScheme added in v0.7.0

type SignatureScheme struct {
	// ID is the algorithm identifier string.
	ID string

	// Verify checks a signature against a public key and message.
	// The implementation handles any algorithm-specific hashing internally.
	Verify func(publicKey, message, signature []byte) error

	// SignatureSize is the expected byte length of signatures (e.g., 65 for secp256k1, 64 for Ed25519).
	SignatureSize int

	// PublicKeySize is the expected byte length of public keys (e.g., 33 for compressed secp256k1, 32 for Ed25519).
	PublicKeySize int
}

SignatureScheme is a canonical algorithm descriptor with verification function and metadata. This type does NOT act as a registry — actual dispatch is handled by injected verifier maps in each consumer (handshake, provenance). SignatureScheme provides a single source of truth for algorithm metadata (key/signature sizes) and verification logic.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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