Skip to content

Add OS keychain integration #57

@KofTwentyTwo

Description

@KofTwentyTwo

User Story

As a developer, I want qctl to store my credentials in the OS keychain so I don't have tokens in plain text files and they're protected by my system's security.

Design

Command Interface

# Credentials are stored automatically on login
qctl auth login
# -> Stores token in macOS Keychain / Windows Credential Store / Linux secret-tool

# View credential storage location
qctl auth status
# -> Shows: "Credentials stored in: macOS Keychain"

# Configure storage backend
qctl config set auth.credential_store keychain  # keychain | file | none

Keychain Entries

# macOS Keychain
Service: qctl
Account: voyage:access_token
Password: <token>

Service: qctl
Account: voyage:refresh_token
Password: <token>

# Windows Credential Store
Target: qctl:voyage:access_token
User: qctl
Password: <token>

# Linux secret-tool (libsecret)
Label: qctl voyage access_token
Attributes: application=qctl, provider=voyage, type=access_token
Secret: <token>

Fallback Strategy

1. Try OS keychain
2. If unavailable, fall back to encrypted file (~/.qctl/credentials.enc)
3. If encryption unavailable, warn and use plain file with strict permissions

Storage Configuration

# ~/.qctl/qctl.yaml
auth:
  credential_store: keychain  # keychain | file | memory
  file_encryption: true       # encrypt file-based storage
  encryption_key_source: keychain  # where to get encryption key

Files to Create/Modify

File Action Purpose
qctl-core/src/main/java/io/qrun/qctl/core/auth/CredentialStore.java Create Store interface
qctl-core/src/main/java/io/qrun/qctl/core/auth/KeychainStore.java Create OS keychain implementation
qctl-core/src/main/java/io/qrun/qctl/core/auth/MacOSKeychain.java Create macOS Keychain via Security.framework
qctl-core/src/main/java/io/qrun/qctl/core/auth/WindowsCredStore.java Create Windows Credential Store
qctl-core/src/main/java/io/qrun/qctl/core/auth/LinuxSecretService.java Create Linux libsecret/secret-tool
qctl-core/src/main/java/io/qrun/qctl/core/auth/FileCredentialStore.java Create Encrypted file fallback
qctl-core/src/main/java/io/qrun/qctl/core/auth/CredentialStoreFactory.java Create Platform detection

Implementation Tasks

  • Create CredentialStore interface
  • Implement MacOSKeychain using JNA/native calls
  • Implement WindowsCredStore using JNA/Credential Manager API
  • Implement LinuxSecretService using secret-tool CLI
  • Implement FileCredentialStore with AES encryption
  • Create CredentialStoreFactory with platform detection
  • Integrate with TokenManager
  • Handle keychain access prompts gracefully
  • Add fallback chain with warnings
  • Support credential migration between stores
  • Set strict file permissions (600) for fallback
  • Write unit tests for each store implementation
  • Write integration tests for credential lifecycle

Acceptance Criteria

  • macOS: tokens stored in Keychain.app
  • Windows: tokens in Credential Manager
  • Linux: tokens via secret-tool (libsecret)
  • Fallback to encrypted file if keychain unavailable
  • qctl auth status shows storage location
  • Tokens survive qctl upgrades
  • No plaintext tokens in ~/.qctl/
  • Graceful handling of keychain access denied
  • Works in headless/SSH environments with fallback

Metadata

Metadata

Assignees

No one assigned

    Labels

    module:coreCore infrastructurestoryFeature story linked to epic

    Projects

    Status

    No status

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions