Coverage for src / osiris_cli / key_manager.py: 0%
61 statements
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-27 17:41 +0200
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-27 17:41 +0200
1#!/usr/bin/env python3
2"""
3Secure API Key Manager for Osiris CLI
4Stores API keys encrypted in user's home directory
5"""
7import json
8from pathlib import Path
9from typing import Dict, Optional
10from cryptography.fernet import Fernet
11import base64
12import hashlib
13from rich.console import Console
15console = Console()
18class SecureKeyManager:
19 """Manage API keys securely with encryption"""
21 def __init__(self):
22 self.keys_dir = Path.home() / ".osiris" / "keys"
23 self.keys_dir.mkdir(parents=True, exist_ok=True)
24 self.keys_file = self.keys_dir / "api_keys.enc"
25 self.cipher = self._get_cipher()
27 def _get_cipher(self) -> Fernet:
28 """Get or create encryption cipher"""
29 # Use machine-specific key (derived from hostname + user)
30 import socket
31 import getpass
33 machine_id = f"{socket.gethostname()}-{getpass.getuser()}"
34 key_material = hashlib.sha256(machine_id.encode()).digest()
35 key = base64.urlsafe_b64encode(key_material)
37 return Fernet(key)
39 def save_key(self, provider: str, api_key: str):
40 """Save API key securely"""
41 # Load existing keys
42 keys = self.load_all_keys()
44 # Add/update key
45 keys[provider] = api_key
47 # Encrypt and save
48 try:
49 encrypted_data = self.cipher.encrypt(json.dumps(keys).encode())
50 self.keys_file.write_bytes(encrypted_data)
51 console.print(f"[green]✓[/green] API key saved securely for {provider}")
52 except Exception as e:
53 console.print(f"[red]Error saving key: {e}[/red]")
55 def load_key(self, provider: str) -> Optional[str]:
56 """Load API key for provider"""
57 keys = self.load_all_keys()
58 return keys.get(provider)
60 def load_all_keys(self) -> Dict[str, str]:
61 """Load all saved API keys"""
62 if not self.keys_file.exists():
63 return {}
65 try:
66 encrypted_data = self.keys_file.read_bytes()
67 decrypted_data = self.cipher.decrypt(encrypted_data)
68 return json.loads(decrypted_data.decode())
69 except Exception:
70 # If decryption fails, return empty dict
71 return {}
73 def has_key(self, provider: str) -> bool:
74 """Check if API key exists for provider"""
75 return provider in self.load_all_keys()
77 def delete_key(self, provider: str):
78 """Delete API key for provider"""
79 keys = self.load_all_keys()
80 if provider in keys:
81 del keys[provider]
83 # Save updated keys
84 try:
85 encrypted_data = self.cipher.encrypt(json.dumps(keys).encode())
86 self.keys_file.write_bytes(encrypted_data)
87 console.print(f"[green]✓[/green] API key deleted for {provider}")
88 except Exception as e:
89 console.print(f"[red]Error deleting key: {e}[/red]")
91 def list_providers_with_keys(self) -> list:
92 """List all providers with saved keys"""
93 return list(self.load_all_keys().keys())
95 def mask_key(self, api_key: str) -> str:
96 """Mask API key for display"""
97 if len(api_key) <= 8:
98 return "***"
99 return f"{api_key[:4]}...{api_key[-4:]}"
102# Global key manager instance
103key_manager = SecureKeyManager()