Key Management
Requirements
This specification defines the requirements for key management in VS Code GPG.
Functional Requirements
MUST
- The extension MUST allow users to generate new GPG key pairs
- The extension MUST allow users to import existing GPG keys from files
- The extension MUST allow users to list all stored keys
- The extension MUST allow users to remove stored keys
- The extension MUST store keys in VS Code's global state
- The extension MUST distinguish between public and private keys
- The extension MUST allow users to set a default recipient for encryption
- The extension MUST allow loading keys from external file/directory paths via
gpg.keyPathsconfiguration - The extension MUST distinguish between stored keys and externally loaded keys
SHOULD
- The extension SHOULD support passphrase-protected private keys
- The extension SHOULD store passphrases in VS Code's secure storage (secrets API)
- The extension SHOULD allow users to store passphrases for convenience
- The extension SHOULD detect whether a key requires a passphrase
- The extension SHOULD display key information (user ID, key ID, type)
- The extension SHOULD support importing multiple keys from a single file
- The extension SHOULD reload external keys when
gpg.keyPathsconfiguration changes - The extension SHOULD display source file paths for externally loaded keys
- The extension SHOULD prevent removal of externally loaded keys through the UI
MAY
- The extension MAY support exporting keys to files
- The extension MAY support key expiration warnings
- The extension MAY support key revocation
Key Export
When exporting a newly generated key pair:
1. After successful key generation, prompt user if they want to save the key pair to disk
2. If user confirms, show a save dialog for the private key:
- Default filename: {sanitized_userId}_private.asc
- File filter: GPG Private Key (.asc)
3. Write the armored private key to the selected location
4. Show a save dialog for the public key:
- Default filename: {sanitized_userId}_public.asc
- File filter: GPG Public Key (.asc)
5. Write the armored public key to the selected location
6. Display confirmation messages for each saved file
Implementation Details
Key Storage
Keys are stored in VS Code's global state:
- Storage Key: gpg.storedKeys
- Structure: Array of StoredKey objects
- Properties:
- keyId: The key's unique identifier (hex string)
- userId: The user ID associated with the key
- isPrivate: Boolean indicating if this is a private key
- armoredKey: The armored key data
- isExternal: Boolean indicating if loaded from external path (optional)
- sourcePath: File path the key was loaded from (for external keys)
Composite Key Pattern
To prevent overwriting public/private keys with the same ID:
- Storage keys are composite: {keyId}_{type}
- Types are public and private
- Example: ABC123_public, ABC123_private
External Key Loading
The extension supports loading keys from external file/directory paths:
Configuration
- Setting:
gpg.keyPaths - Type: Array of strings (file/directory paths)
- Default:
[]
Loading Behavior
- Keys are loaded on extension activation
- Keys are reloaded when
gpg.keyPathsconfiguration changes - Loaded keys are kept in a separate cache (
externalKeysMap) - External keys are NOT persisted to VS Code's global state
File Discovery
For directories:
- Recursively scans all subdirectories
- Recognizes files with extensions: .asc, .gpg, .key, .pub, .sec
- Recognizes files starting with keyring
For files: - Reads the entire file content - Extracts all PGP key blocks (supports multiple keys per file)
Key Storage Structure
External keys use the same StoredKey interface with additional fields:
- isExternal: true
- sourcePath: The file path the key was loaded from
Key Retrieval
All key retrieval methods check both stored and external keys:
- getKey(keyId, isPrivate): Returns from either cache
- getPublicKey(keyId): Returns from either cache
- getPrivateKey(keyId): Returns from either cache
- listKeys(): Returns all keys from both caches
- listStoredKeys(): Returns only stored keys
- listExternalKeys(): Returns only external keys
Key Removal
- Stored keys can be removed via the key management UI
- External keys cannot be removed (attempt shows error message directing user to update
gpg.keyPaths) - Passphrases are removed when stored keys are deleted
Passphrase Storage
Passphrases are stored in VS Code's secrets API:
- Storage Key Pattern: gpg.passphrase.{keyId}
- Storage Method: context.secrets.store()
- Retrieval Method: context.secrets.get()
- Deletion: context.secrets.delete()
Key Generation
When generating a new key pair: 1. Prompt for user ID (name and email) 2. Prompt for passphrase (optional) 3. Confirm passphrase 4. Generate key pair using: - Algorithm: ECC (Elliptic Curve Cryptography) - Curve: curve25519 5. Import both public and private keys 6. Store passphrase if provided 7. Set as default recipient 8. Prompt user to save the key pair to disk 9. If user confirms, save private key and public key to user-selected locations
Key Import
When importing a key:
1. Open file picker for key selection
2. Read the key file
3. Parse PGP blocks (support multiple keys per file)
4. For each block:
- Detect if it's public or private
- Parse key information
- Store the key in cachedKeys (persisted)
5. For private keys:
- Check if passphrase is required
- Prompt for passphrase if required
- Offer to save passphrase
6. Display success message with summary
Key Listing
The key management UI displays keys in two sections:
Stored in VS Code
- Keys manually imported or generated
- Can be removed via trash icon button
- Shows: user ID, key type (private/public), key ID
Loaded from File System
- Keys loaded from
gpg.keyPathsconfiguration - Cannot be removed (read-only)
- Shows: user ID, key type, key ID, source file path
Configuration Change Handling
When gpg.keyPaths configuration changes:
1. Extension receives onDidChangeConfiguration event
2. Calls keyManager.reloadKeysFromPaths()
3. KeyManager clears and reloads external keys from new paths
4. If key management quick pick is open, it waits for reload to complete then refreshes
Race Condition Prevention
- KeyManager tracks the load promise in
loadKeysPromise awaitKeysLoaded()method allows waiting for in-progress loads- Quick pick's config change listener awaits before refreshing UI
Key Removal
When removing a key:
1. User clicks trash icon on a stored key
2. Check if key is external (if so, show error and abort)
3. Confirm removal
4. Remove key from cachedKeys
5. Remove associated passphrase from secrets (if private key)
6. Update storage
7. Refresh the key list to show remaining keys
Configuration
The default recipient is stored in:
- Storage Key: gpg.defaultRecipient
- Type: String (key ID)
- Accessed via: KeyManager.getDefaultRecipient() / setDefaultRecipient()
Commands
| Command | Description |
|---|---|
gpg.generateKey |
Generate a new GPG key pair |
gpg.importKey |
Import a GPG key from a file |
gpg.manageKeys |
List and manage (remove) stored GPG keys |
gpg.setDefaultRecipient |
Set the default encryption recipient |
gpg.reloadKeys |
Manually reload keys from configured paths |
Testing Scenarios
Basic Key Operations
- Generate new key pair with passphrase
- Generate new key pair without passphrase
- Import public key from file
- Import private key from file
- Import file with multiple keys
- Import file with both public and private keys
- List all keys (empty, single, multiple)
- Remove public key
- Remove private key (verify passphrase is also removed)
- Set default recipient
- Store passphrase for key
- Retrieve stored passphrase
- Detect passphrase requirement
- Import key with wrong passphrase
- Verify composite key storage (public/private with same ID)
External Key Loading
- Load keys from single file path
- Load keys from directory path
- Load keys from multiple paths
- Load keys from nested directories
- Handle non-existent paths gracefully
- Handle invalid key files gracefully
- Verify external keys are not persisted
- Verify external keys display source path
- Attempt to remove external key (should fail with helpful message)
- Reload keys when configuration changes
- Verify old external keys are cleared when paths are removed
- Handle race condition: config change while quick pick is open
- Verify quick pick refreshes after config change
- Verify stored and external keys are displayed in separate sections
Configuration Scenarios
- Set workspace-specific key paths
- Set user-level key paths
- Verify workspace paths take precedence
- Change key paths and verify keys reload
- Clear key paths and verify external keys are removed