docs/SECURITY.md gelöscht
This commit is contained in:
parent
43165f86f2
commit
30e33e4fa1
1 changed files with 0 additions and 423 deletions
423
docs/SECURITY.md
423
docs/SECURITY.md
|
|
@ -1,423 +0,0 @@
|
|||
# Security Documentation - NOMYO.js
|
||||
|
||||
## Overview
|
||||
|
||||
NOMYO.js implements end-to-end encryption for OpenAI-compatible chat completions using hybrid cryptography. This document details the security architecture, current implementation status, and limitations.
|
||||
|
||||
---
|
||||
|
||||
## Encryption Architecture
|
||||
|
||||
### Hybrid Encryption (AES-256-GCM + RSA-OAEP)
|
||||
|
||||
**Request Encryption Flow:**
|
||||
1. Client generates ephemeral AES-256 key (32 bytes)
|
||||
2. Payload serialized to JSON and encrypted with AES-256-GCM
|
||||
3. AES key encrypted with server's RSA-4096 public key (RSA-OAEP-SHA256)
|
||||
4. Encrypted package sent to server with client's public key
|
||||
|
||||
**Response Decryption Flow:**
|
||||
1. Server encrypts response with new AES-256 key
|
||||
2. AES key encrypted with client's RSA public key
|
||||
3. Client decrypts AES key with private RSA key
|
||||
4. Client decrypts response with AES key
|
||||
|
||||
### Cryptographic Primitives
|
||||
|
||||
| Component | Algorithm | Parameters |
|
||||
|-----------|-----------|------------|
|
||||
| **Symmetric Encryption** | AES-256-GCM | 256-bit key, 96-bit nonce, 128-bit tag |
|
||||
| **Asymmetric Encryption** | RSA-OAEP | 4096-bit modulus, SHA-256 hash, MGF1-SHA256 |
|
||||
| **Key Derivation** | PBKDF2 | 100,000 iterations, SHA-256, 16-byte salt |
|
||||
| **Private Key Encryption** | AES-256-CBC | 256-bit key (from PBKDF2), 128-bit IV |
|
||||
|
||||
---
|
||||
|
||||
## Current Implementation Status
|
||||
|
||||
### ✅ Fully Implemented
|
||||
|
||||
1. **Web Crypto API Integration**
|
||||
- Platform-agnostic cryptography (Node.js 15+ and modern browsers)
|
||||
- Hardware-accelerated when available
|
||||
- Constant-time operations (timing attack resistant)
|
||||
|
||||
2. **Hybrid Encryption**
|
||||
- AES-256-GCM for payload encryption
|
||||
- RSA-OAEP-SHA256 for key exchange
|
||||
- Authenticated encryption (GCM provides AEAD)
|
||||
- Unique nonce per encryption (96-bit random)
|
||||
|
||||
3. **Key Management**
|
||||
- 4096-bit RSA keys (default, configurable to 2048)
|
||||
- Automatic key generation on first use
|
||||
- File-based persistence (Node.js)
|
||||
- In-memory keys (browsers)
|
||||
- Password protection via PBKDF2 + AES-256-CBC (minimum 8-character password enforced)
|
||||
- Automatic periodic key rotation (default: 24 hours, configurable, or disabled with `keyRotationInterval: 0`)
|
||||
- `dispose()` method severs in-memory key references and cancels the rotation timer
|
||||
|
||||
4. **Transport Security**
|
||||
- HTTPS enforcement using proper URL parsing (`new URL()`) — not string prefix matching
|
||||
- Certificate validation (browsers/Node.js)
|
||||
- Optional HTTP for local development (explicit opt-in)
|
||||
- API key validated to reject CR/LF characters (prevents HTTP header injection)
|
||||
- Server error detail truncated to 100 printable characters (prevents log injection)
|
||||
|
||||
5. **Memory Protection (Pure JavaScript)**
|
||||
- Immediate zeroing of sensitive buffers
|
||||
- Context managers for automatic cleanup (`SecureByteContext`) with guarded `finally` blocks
|
||||
- Intermediate crypto buffers (password bytes, salt, IV) wrapped in `SecureByteContext` during key encryption
|
||||
- HTTP request body (`ArrayBuffer`) zeroed after data is handed to the socket
|
||||
- Best-effort memory management
|
||||
|
||||
6. **Response Integrity**
|
||||
- Decrypted response validated against required `ChatCompletionResponse` schema fields before use
|
||||
- Generic error messages from all crypto operations (no internal engine details leaked)
|
||||
|
||||
### ⚠️ Limitations (Pure JavaScript)
|
||||
|
||||
1. **No OS-Level Memory Locking**
|
||||
- Cannot use `mlock()` (Linux/macOS) or `VirtualLock()` (Windows)
|
||||
- JavaScript GC controls memory lifecycle
|
||||
- Memory may be paged to swap
|
||||
- **Impact**: Sensitive data could be written to disk during high memory pressure
|
||||
|
||||
2. **Memory Zeroing Only**
|
||||
- Zeroes `ArrayBuffer` contents immediately after use
|
||||
- Cannot prevent GC from copying data internally
|
||||
- Cannot guarantee memory won't be swapped
|
||||
- **Mitigation**: Minimizes exposure window
|
||||
|
||||
3. **Browser Limitations**
|
||||
- Keys not persisted (in-memory only)
|
||||
- No file system access
|
||||
- Subject to browser security policies
|
||||
- **Impact**: Keys regenerated on each page load
|
||||
|
||||
---
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
### Deployment
|
||||
|
||||
✅ **DO:**
|
||||
- Use HTTPS in production (enforced by default)
|
||||
- Enable secure memory protection (default: `secureMemory: true`)
|
||||
- Use password-protected private keys in Node.js (minimum 8 characters)
|
||||
- Set private key file permissions to 600 (owner-only)
|
||||
- Rely on automatic key rotation (`keyRotationInterval`, default 24h) to limit fingerprint lifetime
|
||||
- Call `dispose()` when the client is no longer needed
|
||||
- Validate server public key fingerprint on first use
|
||||
|
||||
❌ **DON'T:**
|
||||
- Use HTTP in production (only for localhost development)
|
||||
- Disable secure memory unless absolutely necessary
|
||||
- Store unencrypted private keys
|
||||
- Share private keys across systems
|
||||
- Store keys in public repositories
|
||||
|
||||
### Key Management
|
||||
|
||||
**Node.js (Recommended):**
|
||||
```javascript
|
||||
const client = new SecureCompletionClient({ routerUrl: 'https://...' });
|
||||
|
||||
// Generate with password protection
|
||||
await client.generateKeys({
|
||||
saveToFile: true,
|
||||
keyDir: 'client_keys',
|
||||
password: process.env.KEY_PASSWORD // From environment variable
|
||||
});
|
||||
```
|
||||
|
||||
**Browsers (In-Memory):**
|
||||
```javascript
|
||||
// Keys generated automatically, not persisted
|
||||
const client = new SecureChatCompletion({ baseUrl: 'https://...' });
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
```bash
|
||||
# .env file (never commit to git)
|
||||
NOMYO_API_KEY=your-api-key
|
||||
NOMYO_KEY_PASSWORD=your-key-password
|
||||
NOMYO_SERVER_URL=https://api.nomyo.ai
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Cryptographic Implementation Details
|
||||
|
||||
### AES-256-GCM
|
||||
|
||||
```typescript
|
||||
// Key generation
|
||||
const key = await crypto.subtle.generateKey(
|
||||
{ name: 'AES-GCM', length: 256 },
|
||||
true, // extractable
|
||||
['encrypt', 'decrypt']
|
||||
);
|
||||
|
||||
// Encryption
|
||||
const nonce = crypto.getRandomValues(new Uint8Array(12)); // 96 bits
|
||||
const ciphertext = await crypto.subtle.encrypt(
|
||||
{ name: 'AES-GCM', iv: nonce, tagLength: 128 },
|
||||
key,
|
||||
plaintext
|
||||
);
|
||||
```
|
||||
|
||||
**Security Properties:**
|
||||
- **Authenticated Encryption with Associated Data (AEAD)**
|
||||
- **128-bit authentication tag** prevents tampering
|
||||
- **Unique nonce requirement** - Never reuse nonce with same key
|
||||
- **Ephemeral keys** - New AES key per request provides forward secrecy
|
||||
|
||||
### RSA-OAEP
|
||||
|
||||
```typescript
|
||||
// Key generation
|
||||
const keyPair = await crypto.subtle.generateKey(
|
||||
{
|
||||
name: 'RSA-OAEP',
|
||||
modulusLength: 4096,
|
||||
publicExponent: new Uint8Array([1, 0, 1]), // 65537
|
||||
hash: 'SHA-256'
|
||||
},
|
||||
true,
|
||||
['encrypt', 'decrypt']
|
||||
);
|
||||
|
||||
// Encryption
|
||||
const encrypted = await crypto.subtle.encrypt(
|
||||
{ name: 'RSA-OAEP' },
|
||||
publicKey,
|
||||
data
|
||||
);
|
||||
```
|
||||
|
||||
**Security Properties:**
|
||||
- **OAEP padding** prevents chosen ciphertext attacks
|
||||
- **SHA-256 hash** in MGF1 mask generation
|
||||
- **4096-bit keys** provide ~152-bit security level
|
||||
- **No label** (standard practice for hybrid encryption)
|
||||
|
||||
### Password-Protected Keys
|
||||
|
||||
```typescript
|
||||
// Derive key from password
|
||||
const salt = crypto.getRandomValues(new Uint8Array(16));
|
||||
const passwordKey = await crypto.subtle.importKey(
|
||||
'raw',
|
||||
encoder.encode(password),
|
||||
'PBKDF2',
|
||||
false,
|
||||
['deriveKey']
|
||||
);
|
||||
|
||||
const derivedKey = await crypto.subtle.deriveKey(
|
||||
{
|
||||
name: 'PBKDF2',
|
||||
salt: salt,
|
||||
iterations: 100000, // OWASP recommendation
|
||||
hash: 'SHA-256'
|
||||
},
|
||||
passwordKey,
|
||||
{ name: 'AES-CBC', length: 256 },
|
||||
false,
|
||||
['encrypt']
|
||||
);
|
||||
|
||||
// Encrypt private key
|
||||
const iv = crypto.getRandomValues(new Uint8Array(16));
|
||||
const encrypted = await crypto.subtle.encrypt(
|
||||
{ name: 'AES-CBC', iv: iv },
|
||||
derivedKey,
|
||||
privateKeyData
|
||||
);
|
||||
|
||||
// Store: salt + iv + encrypted
|
||||
```
|
||||
|
||||
**Security Properties:**
|
||||
- **100,000 PBKDF2 iterations** (meets OWASP 2023 recommendations)
|
||||
- **SHA-256 hash function**
|
||||
- **16-byte random salt** (unique per key)
|
||||
- **AES-256-CBC** for key encryption
|
||||
|
||||
---
|
||||
|
||||
## Memory Protection Details
|
||||
|
||||
### Current Implementation (Pure JavaScript)
|
||||
|
||||
```typescript
|
||||
class SecureByteContext {
|
||||
async use<T>(fn: (data: ArrayBuffer) => Promise<T>): Promise<T> {
|
||||
try {
|
||||
return await fn(this.data);
|
||||
} finally {
|
||||
// Always zero, even if exception occurs.
|
||||
// zeroMemory failure is swallowed so it cannot mask the original error.
|
||||
if (this.useSecure) {
|
||||
try {
|
||||
this.secureMemory.zeroMemory(this.data);
|
||||
} catch (_zeroErr) { /* intentional */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**What it does:**
|
||||
- ✅ Zeroes memory immediately after use
|
||||
- ✅ Guarantees cleanup even on exceptions
|
||||
- ✅ Minimizes exposure window
|
||||
|
||||
**What it cannot do:**
|
||||
- ❌ Prevent JavaScript GC from copying data
|
||||
- ❌ Lock memory pages (no swap)
|
||||
- ❌ Prevent core dumps from containing data
|
||||
- ❌ Guarantee OS won't page data to disk
|
||||
|
||||
### Future: Native Addon (Optional)
|
||||
|
||||
A native Node.js addon can provide true memory protection:
|
||||
|
||||
**Linux/macOS:**
|
||||
```c
|
||||
#include <sys/mman.h>
|
||||
|
||||
// Lock memory
|
||||
mlock(data, length);
|
||||
|
||||
// Zero and unlock
|
||||
memset(data, 0, length);
|
||||
munlock(data, length);
|
||||
```
|
||||
|
||||
**Windows:**
|
||||
```c
|
||||
#include <windows.h>
|
||||
|
||||
// Lock memory
|
||||
VirtualLock(data, length);
|
||||
|
||||
// Zero and unlock
|
||||
SecureZeroMemory(data, length);
|
||||
VirtualUnlock(data, length);
|
||||
```
|
||||
|
||||
**Installation:**
|
||||
```bash
|
||||
# Optional dependency
|
||||
npm install nomyo-native
|
||||
|
||||
# Will use native addon if available, fallback to pure JS otherwise
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Threat Model
|
||||
|
||||
### Protected Against
|
||||
|
||||
✅ **Network Eavesdropping**
|
||||
- All data encrypted end-to-end
|
||||
- HTTPS transport encryption
|
||||
- Authenticated encryption prevents tampering
|
||||
|
||||
✅ **MITM Attacks**
|
||||
- HTTPS certificate validation
|
||||
- Server public key verification
|
||||
- Warning on HTTP usage
|
||||
|
||||
✅ **Replay Attacks**
|
||||
- Unique nonce per encryption
|
||||
- Authenticated encryption with GCM
|
||||
- Server timestamp validation (server-side)
|
||||
|
||||
✅ **Timing Attacks (Partial)**
|
||||
- Web Crypto API uses constant-time operations
|
||||
- No length leakage in comparisons
|
||||
- Generic error messages from all crypto operations (RSA, AES) — internal engine errors not forwarded
|
||||
|
||||
✅ **Concurrent Key Generation Race**
|
||||
- Promise-chain mutex serialises all `ensureKeys()` callers
|
||||
- No risk of multiple simultaneous key generations overwriting each other
|
||||
|
||||
✅ **Key Compromise (Forward Secrecy)**
|
||||
- Ephemeral AES keys
|
||||
- Each request uses new AES key
|
||||
- Compromise of one key affects only that request
|
||||
|
||||
### Not Protected Against (Pure JS)
|
||||
|
||||
⚠️ **Memory Inspection**
|
||||
- Admin/root can read process memory
|
||||
- Debuggers can access sensitive data
|
||||
- Core dumps may contain keys
|
||||
- **Mitigation**: Use native addon for `mlock` support
|
||||
|
||||
⚠️ **Swap File Exposure**
|
||||
- OS may page memory to disk
|
||||
- Sensitive data could persist in swap
|
||||
- **Mitigation**: Disable swap or use native addon
|
||||
|
||||
⚠️ **Local Malware**
|
||||
- Keyloggers can capture passwords
|
||||
- Memory scrapers can extract keys
|
||||
- **Mitigation**: Standard OS security practices
|
||||
|
||||
---
|
||||
|
||||
## Comparison: JavaScript vs Python Implementation
|
||||
|
||||
| Feature | Python | JavaScript (Pure) | JavaScript (+ Native Addon) |
|
||||
|---------|--------|-------------------|----------------------------|
|
||||
| **Encryption** | AES-256-GCM | ✅ AES-256-GCM | ✅ AES-256-GCM |
|
||||
| **Key Exchange** | RSA-OAEP-4096 | ✅ RSA-OAEP-4096 | ✅ RSA-OAEP-4096 |
|
||||
| **Memory Locking** | ✅ `mlock` | ❌ Not available | ✅ `mlock` |
|
||||
| **Memory Zeroing** | ✅ Guaranteed | ✅ Best-effort | ✅ Guaranteed |
|
||||
| **Key Persistence** | ✅ File-based | ✅ Node.js only | ✅ Node.js only |
|
||||
| **Browser Support** | ❌ | ✅ | ❌ |
|
||||
| **Zero Dependencies** | ❌ | ✅ | ❌ (native addon) |
|
||||
|
||||
---
|
||||
|
||||
## Audit & Compliance
|
||||
|
||||
### Recommendations for Production
|
||||
|
||||
1. **Code Review**
|
||||
- Review cryptographic implementations
|
||||
- Verify key generation randomness
|
||||
- Check for timing vulnerabilities
|
||||
|
||||
2. **Penetration Testing**
|
||||
- Test against MITM attacks
|
||||
- Verify HTTPS enforcement
|
||||
- Test key management security
|
||||
|
||||
3. **Compliance**
|
||||
- Document security architecture
|
||||
- Risk assessment for pure JS vs native
|
||||
- Decide if `mlock` is required for your use case
|
||||
|
||||
### Known Limitations
|
||||
|
||||
This implementation uses **pure JavaScript** without native addons. For maximum security:
|
||||
- Consider implementing the optional native addon
|
||||
- Or use the Python client in security-critical server environments
|
||||
- Or accept the risk given other security controls (encrypted disk, no swap, etc.)
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- [Web Crypto API Specification](https://www.w3.org/TR/WebCryptoAPI/)
|
||||
- [NIST SP 800-38D](https://csrc.nist.gov/publications/detail/sp/800-38d/final) - GCM mode
|
||||
- [RFC 8017](https://datatracker.ietf.org/doc/html/rfc8017) - RSA-OAEP
|
||||
- [OWASP Password Storage Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html)
|
||||
Loading…
Add table
Add a link
Reference in a new issue