Documentation
¶
Overview ¶
Package keychain provides cross-platform secure storage for secrets. - macOS: System Keychain stores DEK (Data Encryption Key), data encrypted with AES-256-GCM - Linux: File-based DEK storage with AES-256-GCM encryption - Windows: DPAPI + Registry storage
Index ¶
- Constants
- Variables
- func CleanupLegacyBackup(configDir string) error
- func Exists(service, account string) bool
- func Get(service, account string) (string, error)
- func HasLegacyData(configDir string) bool
- func IsCiphertextKeyMismatch(err error) bool
- func IsDEKMissing(err error) bool
- func IsUnavailable(err error) bool
- func MigrateToFileDEK(service string, dryRun bool) (int, error)
- func NewUnavailableError(op string, err error) error
- func Remove(service, account string) error
- func RemoveAuthTokenEntries(service string) error
- func Set(service, account, data string) error
- func StorageDir(service string) string
- func ValidateAuthTokenEntries(service string) error
- type Diagnostic
- type KeychainAccess
- type MigrationResult
- type UnavailableError
Constants ¶
const ( // Service is the unified keychain service name for all secrets. Service = "dws-cli" // AccountToken is the account key for storing auth token data. AccountToken = "auth-token" // StorageDirEnv overrides the on-disk keychain storage root on // platforms that use file-backed storage (macOS, Linux). It is // intended for tests that need to isolate keychain state from the // real user environment and from sibling test packages running in // parallel. When empty, the platform default applies. StorageDirEnv = "DWS_KEYCHAIN_DIR" // TestNamespaceEnv isolates the Windows HKCU registry backend for tests. // Production code must not set it. Other platforms already isolate secure // storage through StorageDirEnv and ignore this value. TestNamespaceEnv = "DWS_KEYCHAIN_TEST_NAMESPACE" // DisableKeychainEnv opts the macOS implementation out of system // Keychain access for the DEK, falling back to a file-based DEK // (same scheme as Linux). Intended for sandboxed runtimes where // Keychain APIs are blocked (e.g. Codex App). This weakens the // at-rest protection — DEK and ciphertext live in the same // directory — and is therefore opt-in. DisableKeychainEnv = "DWS_DISABLE_KEYCHAIN" )
Variables ¶
var ( // ErrDEKMissing means encrypted local data may exist, but the Data Encryption // Key needed to decrypt it is missing. Read paths must not create a new DEK, // because a fresh key cannot decrypt existing ciphertext. ErrDEKMissing = errors.New("dek missing") // ErrCiphertextKeyMismatch means encrypted data exists but none of the // available DEKs can decrypt it. Write paths must not overwrite the data. ErrCiphertextKeyMismatch = errors.New("ciphertext key mismatch") )
Functions ¶
func CleanupLegacyBackup ¶
CleanupLegacyBackup removes the backup file created during migration. Call this after confirming the new keychain storage works correctly.
func Get ¶
Get retrieves a value from the keychain. Returns empty string and nil error if the entry does not exist.
func HasLegacyData ¶
HasLegacyData checks if legacy .data file exists.
func IsCiphertextKeyMismatch ¶ added in v1.0.52
func IsDEKMissing ¶ added in v1.0.51
func IsUnavailable ¶ added in v1.0.51
func MigrateToFileDEK ¶ added in v1.0.52
MigrateToFileDEK re-encrypts the legacy and profile-scoped auth token entries for service with the macOS file DEK. It is supported only on macOS and must be invoked from a process that can still access the system Keychain. When dryRun is true, all selected entries are validated without modifying data.
func NewUnavailableError ¶ added in v1.0.51
func RemoveAuthTokenEntries ¶ added in v1.0.54
RemoveAuthTokenEntries deletes the legacy token plus every organization and identity scoped auth-token entry for service. Other keychain accounts are preserved.
func StorageDir ¶
StorageDir returns the storage directory for a given service name. Follows XDG Base Directory Specification: ~/.local/share/<service>. When the DWS_KEYCHAIN_DIR environment variable is set (used by tests for isolation), the storage root is taken from that env var instead.
func ValidateAuthTokenEntries ¶ added in v1.0.52
ValidateAuthTokenEntries verifies every persisted auth-token ciphertext, including profile slots not yet registered in profiles.json, without creating or rotating key material.
Types ¶
type Diagnostic ¶ added in v1.0.51
type Diagnostic struct {
OK bool `json:"ok"`
Reason string `json:"reason,omitempty"`
Message string `json:"message"`
Hint string `json:"hint,omitempty"`
Detail map[string]string `json:"detail,omitempty"`
}
Diagnostic is a read-only health report for the platform keychain backend. It never mutates credentials, DEKs, or OS keychain settings.
func Diagnose ¶ added in v1.0.51
func Diagnose() Diagnostic
type KeychainAccess ¶
type KeychainAccess interface {
Get(service, account string) (string, error)
Set(service, account, value string) error
Remove(service, account string) error
}
KeychainAccess abstracts keychain Get/Set/Remove for dependency injection.
type MigrationResult ¶
type MigrationResult struct {
Migrated bool // true if migration was performed
FromPath string // source file path
BackupPath string // backup file path (if migrated)
Error error // error if migration failed
NeedRelogin bool // true if user needs to re-login
}
MigrationResult contains the result of a migration attempt.
func MigrateFromLegacy ¶
func MigrateFromLegacy(configDir string) *MigrationResult
MigrateFromLegacy attempts to migrate from the legacy MAC-based encryption to the new keychain-based storage. It: 1. Checks if legacy .data file exists 2. Tries to decrypt with MAC address 3. Re-encrypts and stores in keychain 4. Backs up the old file
If the keychain already has data, migration is skipped. If the legacy file doesn't exist, migration is skipped. If decryption fails (wrong MAC/corrupted), returns NeedRelogin=true.
type UnavailableError ¶ added in v1.0.51
type UnavailableError struct {
}
UnavailableError marks failures where the platform keychain itself could not be reached, unlocked, or created. Callers can surface a diagnostic instead of treating the result as a normal missing credential.
func (*UnavailableError) Error ¶ added in v1.0.51
func (e *UnavailableError) Error() string
func (*UnavailableError) Unwrap ¶ added in v1.0.51
func (e *UnavailableError) Unwrap() error