Implement a VS Code extension that transparently handles GPG encrypted files. Files on disk remain encrypted (.gpg/.asc), but the editor displays decrypted plain text content.
- Encryption: Use configurable default recipient (no prompts during save)
- File extensions: Handle
.gpgand.ascfiles - Display: Keep full filename in editor (e.g.,
file.txt.gpg)
- TextDocumentContentProvider (
gpgfile:scheme) - Decrypts and provides content for reading - FileSystemProvider (
gpgfs:scheme) - Encrypts content on write - openpgp.js - Pure JavaScript OpenPGP implementation (no external GPG binary required)
- Keys stored in VS Code's globalState (encrypted with VS Code's encryption API)
- Support importing private/public keys from files or clipboard
- Passphrase stored securely in VS Code's secret storage (extension.secrets API)
- UI for managing keys (list, import, remove, set default)
vscode-gpg/
├── package.json # Extension manifest
├── tsconfig.json # TypeScript config
├── src/
│ ├── extension.ts # Entry point, provider registration
│ ├── crypto/
│ │ ├── openpgp.ts # openpgp.js wrapper
│ │ ├── keyManager.ts # Key storage and management
│ │ └── config.ts # Configuration handling
│ ├── providers/
│ │ ├── encryptedDocumentProvider.ts # Read path (TextDocumentContentProvider)
│ │ └── encryptedFileSystemProvider.ts # Write path (FileSystemProvider)
│ ├── commands/
│ │ ├── keyCommands.ts # Key management commands
│ │ └── encryptCommands.ts # Encryption commands
│ └── util/
│ └── logger.ts # Output channel logging
├── README.md
└── .vscode/
└── launch.json # Debug config
Create package.json with:
- Extension metadata (name, publisher, version)
- Dependencies:
openpgppackage - Activation events:
onLanguage:plaintext,onCommand:vscode.openWith - Contribution: language definition for
.gpg/.ascfiles, commands for key management - Configuration schema (default recipient, etc.)
Create tsconfig.json for VS Code extension development.
Implement encryption/decryption using openpgp.js:
encrypt(content: string, recipientKeyId: string): Promise<Uint8Array>- Encrypt to recipientdecrypt(encryptedData: Uint8Array): Promise<string>- Decrypt with stored private keygenerateKey(userId: string, passphrase: string): Promise<KeyPair>- Generate new key pair- Use openpgp.js API:
openpgp.encrypt(),openpgp.decrypt(),openpgp.readKey(),openpgp.readPrivateKey()
Implement key storage and management:
- Store keys in VS Code globalState (encrypted)
- Store passphrases in VS Code extension.secrets API
- Functions:
importKey(keyData: string, isPrivate: boolean): Promise<void>- Import key from armored stringlistKeys(): Promise<KeyInfo[]>- List stored keyssetDefaultRecipient(keyId: string): Promise<void>- Set default encryption keyremoveKey(keyId: string): Promise<void>- Remove stored keygetPrivateKey(keyId: string): Promise<Key>- Retrieve private key with passphrase
Implement TextDocumentContentProvider:
provideTextDocumentContent(uri: Uri): Promise<string>- Parse URI to extract file path
- Read encrypted file from disk
- Decrypt via openpgp.js wrapper
- Return plain text
- Register with
gpgfile:scheme - Handle
.gpgand.ascextensions
Implement FileSystemProvider:
writeFile(uri, content, options): Promise<void>- Encrypt content via openpgp.js wrapper
- Write encrypted data to disk
- Implement required methods:
stat,readFile,watch,delete,rename, etc. - Register with
gpgfs:scheme
Implement commands:
gpg.importKey- Import key from file or clipboardgpg.listKeys- Show all stored keysgpg.removeKey- Remove a stored keygpg.setDefaultRecipient- Set default encryption recipientgpg.generateKey- Generate new key pair
Initialize in activate():
- Register
EncryptedDocumentProviderforgpgfile:scheme - Register
EncryptedFileSystemProviderforgpgfs:scheme - Register commands:
gpg.openEncrypted- Open file with decryption- All key management commands
- Initialize keyManager
- Create OutputChannel for logging
Add VS Code settings (package.json):
{
"gpg.defaultRecipient": { "type": "string", "default": "" },
"gpg.fileExtensions": { "type": "array", "default": [".gpg", ".asc"] },
"gpg.askForPassphrase": { "type": "boolean", "default": "true" }
}- Create OutputChannel for operation logs
- Show error messages for: no keys, decryption failed, missing passphrase
- Prompt for passphrase if not stored (using
window.showInputBox()with password option) - Status bar indicator showing encryption status
- First-run setup: prompt user to import or generate keys
package.json- Extension manifest and config schemasrc/extension.ts- Entry point with provider registrationsrc/crypto/openpgp.ts- openpgp.js wrappersrc/crypto/keyManager.ts- Key storage and managementsrc/providers/encryptedDocumentProvider.ts- Read pathsrc/providers/encryptedFileSystemProvider.ts- Write pathsrc/commands/keyCommands.ts- Key management commands
- Import or generate a GPG key pair via commands
- Open a
.gpgfile - should show decrypted content - Edit and save - file on disk should remain encrypted
- Check OutputChannel for operation logs
- Test with no keys - should prompt to import/generate
- Test with wrong passphrase - should show decryption error
- Test key management commands (list, remove, set default)
workspace.registerTextDocumentContentProvider('gpgfile', provider)workspace.registerFileSystemProvider('gpgfs', fsProvider)context.globalState- Store encrypted keysextension.secretsAPI - Store passphrases securelywindow.createOutputChannel('GPG')workspace.getConfiguration('gpg')window.showInputBox()- For passphrase inputwindow.showQuickPick()- For key selection