forked from jsnbuchanan/crowd-funder-for-time-pwa
Add detailed platform implementations, usage examples, and error handling: - Add comprehensive platform-specific implementations for Web (Dexie) and Capacitor (SQLite) - Include detailed database initialization and security features - Add practical usage examples for account and settings management - Document error handling strategies and edge cases - Add concurrency management and data integrity checks - Include platform transition handling This update provides a complete reference for implementing secure storage across different platforms while maintaining backward compatibility.
2077 lines
62 KiB
Markdown
2077 lines
62 KiB
Markdown
# Secure Storage Implementation Guide for TimeSafari App
|
|
|
|
## Overview
|
|
|
|
This document outlines the implementation of secure storage for the TimeSafari app using Capacitor solutions. Two primary storage options are provided:
|
|
|
|
1. **SQLite with SQLCipher Encryption (Primary Solution)**:
|
|
- Utilizes `@capacitor-community/sqlite` plugin with SQLCipher for 256-bit AES encryption.
|
|
- Supports web and native platforms (iOS, Android).
|
|
- Ideal for complex queries and relational data.
|
|
- Platform-specific SQLCipher implementations:
|
|
- **Android**: Native SQLCipher library.
|
|
- **iOS**: SQLCipher as a drop-in SQLite replacement.
|
|
- **Web**: WebAssembly-based SQLCipher.
|
|
|
|
2. **Capacitor Preferences API (For Small Data)**:
|
|
- Built-in Capacitor solution for lightweight key-value storage.
|
|
- Uses platform-specific secure storage (e.g., Keychain on iOS, EncryptedSharedPreferences on Android).
|
|
- Limited to small datasets; no query capabilities.
|
|
|
|
## Architecture
|
|
|
|
### Directory Structure
|
|
|
|
```
|
|
src/services/
|
|
├── storage/ # Storage services
|
|
│ ├── SQLiteService.ts # Core SQLite service
|
|
│ ├── EncryptionService.ts # SQLCipher encryption handling
|
|
│ ├── KeyManagementService.ts # Secure key management
|
|
│ ├── platforms/ # Platform-specific implementations
|
|
│ │ ├── WebStorageService.ts
|
|
│ │ ├── CapacitorStorageService.ts
|
|
│ │ └── ElectronStorageService.ts
|
|
│ ├── migrations/ # Database migration scripts
|
|
│ │ ├── 001_initial.ts # Initial schema setup
|
|
│ │ └── 002_encryption.ts # Encryption configuration
|
|
│ └── types/
|
|
│ └── storage.types.ts # TypeScript type definitions
|
|
└── PlatformService.ts # Existing platform service interface
|
|
```
|
|
|
|
### Platform Service Integration
|
|
|
|
The storage implementation integrates with the existing `PlatformService` interface to provide platform-specific storage operations. This allows for consistent API usage across platforms while maintaining platform-specific optimizations.
|
|
|
|
### Storage Service Interface
|
|
|
|
```typescript
|
|
// src/services/PlatformService.ts
|
|
import { Account } from '../db/tables/accounts';
|
|
import { Contact } from '../db/tables/contacts';
|
|
import { Settings } from '../db/tables/settings';
|
|
import { Secret } from '../db/tables/secret';
|
|
|
|
export interface PlatformService {
|
|
// ... existing platform methods ...
|
|
|
|
// Storage Operations
|
|
// Secret Database
|
|
openSecretDatabase(): Promise<void>;
|
|
getMasterSecret(): Promise<Secret | undefined>;
|
|
setMasterSecret(secret: Secret): Promise<void>;
|
|
|
|
// Accounts Database
|
|
openAccountsDatabase(): Promise<void>;
|
|
getAccountsCount(): Promise<number>;
|
|
getAllAccounts(): Promise<Account[]>;
|
|
getAccountByDid(did: string): Promise<Account | undefined>;
|
|
addAccount(account: Account): Promise<void>;
|
|
updateAccountSettings(did: string, settings: Partial<Settings>): Promise<void>;
|
|
|
|
// Settings Operations
|
|
getDefaultSettings(): Promise<Settings | undefined>;
|
|
getAccountSettings(did: string): Promise<Settings | undefined>;
|
|
updateSettings(key: string, changes: Partial<Settings>): Promise<void>;
|
|
addSettings(settings: Settings): Promise<void>;
|
|
getSettingsCount(): Promise<number>;
|
|
|
|
// Contacts Operations
|
|
getAllContacts(): Promise<Contact[]>;
|
|
addContact(contact: Contact): Promise<void>;
|
|
|
|
// Database Management
|
|
deleteDatabase(): Promise<void>;
|
|
importDatabase(data: Blob): Promise<void>;
|
|
exportDatabase(): Promise<Blob>;
|
|
|
|
// Migration Support
|
|
isFirstInstall(): Promise<boolean>;
|
|
needsMigration(): Promise<boolean>;
|
|
performMigration(): Promise<void>;
|
|
|
|
// Platform Detection
|
|
isCapacitor(): boolean;
|
|
isElectron(): boolean;
|
|
isWeb(): boolean;
|
|
getCapabilities(): {
|
|
hasFileSystem: boolean;
|
|
hasSecureStorage: boolean;
|
|
hasBiometrics: boolean;
|
|
isIOS: boolean;
|
|
isAndroid: boolean;
|
|
};
|
|
}
|
|
```
|
|
|
|
### Platform-Specific Implementations
|
|
|
|
1. **Web Platform (Dexie)**
|
|
```typescript
|
|
// src/services/platforms/WebPlatformService.ts
|
|
export class WebPlatformService implements PlatformService {
|
|
// ... existing web platform methods ...
|
|
|
|
// Secret Database
|
|
async openSecretDatabase(): Promise<void> {
|
|
await secretDB.open();
|
|
}
|
|
|
|
async getMasterSecret(): Promise<Secret | undefined> {
|
|
return await secretDB.secret.get(MASTER_SECRET_KEY);
|
|
}
|
|
|
|
async setMasterSecret(secret: Secret): Promise<void> {
|
|
await secretDB.secret.put(secret);
|
|
}
|
|
|
|
// Accounts Database
|
|
async openAccountsDatabase(): Promise<void> {
|
|
const accountsDB = await accountsDBPromise;
|
|
await accountsDB.open();
|
|
}
|
|
|
|
async getAccountsCount(): Promise<number> {
|
|
const accountsDB = await accountsDBPromise;
|
|
return await accountsDB.accounts.count();
|
|
}
|
|
|
|
// ... implement other storage methods using Dexie ...
|
|
}
|
|
```
|
|
|
|
2. **Capacitor Platform (SQLite)**
|
|
```typescript
|
|
// src/services/platforms/CapacitorPlatformService.ts
|
|
export class CapacitorPlatformService implements PlatformService {
|
|
private sqliteService: SQLiteService;
|
|
private keyManagement: KeyManagementService;
|
|
|
|
constructor() {
|
|
this.sqliteService = SQLiteService.getInstance();
|
|
this.keyManagement = KeyManagementService.getInstance();
|
|
}
|
|
|
|
// Secret Database
|
|
async openSecretDatabase(): Promise<void> {
|
|
await this.sqliteService.initialize({
|
|
database: 'timesafari_secret.db',
|
|
encrypted: true,
|
|
version: 1
|
|
});
|
|
}
|
|
|
|
async getMasterSecret(): Promise<Secret | undefined> {
|
|
const result = await this.sqliteService.query<Secret>(
|
|
'SELECT * FROM secret WHERE id = ?',
|
|
[MASTER_SECRET_KEY]
|
|
);
|
|
return result.value?.[0];
|
|
}
|
|
|
|
async setMasterSecret(secret: Secret): Promise<void> {
|
|
await this.sqliteService.query(
|
|
'INSERT OR REPLACE INTO secret (id, secret) VALUES (?, ?)',
|
|
[secret.id, secret.secret]
|
|
);
|
|
}
|
|
|
|
// Accounts Database
|
|
async openAccountsDatabase(): Promise<void> {
|
|
await this.sqliteService.initialize({
|
|
database: 'timesafari_accounts.db',
|
|
encrypted: true,
|
|
version: 1,
|
|
key: await this.keyManagement.getEncryptionKey()
|
|
});
|
|
}
|
|
|
|
async getAccountsCount(): Promise<number> {
|
|
const result = await this.sqliteService.query<{ count: number }>(
|
|
'SELECT COUNT(*) as count FROM accounts'
|
|
);
|
|
return result.value?.[0]?.count ?? 0;
|
|
}
|
|
|
|
// ... implement other storage methods using SQLite ...
|
|
}
|
|
```
|
|
|
|
### Usage Example
|
|
|
|
```typescript
|
|
// src/services/storage/StorageService.ts
|
|
import { PlatformServiceFactory } from '../PlatformServiceFactory';
|
|
|
|
export class StorageService {
|
|
private static instance: StorageService | null = null;
|
|
private platformService: PlatformService;
|
|
|
|
private constructor() {
|
|
this.platformService = PlatformServiceFactory.getInstance();
|
|
}
|
|
|
|
static getInstance(): StorageService {
|
|
if (!StorageService.instance) {
|
|
StorageService.instance = new StorageService();
|
|
}
|
|
return StorageService.instance;
|
|
}
|
|
|
|
async initialize(): Promise<void> {
|
|
// Initialize secret database
|
|
await this.platformService.openSecretDatabase();
|
|
|
|
// Initialize accounts database
|
|
await this.platformService.openAccountsDatabase();
|
|
|
|
// Check if migration is needed
|
|
if (await this.platformService.needsMigration()) {
|
|
await this.platformService.performMigration();
|
|
}
|
|
}
|
|
|
|
async getAccountByDid(did: string): Promise<Account | undefined> {
|
|
return await this.platformService.getAccountByDid(did);
|
|
}
|
|
|
|
async updateAccountSettings(did: string, settings: Partial<Settings>): Promise<void> {
|
|
await this.platformService.updateAccountSettings(did, settings);
|
|
}
|
|
|
|
// ... implement other storage methods delegating to platformService ...
|
|
}
|
|
```
|
|
|
|
### Migration Strategy
|
|
|
|
The platform service interface supports a smooth migration from Dexie to SQLite:
|
|
|
|
1. **Web Platform**:
|
|
- Continues using Dexie implementation
|
|
- No changes to existing code
|
|
- Maintains backward compatibility
|
|
|
|
2. **Capacitor Platform**:
|
|
- Uses SQLite with SQLCipher
|
|
- Implements platform-specific security
|
|
- Handles migration from Dexie if needed
|
|
|
|
3. **Migration Process**:
|
|
```typescript
|
|
// src/services/storage/migration/MigrationService.ts
|
|
export class MigrationService {
|
|
async migrateFromDexieToSQLite(): Promise<void> {
|
|
// 1. Export data from Dexie
|
|
const accounts = await this.platformService.getAllAccounts();
|
|
const settings = await this.platformService.getAllSettings();
|
|
const contacts = await this.platformService.getAllContacts();
|
|
|
|
// 2. Initialize SQLite
|
|
await this.platformService.openAccountsDatabase();
|
|
|
|
// 3. Import data to SQLite
|
|
for (const account of accounts) {
|
|
await this.platformService.addAccount(account);
|
|
}
|
|
|
|
// 4. Import settings and contacts
|
|
for (const setting of settings) {
|
|
await this.platformService.addSettings(setting);
|
|
}
|
|
for (const contact of contacts) {
|
|
await this.platformService.addContact(contact);
|
|
}
|
|
|
|
// 5. Verify migration
|
|
await this.verifyMigration();
|
|
}
|
|
}
|
|
```
|
|
|
|
## Dependencies
|
|
|
|
Verified and updated dependencies for the implementation:
|
|
|
|
- **Node.js Dependencies**:
|
|
```bash
|
|
npm install @capacitor-community/sqlite@6.0.0
|
|
npm install @capacitor/core@6.1.2
|
|
npm install @capacitor/preferences@6.0.2
|
|
npm install @jlongster/sql.js@1.10.3 --save-dev # For web SQLCipher
|
|
```
|
|
|
|
- **Android (build.gradle)**:
|
|
```gradle
|
|
implementation 'net.zetetic:android-database-sqlcipher:4.6.1'
|
|
```
|
|
|
|
- **iOS (Podfile)**:
|
|
```ruby
|
|
pod 'SQLCipher', '~> 4.6.0'
|
|
```
|
|
|
|
Run `npx cap sync` after installing dependencies to update native projects.
|
|
|
|
## Type Definitions
|
|
|
|
```typescript
|
|
// src/services/storage/types/storage.types.ts
|
|
|
|
export interface StorageOptions {
|
|
encrypted?: boolean; // Whether to use encryption (default: true)
|
|
database: string; // Database name
|
|
version: number; // Migration version
|
|
key?: string; // Encryption key (optional)
|
|
}
|
|
|
|
export interface StorageResult<T> {
|
|
success: boolean; // Operation success status
|
|
error?: string; // Error message if operation failed
|
|
value?: T; // Stored/retrieved value
|
|
}
|
|
|
|
export interface StorageService {
|
|
initialize(options: StorageOptions): Promise<void>;
|
|
setItem<T>(key: string, value: T): Promise<StorageResult<T>>;
|
|
getItem<T>(key: string): Promise<StorageResult<T>>;
|
|
removeItem(key: string): Promise<StorageResult<void>>;
|
|
query<T>(sql: string, params?: any[]): Promise<StorageResult<T[]>>;
|
|
}
|
|
```
|
|
|
|
## Implementation Details
|
|
|
|
### 1. SQLite Service
|
|
|
|
```typescript
|
|
// src/services/storage/SQLiteService.ts
|
|
import { CapacitorSQLite, SQLiteConnection, SQLiteDBConnection } from '@capacitor-community/sqlite';
|
|
import { EncryptionService } from './EncryptionService';
|
|
|
|
export class SQLiteService implements StorageService {
|
|
private static instance: SQLiteService;
|
|
private connection: SQLiteConnection;
|
|
private db?: SQLiteDBConnection;
|
|
private encryptionService: EncryptionService;
|
|
|
|
private constructor() {
|
|
this.connection = new SQLiteConnection(CapacitorSQLite);
|
|
this.encryptionService = new EncryptionService();
|
|
}
|
|
|
|
static getInstance(): SQLiteService {
|
|
if (!SQLiteService.instance) {
|
|
SQLiteService.instance = new SQLiteService();
|
|
}
|
|
return SQLiteService.instance;
|
|
}
|
|
|
|
async initialize(options: StorageOptions): Promise<void> {
|
|
try {
|
|
this.db = await this.connection.createConnection(
|
|
options.database,
|
|
options.encrypted ?? true,
|
|
options.encrypted ? 'encryption' : 'no-encryption',
|
|
options.version,
|
|
false
|
|
);
|
|
|
|
await this.db.open();
|
|
|
|
if (options.encrypted) {
|
|
const encryptionKey = options.key ?? await this.encryptionService.getEncryptionKey();
|
|
await this.db.execute(`PRAGMA key = '${encryptionKey}'`);
|
|
}
|
|
|
|
await this.runMigrations();
|
|
} catch (error) {
|
|
throw new Error(`Database initialization failed: ${(error as Error).message}`);
|
|
}
|
|
}
|
|
|
|
async setItem<T>(key: string, value: T): Promise<StorageResult<T>> {
|
|
if (!this.db) throw new Error('Database not initialized');
|
|
try {
|
|
const encryptedValue = await this.encryptionService.encrypt(JSON.stringify(value));
|
|
await this.db.run(
|
|
'INSERT OR REPLACE INTO storage (key, value) VALUES (?, ?)',
|
|
[key, encryptedValue]
|
|
);
|
|
return { success: true, value };
|
|
} catch (error) {
|
|
return { success: false, error: (error as Error).message };
|
|
}
|
|
}
|
|
|
|
async getItem<T>(key: string): Promise<StorageResult<T>> {
|
|
if (!this.db) throw new Error('Database not initialized');
|
|
try {
|
|
const result = await this.db.query('SELECT value FROM storage WHERE key = ?', [key]);
|
|
if (!result.values?.length) {
|
|
return { success: false, error: 'Key not found' };
|
|
}
|
|
const decryptedValue = await this.encryptionService.decrypt(result.values[0].value);
|
|
return { success: true, value: JSON.parse(decryptedValue) as T };
|
|
} catch (error) {
|
|
return { success: false, error: (error as Error).message };
|
|
}
|
|
}
|
|
|
|
async removeItem(key: string): Promise<StorageResult<void>> {
|
|
if (!this.db) throw new Error('Database not initialized');
|
|
try {
|
|
await this.db.run('DELETE FROM storage WHERE key = ?', [key]);
|
|
return { success: true };
|
|
} catch (error) {
|
|
return { success: false, error: (error as Error).message };
|
|
}
|
|
}
|
|
|
|
async query<T>(sql: string, params: any[] = []): Promise<StorageResult<T[]>> {
|
|
if (!this.db) throw new Error('Database not initialized');
|
|
try {
|
|
const result = await this.db.query(sql, params);
|
|
return { success: true, value: (result.values || []) as T[] };
|
|
} catch (error) {
|
|
return { success: false, error: (error as Error).message };
|
|
}
|
|
}
|
|
|
|
private async runMigrations(): Promise<void> {
|
|
if (!this.db) throw new Error('Database not initialized');
|
|
await this.db.execute(`
|
|
CREATE TABLE IF NOT EXISTS storage (
|
|
key TEXT PRIMARY KEY,
|
|
value TEXT
|
|
)
|
|
`);
|
|
// Add additional migration scripts from migrations/ folder as needed
|
|
}
|
|
}
|
|
```
|
|
|
|
### 2. Encryption Service
|
|
|
|
```typescript
|
|
// src/services/storage/EncryptionService.ts
|
|
import { SQLiteDBConnection } from '@capacitor-community/sqlite';
|
|
import { Capacitor } from '@capacitor/core';
|
|
|
|
export class EncryptionService {
|
|
private encryptionKey: string | null = null;
|
|
|
|
async getEncryptionKey(): Promise<string> {
|
|
if (!this.encryptionKey) {
|
|
// In a real implementation, use platform-specific secure storage
|
|
// This is a simplified example
|
|
this.encryptionKey = await this.generateKey();
|
|
}
|
|
return this.encryptionKey;
|
|
}
|
|
|
|
async initialize(db: SQLiteDBConnection): Promise<void> {
|
|
const key = await this.getEncryptionKey();
|
|
await db.execute(`PRAGMA key = '${key}'`);
|
|
await db.execute('PRAGMA cipher_default_kdf_iter = 64000');
|
|
await db.execute('PRAGMA cipher_page_size = 4096');
|
|
}
|
|
|
|
async encrypt(value: string): Promise<string> {
|
|
if (Capacitor.isNativePlatform()) {
|
|
// Use platform-specific encryption (e.g., iOS Keychain, Android Keystore)
|
|
return value; // Placeholder for native encryption
|
|
}
|
|
// Web Crypto API for web platform
|
|
const encoder = new TextEncoder();
|
|
const key = await this.getWebCryptoKey();
|
|
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
const encrypted = await crypto.subtle.encrypt(
|
|
{ name: 'AES-GCM', iv },
|
|
key,
|
|
encoder.encode(value)
|
|
);
|
|
return btoa(String.fromCharCode(...new Uint8Array(iv), ...new Uint8Array(encrypted)));
|
|
}
|
|
|
|
async decrypt(value: string): Promise<string> {
|
|
if (Capacitor.isNativePlatform()) {
|
|
// Use platform-specific decryption
|
|
return value; // Placeholder for native decryption
|
|
}
|
|
// Web Crypto API for web platform
|
|
const data = Uint8Array.from(atob(value), c => c.charCodeAt(0));
|
|
const iv = data.slice(0, 12);
|
|
const encrypted = data.slice(12);
|
|
const key = await this.getWebCryptoKey();
|
|
const decrypted = await crypto.subtle.decrypt(
|
|
{ name: 'AES-GCM', iv },
|
|
key,
|
|
encrypted
|
|
);
|
|
return new TextDecoder().decode(decrypted);
|
|
}
|
|
|
|
private async generateKey(): Promise<string> {
|
|
const key = crypto.getRandomValues(new Uint8Array(32));
|
|
return btoa(String.fromCharCode(...key));
|
|
}
|
|
|
|
private async getWebCryptoKey(): Promise<CryptoKey> {
|
|
const key = await this.getEncryptionKey();
|
|
return crypto.subtle.importKey(
|
|
'raw',
|
|
Uint8Array.from(atob(key), c => c.charCodeAt(0)),
|
|
{ name: 'AES-GCM' },
|
|
false,
|
|
['encrypt', 'decrypt']
|
|
);
|
|
}
|
|
}
|
|
```
|
|
|
|
### 3. Preferences Service
|
|
|
|
```typescript
|
|
// src/services/storage/PreferencesService.ts
|
|
import { Preferences } from '@capacitor/preferences';
|
|
import { StorageService, StorageResult } from './types/storage.types';
|
|
|
|
export class PreferencesService implements StorageService {
|
|
async initialize(): Promise<void> {
|
|
// No initialization needed for Preferences API
|
|
}
|
|
|
|
async setItem<T>(key: string, value: T): Promise<StorageResult<T>> {
|
|
try {
|
|
await Preferences.set({ key, value: JSON.stringify(value) });
|
|
return { success: true, value };
|
|
} catch (error) {
|
|
return { success: false, error: (error as Error).message };
|
|
}
|
|
}
|
|
|
|
async getItem<T>(key: string): Promise<StorageResult<T>> {
|
|
try {
|
|
const { value } = await Preferences.get({ key });
|
|
if (!value) {
|
|
return { success: false, error: 'Key not found' };
|
|
}
|
|
return { success: true, value: JSON.parse(value) as T };
|
|
} catch (error) {
|
|
return { success: false, error: (error as Error).message };
|
|
}
|
|
}
|
|
|
|
async removeItem(key: string): Promise<StorageResult<void>> {
|
|
try {
|
|
await Preferences.remove({ key });
|
|
return { success: true };
|
|
} catch (error) {
|
|
return { success: false, error: (error as Error).message };
|
|
}
|
|
}
|
|
|
|
async query<T>(): Promise<StorageResult<T[]>> {
|
|
return { success: false, error: 'Query not supported in Preferences API' };
|
|
}
|
|
}
|
|
```
|
|
|
|
### 4. Key Management Service
|
|
|
|
```typescript
|
|
// src/services/storage/KeyManagementService.ts
|
|
import { Capacitor } from '@capacitor/core';
|
|
|
|
interface SecureKeyOptions {
|
|
useBiometrics: boolean;
|
|
keySize: number;
|
|
keyAlgorithm: 'AES-GCM' | 'AES-CBC';
|
|
}
|
|
|
|
export class KeyManagementService {
|
|
private static instance: KeyManagementService;
|
|
|
|
private constructor() {}
|
|
|
|
static getInstance(): KeyManagementService {
|
|
if (!KeyManagementService.instance) {
|
|
KeyManagementService.instance = new KeyManagementService();
|
|
}
|
|
return KeyManagementService.instance;
|
|
}
|
|
|
|
async generateSecureKey(options: SecureKeyOptions): Promise<string> {
|
|
const key = await this.generateRandomKey(options.keySize);
|
|
if (Capacitor.isNativePlatform()) {
|
|
return Capacitor.getPlatform() === 'ios'
|
|
? this.encryptWithSecureEnclave(key, options)
|
|
: this.encryptWithAndroidKeystore(key, options);
|
|
}
|
|
return this.encryptWithWebCrypto(key, options);
|
|
}
|
|
|
|
private async generateRandomKey(keySize: number): Promise<string> {
|
|
const key = crypto.getRandomValues(new Uint8Array(keySize / 8));
|
|
return btoa(String.fromCharCode(...key));
|
|
}
|
|
|
|
private async encryptWithSecureEnclave(key: string, options: SecureKeyOptions): Promise<string> {
|
|
// iOS Secure Enclave implementation (placeholder)
|
|
// Use Keychain with biometric protection
|
|
return key; // Implement platform-specific code
|
|
}
|
|
|
|
private async encryptWithAndroidKeystore(key: string, options: SecureKeyOptions): Promise<string> {
|
|
// Android Keystore implementation (placeholder)
|
|
// Use EncryptedSharedPreferences with biometric protection
|
|
return key; // Implement platform-specific code
|
|
}
|
|
|
|
private async encryptWithWebCrypto(key: string, options: SecureKeyOptions): Promise<string> {
|
|
// Web Crypto API implementation
|
|
const encoder = new TextEncoder();
|
|
const cryptoKey = await crypto.subtle.generateKey(
|
|
{ name: options.keyAlgorithm, length: options.keySize },
|
|
true,
|
|
['encrypt', 'decrypt']
|
|
);
|
|
const exportedKey = await crypto.subtle.exportKey('raw', cryptoKey);
|
|
return btoa(String.fromCharCode(...new Uint8Array(exportedKey)));
|
|
}
|
|
}
|
|
```
|
|
|
|
### 5. Biometric Service
|
|
|
|
```typescript
|
|
// src/services/storage/BiometricService.ts
|
|
import { Capacitor } from '@capacitor/core';
|
|
|
|
export class BiometricService {
|
|
async authenticate(): Promise<boolean> {
|
|
if (!Capacitor.isNativePlatform()) {
|
|
return true; // Web fallback (no biometric auth)
|
|
}
|
|
try {
|
|
// Use Capacitor Biometric plugin (e.g., @capacitor-community/biometric-auth)
|
|
// Placeholder for actual implementation
|
|
return true;
|
|
} catch (error) {
|
|
console.error('Biometric authentication failed:', error);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## Migration Strategy
|
|
|
|
### Two-Day Implementation Timeline (Revised)
|
|
|
|
#### Day 1: Core Implementation and Basic Security
|
|
|
|
**Morning (4 hours)**:
|
|
1. **Essential Setup (1 hour)**
|
|
```bash
|
|
# Install core dependencies
|
|
npm install @capacitor-community/sqlite@6.0.0 @capacitor/core@6.1.2
|
|
npx cap sync
|
|
```
|
|
|
|
2. **Core Services Implementation (3 hours)**
|
|
- Implement basic `SQLiteService` with encryption support
|
|
- Create simplified `KeyManagementService` for platform-specific key storage
|
|
- Set up initial database schema
|
|
```typescript
|
|
// Priority 1: Core tables
|
|
export const initialSchema = `
|
|
CREATE TABLE IF NOT EXISTS accounts (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
did TEXT UNIQUE,
|
|
publicKeyHex TEXT,
|
|
identity TEXT
|
|
);
|
|
CREATE TABLE IF NOT EXISTS secret (
|
|
id INTEGER PRIMARY KEY,
|
|
secret TEXT
|
|
);
|
|
`;
|
|
```
|
|
|
|
**Afternoon (4 hours)**:
|
|
1. **Platform-Specific Security (2 hours)**
|
|
- Implement basic platform detection
|
|
- Set up platform-specific key storage:
|
|
- iOS: Basic Keychain integration
|
|
- Android: Basic Keystore integration
|
|
- Web: Web Crypto API with IndexedDB
|
|
- Defer advanced features (Secure Enclave, StrongBox) to post-migration
|
|
|
|
2. **Migration Utilities (2 hours)**
|
|
- Create basic data export from Dexie
|
|
- Implement simple data import to SQLite
|
|
- Add basic validation
|
|
```typescript
|
|
// Simplified migration utility
|
|
export class MigrationUtils {
|
|
async migrateData(): Promise<void> {
|
|
// 1. Export from Dexie
|
|
const data = await this.exportFromDexie();
|
|
|
|
// 2. Initialize SQLite
|
|
await this.initializeSQLite();
|
|
|
|
// 3. Import data
|
|
await this.importToSQLite(data);
|
|
|
|
// 4. Basic validation
|
|
await this.validateMigration();
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Day 2: Migration and Testing
|
|
|
|
**Morning (4 hours)**:
|
|
1. **Migration Implementation (2 hours)**
|
|
- Implement migration script
|
|
- Add basic error handling
|
|
- Create rollback capability
|
|
```typescript
|
|
export class DatabaseMigration {
|
|
async migrate(): Promise<void> {
|
|
try {
|
|
// 1. Backup existing data
|
|
await this.backupExistingData();
|
|
|
|
// 2. Perform migration
|
|
await this.migrationUtils.migrateData();
|
|
|
|
// 3. Verify migration
|
|
const isValid = await this.verifyMigration();
|
|
if (!isValid) {
|
|
await this.rollback();
|
|
throw new Error('Migration validation failed');
|
|
}
|
|
} catch (error) {
|
|
await this.rollback();
|
|
throw error;
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
2. **Basic Security Integration (2 hours)**
|
|
- Implement basic biometric check
|
|
- Add simple key derivation
|
|
- Set up basic encryption
|
|
```typescript
|
|
export class SecurityService {
|
|
async initialize(): Promise<void> {
|
|
// Basic security setup
|
|
await this.setupEncryption();
|
|
await this.setupBiometrics();
|
|
}
|
|
}
|
|
```
|
|
|
|
**Afternoon (4 hours)**:
|
|
1. **Testing and Validation (2 hours)**
|
|
- Unit tests for core functionality
|
|
- Basic integration tests
|
|
- Platform-specific tests
|
|
```typescript
|
|
describe('Database Migration', () => {
|
|
it('should migrate accounts successfully', async () => {
|
|
// Basic migration test
|
|
});
|
|
|
|
it('should handle encryption correctly', async () => {
|
|
// Basic encryption test
|
|
});
|
|
});
|
|
```
|
|
|
|
2. **Documentation and Cleanup (2 hours)**
|
|
- Update documentation
|
|
- Clean up code
|
|
- Create basic backup/restore functionality
|
|
|
|
### Post-Migration Features (To Be Implemented Later)
|
|
|
|
1. **Enhanced Security**
|
|
- Advanced platform-specific features (Secure Enclave, StrongBox)
|
|
- Sophisticated key rotation
|
|
- Advanced biometric integration
|
|
|
|
2. **Advanced Backup**
|
|
- Encrypted backup system
|
|
- Recovery key management
|
|
- Cross-device backup
|
|
|
|
3. **Performance Optimization**
|
|
- Query optimization
|
|
- Index management
|
|
- Caching strategies
|
|
|
|
### Critical Path Items (Detailed Breakdown)
|
|
|
|
#### Build System Integration (Day 1 Morning)
|
|
|
|
1. **Vite Configuration for Capacitor**
|
|
```typescript
|
|
// vite.config.ts
|
|
import { defineConfig } from 'vite';
|
|
import { Capacitor } from '@capacitor/core';
|
|
|
|
export default defineConfig({
|
|
// ... existing config ...
|
|
build: {
|
|
// Ensure proper bundling for Capacitor
|
|
target: 'es2015',
|
|
rollupOptions: {
|
|
output: {
|
|
// Handle platform-specific code
|
|
format: 'es',
|
|
manualChunks: {
|
|
'capacitor-core': ['@capacitor/core'],
|
|
'sqlite': ['@capacitor-community/sqlite']
|
|
}
|
|
}
|
|
}
|
|
},
|
|
plugins: [
|
|
// ... existing plugins ...
|
|
{
|
|
name: 'capacitor-platform',
|
|
config(config, { command }) {
|
|
// Add platform-specific environment variables
|
|
return {
|
|
define: {
|
|
'process.env.CAPACITOR_PLATFORM': JSON.stringify(Capacitor.getPlatform())
|
|
}
|
|
};
|
|
}
|
|
}
|
|
]
|
|
});
|
|
```
|
|
|
|
2. **Platform Detection and Conditional Imports**
|
|
```typescript
|
|
// src/services/storage/platform-detection.ts
|
|
import { Capacitor } from '@capacitor/core';
|
|
|
|
export const isNativePlatform = Capacitor.isNativePlatform();
|
|
export const platform = Capacitor.getPlatform();
|
|
|
|
// Conditional imports for platform-specific code
|
|
export const getPlatformService = async () => {
|
|
if (!isNativePlatform) {
|
|
return null; // Web platform uses existing implementation
|
|
}
|
|
|
|
switch (platform) {
|
|
case 'ios':
|
|
return (await import('./platforms/ios')).IOSStorageService;
|
|
case 'android':
|
|
return (await import('./platforms/android')).AndroidStorageService;
|
|
default:
|
|
throw new Error(`Unsupported platform: ${platform}`);
|
|
}
|
|
};
|
|
```
|
|
|
|
#### Day 1 Critical Path
|
|
|
|
1. **Core Database Setup (Morning)**
|
|
- [ ] SQLite plugin installation and configuration
|
|
- [ ] Basic database schema implementation
|
|
- [ ] Platform detection integration
|
|
- [ ] Vite build configuration for Capacitor
|
|
- [ ] Basic encryption setup
|
|
|
|
2. **Platform-Specific Implementation (Afternoon)**
|
|
- [ ] iOS Keychain integration
|
|
- Basic key storage
|
|
- Error handling
|
|
- Platform detection
|
|
- [ ] Android Keystore integration
|
|
- Basic key storage
|
|
- Error handling
|
|
- Platform detection
|
|
- [ ] Web platform detection
|
|
- Skip implementation for web
|
|
- Maintain existing Dexie implementation
|
|
|
|
3. **Migration Utilities (Afternoon)**
|
|
- [ ] Data export from Dexie
|
|
- Account data
|
|
- Secret data
|
|
- Basic validation
|
|
- [ ] SQLite import
|
|
- Table creation
|
|
- Data import
|
|
- Basic error handling
|
|
|
|
#### Day 2 Critical Path
|
|
|
|
1. **Migration Implementation (Morning)**
|
|
- [ ] Migration script
|
|
```typescript
|
|
// src/services/storage/migration/MigrationScript.ts
|
|
export class MigrationScript {
|
|
async execute(): Promise<void> {
|
|
// 1. Platform check
|
|
if (!isNativePlatform) {
|
|
console.log('Skipping migration on web platform');
|
|
return;
|
|
}
|
|
|
|
// 2. Backup
|
|
await this.backupExistingData();
|
|
|
|
// 3. Migration
|
|
await this.performMigration();
|
|
|
|
// 4. Verification
|
|
await this.verifyMigration();
|
|
}
|
|
}
|
|
```
|
|
- [ ] Rollback mechanism
|
|
- [ ] Error handling
|
|
- [ ] Platform-specific validation
|
|
|
|
2. **Security Integration (Morning)**
|
|
- [ ] Basic biometric check
|
|
- [ ] Key derivation
|
|
- [ ] Platform-specific encryption
|
|
- [ ] Error handling for security features
|
|
|
|
3. **Testing and Validation (Afternoon)**
|
|
- [ ] Unit tests
|
|
```typescript
|
|
// src/services/storage/__tests__/StorageService.spec.ts
|
|
describe('StorageService', () => {
|
|
it('should use SQLite on native platforms', async () => {
|
|
if (isNativePlatform) {
|
|
const service = await getPlatformService();
|
|
expect(service).toBeDefined();
|
|
// ... more tests
|
|
}
|
|
});
|
|
|
|
it('should skip migration on web', async () => {
|
|
if (!isNativePlatform) {
|
|
const migration = new MigrationScript();
|
|
await migration.execute();
|
|
// Verify web implementation unchanged
|
|
}
|
|
});
|
|
});
|
|
```
|
|
- [ ] Integration tests
|
|
- [ ] Platform-specific tests
|
|
- [ ] Migration validation
|
|
|
|
4. **Documentation and Cleanup (Afternoon)**
|
|
- [ ] Update documentation
|
|
- [ ] Code cleanup
|
|
- [ ] Basic backup/restore
|
|
- [ ] Build system verification
|
|
|
|
### Build System Integration Details
|
|
|
|
1. **Vite Configuration for Capacitor**
|
|
```typescript
|
|
// vite.config.ts
|
|
import { defineConfig } from 'vite';
|
|
import { Capacitor } from '@capacitor/core';
|
|
|
|
const capacitorConfig = {
|
|
// Platform-specific entry points
|
|
input: {
|
|
main: 'src/main.ts',
|
|
capacitor: 'src/capacitor.ts'
|
|
},
|
|
// Platform-specific output
|
|
output: {
|
|
format: 'es',
|
|
dir: 'dist',
|
|
entryFileNames: (chunkInfo) => {
|
|
return chunkInfo.name === 'capacitor'
|
|
? 'capacitor/[name].[hash].js'
|
|
: '[name].[hash].js';
|
|
}
|
|
}
|
|
};
|
|
|
|
export default defineConfig({
|
|
// ... existing config ...
|
|
build: {
|
|
...capacitorConfig,
|
|
rollupOptions: {
|
|
external: [
|
|
// External dependencies for Capacitor
|
|
'@capacitor/core',
|
|
'@capacitor-community/sqlite'
|
|
],
|
|
output: {
|
|
globals: {
|
|
'@capacitor/core': 'Capacitor',
|
|
'@capacitor-community/sqlite': 'CapacitorSQLite'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
2. **Platform-Specific Entry Point**
|
|
```typescript
|
|
// src/capacitor.ts
|
|
import { Capacitor } from '@capacitor/core';
|
|
import { SQLiteService } from './services/storage/SQLiteService';
|
|
|
|
// Only initialize Capacitor-specific services on native platforms
|
|
if (Capacitor.isNativePlatform()) {
|
|
const initializeCapacitor = async () => {
|
|
const sqliteService = SQLiteService.getInstance();
|
|
await sqliteService.initialize({
|
|
database: 'timesafari.db',
|
|
encrypted: true,
|
|
version: 1
|
|
});
|
|
};
|
|
|
|
initializeCapacitor().catch(console.error);
|
|
}
|
|
```
|
|
|
|
3. **Build Scripts**
|
|
```json
|
|
// package.json
|
|
{
|
|
"scripts": {
|
|
"build": "vite build",
|
|
"build:capacitor": "vite build --mode capacitor",
|
|
"build:ios": "vite build --mode capacitor --platform ios",
|
|
"build:android": "vite build --mode capacitor --platform android",
|
|
"cap:sync": "npm run build:capacitor && npx cap sync",
|
|
"cap:ios": "npm run build:ios && npx cap sync ios",
|
|
"cap:android": "npm run build:android && npx cap sync android"
|
|
}
|
|
}
|
|
```
|
|
|
|
4. **Environment Configuration**
|
|
```typescript
|
|
// src/config/environment.ts
|
|
import { Capacitor } from '@capacitor/core';
|
|
|
|
export const environment = {
|
|
isNative: Capacitor.isNativePlatform(),
|
|
platform: Capacitor.getPlatform(),
|
|
storage: {
|
|
type: Capacitor.isNativePlatform() ? 'sqlite' : 'dexie',
|
|
// Platform-specific configuration
|
|
sqlite: {
|
|
database: 'timesafari.db',
|
|
encrypted: true,
|
|
version: 1
|
|
}
|
|
}
|
|
};
|
|
```
|
|
|
|
### Success Criteria (Updated)
|
|
|
|
1. **Build System**
|
|
- [ ] Vite configuration properly handles Capacitor builds
|
|
- [ ] Platform-specific code is correctly bundled
|
|
- [ ] Web implementation remains unchanged
|
|
- [ ] Build scripts work for all platforms
|
|
|
|
2. **Day 1**
|
|
- [ ] SQLite database operational on native platforms
|
|
- [ ] Basic encryption working
|
|
- [ ] Migration utilities ready
|
|
- [ ] Platform detection working
|
|
- [ ] Build system integration complete
|
|
|
|
3. **Day 2**
|
|
- [ ] Successful migration on native platforms
|
|
- [ ] Basic security implemented
|
|
- [ ] Core functionality tested
|
|
- [ ] Rollback capability verified
|
|
- [ ] Web implementation unaffected
|
|
|
|
## Security Considerations
|
|
|
|
- **Key Management**: Keys stored in iOS Keychain, Android Keystore, or Web Crypto API. Never stored in plaintext.
|
|
- **Data Protection**: 256-bit AES-GCM encryption via SQLCipher. Data bound to device and user authentication.
|
|
- **Platform-Specific**:
|
|
- **iOS**: Secure Enclave and Keychain with Face ID/Touch ID.
|
|
- **Android**: Android Keystore with BiometricPrompt.
|
|
- **Web**: Web Crypto API with secure storage fallback.
|
|
|
|
## Performance Considerations
|
|
|
|
- Use transactions for batch operations.
|
|
- Implement proper indexing for query performance.
|
|
- Cache frequently accessed data.
|
|
- Monitor memory usage and optimize large datasets.
|
|
|
|
## Future Improvements
|
|
|
|
- Key rotation support.
|
|
- Backup and restore capabilities.
|
|
- Enhanced biometric options.
|
|
- Cross-device synchronization.
|
|
|
|
## Maintenance
|
|
|
|
- Regular security audits.
|
|
- Platform-specific updates.
|
|
- Dependency management with semantic versioning.
|
|
- Performance monitoring.
|
|
|
|
## Platform-Specific Implementation Details
|
|
|
|
### iOS Implementation
|
|
|
|
1. **Secure Enclave Integration**
|
|
```typescript
|
|
// src/services/storage/platforms/ios/SecureEnclaveService.ts
|
|
import { Capacitor } from '@capacitor/core';
|
|
import { Keychain } from '@capacitor-community/native-keychain';
|
|
|
|
export class SecureEnclaveService {
|
|
private static readonly KEYCHAIN_SERVICE = 'com.timesafari.securestorage';
|
|
private static readonly KEYCHAIN_ACCESS_GROUP = 'group.com.timesafari.securestorage';
|
|
|
|
async storeKey(key: string, options: SecureKeyOptions): Promise<void> {
|
|
const accessControl = {
|
|
// Require device to be unlocked
|
|
accessible: Keychain.Accessible.WHEN_UNLOCKED,
|
|
// Use Secure Enclave
|
|
accessControl: Keychain.AccessControl.BIOMETRY_ANY,
|
|
// Require user presence
|
|
authenticationType: Keychain.AuthenticationType.BIOMETRICS,
|
|
// Keychain access group for app extension sharing
|
|
accessGroup: this.KEYCHAIN_ACCESS_GROUP
|
|
};
|
|
|
|
await Keychain.set({
|
|
key: 'sqlite_encryption_key',
|
|
value: key,
|
|
service: this.KEYCHAIN_SERVICE,
|
|
...accessControl
|
|
});
|
|
}
|
|
|
|
async retrieveKey(): Promise<string> {
|
|
const result = await Keychain.get({
|
|
key: 'sqlite_encryption_key',
|
|
service: this.KEYCHAIN_SERVICE
|
|
});
|
|
|
|
if (!result.value) {
|
|
throw new Error('Encryption key not found in Keychain');
|
|
}
|
|
|
|
return result.value;
|
|
}
|
|
}
|
|
```
|
|
|
|
2. **Face ID/Touch ID Integration**
|
|
```typescript
|
|
// src/services/storage/platforms/ios/BiometricService.ts
|
|
import { BiometricAuth } from '@capacitor-community/biometric-auth';
|
|
|
|
export class IOSBiometricService {
|
|
async authenticate(): Promise<boolean> {
|
|
const available = await BiometricAuth.isAvailable();
|
|
if (!available.has) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
const result = await BiometricAuth.verify({
|
|
reason: 'Authenticate to access secure data',
|
|
title: 'TimeSafari Authentication',
|
|
subtitle: 'Verify your identity',
|
|
description: 'Use Face ID or Touch ID to access your secure data',
|
|
negativeButtonText: 'Cancel'
|
|
});
|
|
|
|
return result.verified;
|
|
} catch (error) {
|
|
console.error('Biometric authentication failed:', error);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Android Implementation
|
|
|
|
1. **Android Keystore Integration**
|
|
```typescript
|
|
// src/services/storage/platforms/android/KeystoreService.ts
|
|
import { AndroidKeystore } from '@capacitor-community/android-keystore';
|
|
|
|
export class AndroidKeystoreService {
|
|
private static readonly KEY_ALIAS = 'timesafari_sqlite_key';
|
|
|
|
async storeKey(key: string, options: SecureKeyOptions): Promise<void> {
|
|
const keyGenParameterSpec = {
|
|
keyAlias: this.KEY_ALIAS,
|
|
purposes: ['ENCRYPT', 'DECRYPT'],
|
|
blockModes: ['GCM'],
|
|
encryptionPaddings: ['NoPadding'],
|
|
keySize: 256,
|
|
userAuthenticationRequired: true,
|
|
userAuthenticationValidityDurationSeconds: -1,
|
|
// Use StrongBox if available
|
|
isStrongBoxBacked: true
|
|
};
|
|
|
|
await AndroidKeystore.generateKey(keyGenParameterSpec);
|
|
await AndroidKeystore.encrypt({
|
|
keyAlias: this.KEY_ALIAS,
|
|
data: key
|
|
});
|
|
}
|
|
|
|
async retrieveKey(): Promise<string> {
|
|
const result = await AndroidKeystore.decrypt({
|
|
keyAlias: this.KEY_ALIAS
|
|
});
|
|
|
|
return result.decryptedData;
|
|
}
|
|
}
|
|
```
|
|
|
|
2. **Biometric Integration**
|
|
```typescript
|
|
// src/services/storage/platforms/android/BiometricService.ts
|
|
import { BiometricAuth } from '@capacitor-community/biometric-auth';
|
|
|
|
export class AndroidBiometricService {
|
|
async authenticate(): Promise<boolean> {
|
|
const available = await BiometricAuth.isAvailable();
|
|
if (!available.has) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
const result = await BiometricAuth.verify({
|
|
reason: 'Authenticate to access secure data',
|
|
title: 'TimeSafari Authentication',
|
|
subtitle: 'Verify your identity',
|
|
description: 'Use biometric authentication to access your secure data',
|
|
negativeButtonText: 'Cancel',
|
|
// Android-specific options
|
|
allowDeviceCredential: true
|
|
});
|
|
|
|
return result.verified;
|
|
} catch (error) {
|
|
console.error('Biometric authentication failed:', error);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Web Implementation
|
|
|
|
1. **Web Crypto API Integration**
|
|
```typescript
|
|
// src/services/storage/platforms/web/WebCryptoService.ts
|
|
export class WebCryptoService {
|
|
private static readonly KEY_NAME = 'timesafari_sqlite_key';
|
|
|
|
async storeKey(key: string): Promise<void> {
|
|
// Generate a master key for encrypting the SQLite key
|
|
const masterKey = await crypto.subtle.generateKey(
|
|
{ name: 'AES-GCM', length: 256 },
|
|
true,
|
|
['encrypt', 'decrypt']
|
|
);
|
|
|
|
// Encrypt the SQLite key
|
|
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
const encryptedKey = await crypto.subtle.encrypt(
|
|
{ name: 'AES-GCM', iv },
|
|
masterKey,
|
|
new TextEncoder().encode(key)
|
|
);
|
|
|
|
// Store encrypted key in IndexedDB
|
|
const db = await this.getSecureDB();
|
|
await db.put('keys', {
|
|
name: this.KEY_NAME,
|
|
iv,
|
|
data: encryptedKey
|
|
});
|
|
|
|
// Export and store master key in secure storage
|
|
const exportedKey = await crypto.subtle.exportKey('raw', masterKey);
|
|
await this.storeMasterKey(exportedKey);
|
|
}
|
|
|
|
private async getSecureDB(): Promise<IDBDatabase> {
|
|
// Implementation using IndexedDB with encryption
|
|
}
|
|
|
|
private async storeMasterKey(key: ArrayBuffer): Promise<void> {
|
|
// Store in secure storage (e.g., localStorage with encryption)
|
|
}
|
|
}
|
|
```
|
|
|
|
## Key Rotation and Backup Process
|
|
|
|
### Key Rotation
|
|
|
|
1. **Automatic Key Rotation**
|
|
```typescript
|
|
// src/services/storage/KeyRotationService.ts
|
|
export class KeyRotationService {
|
|
private static readonly ROTATION_INTERVAL = 30 * 24 * 60 * 60 * 1000; // 30 days
|
|
|
|
async checkAndRotateKey(): Promise<void> {
|
|
const lastRotation = await this.getLastRotationDate();
|
|
if (Date.now() - lastRotation > this.ROTATION_INTERVAL) {
|
|
await this.rotateKey();
|
|
}
|
|
}
|
|
|
|
private async rotateKey(): Promise<void> {
|
|
// 1. Generate new key
|
|
const newKey = await this.generateNewKey();
|
|
|
|
// 2. Re-encrypt database with new key
|
|
await this.reencryptDatabase(newKey);
|
|
|
|
// 3. Store new key securely
|
|
await this.storeNewKey(newKey);
|
|
|
|
// 4. Update rotation timestamp
|
|
await this.updateRotationDate();
|
|
}
|
|
|
|
private async reencryptDatabase(newKey: string): Promise<void> {
|
|
const sqliteService = SQLiteService.getInstance();
|
|
|
|
// Export all data
|
|
const data = await sqliteService.exportAllData();
|
|
|
|
// Create new database with new key
|
|
await sqliteService.initialize({
|
|
database: 'timesafari_new.db',
|
|
encrypted: true,
|
|
version: 1,
|
|
key: newKey
|
|
});
|
|
|
|
// Import data to new database
|
|
await sqliteService.importData(data);
|
|
|
|
// Verify data integrity
|
|
await this.verifyDataIntegrity();
|
|
|
|
// Replace old database with new one
|
|
await sqliteService.replaceDatabase('timesafari_new.db', 'timesafari.db');
|
|
}
|
|
}
|
|
```
|
|
|
|
2. **Manual Key Rotation**
|
|
```typescript
|
|
// src/services/storage/KeyRotationService.ts
|
|
export class KeyRotationService {
|
|
async manualRotateKey(): Promise<void> {
|
|
// Require user authentication
|
|
const biometrics = new BiometricService();
|
|
const authenticated = await biometrics.authenticate();
|
|
if (!authenticated) {
|
|
throw new Error('Authentication required for key rotation');
|
|
}
|
|
|
|
await this.rotateKey();
|
|
}
|
|
}
|
|
```
|
|
|
|
### Backup Process
|
|
|
|
1. **Secure Backup**
|
|
```typescript
|
|
// src/services/storage/BackupService.ts
|
|
export class BackupService {
|
|
async createBackup(): Promise<BackupResult> {
|
|
// 1. Export database
|
|
const data = await this.exportDatabase();
|
|
|
|
// 2. Export encryption keys
|
|
const keys = await this.exportKeys();
|
|
|
|
// 3. Create encrypted backup
|
|
const backup = await this.encryptBackup(data, keys);
|
|
|
|
// 4. Store backup securely
|
|
return this.storeBackup(backup);
|
|
}
|
|
|
|
private async exportDatabase(): Promise<DatabaseExport> {
|
|
const sqliteService = SQLiteService.getInstance();
|
|
return {
|
|
version: await sqliteService.getVersion(),
|
|
tables: await sqliteService.exportTables(),
|
|
metadata: await sqliteService.getMetadata()
|
|
};
|
|
}
|
|
|
|
private async exportKeys(): Promise<KeyExport> {
|
|
const keyManagement = KeyManagementService.getInstance();
|
|
return {
|
|
sqliteKey: await keyManagement.exportKey(),
|
|
backupKey: await this.generateBackupKey()
|
|
};
|
|
}
|
|
|
|
private async encryptBackup(
|
|
data: DatabaseExport,
|
|
keys: KeyExport
|
|
): Promise<EncryptedBackup> {
|
|
// Encrypt data with backup key
|
|
const encryptedData = await this.encryptData(data, keys.backupKey);
|
|
|
|
// Encrypt backup key with user's recovery key
|
|
const encryptedBackupKey = await this.encryptBackupKey(
|
|
keys.backupKey,
|
|
await this.getRecoveryKey()
|
|
);
|
|
|
|
return {
|
|
data: encryptedData,
|
|
backupKey: encryptedBackupKey,
|
|
timestamp: Date.now(),
|
|
version: '1.0'
|
|
};
|
|
}
|
|
}
|
|
```
|
|
|
|
2. **Restore Process**
|
|
```typescript
|
|
// src/services/storage/BackupService.ts
|
|
export class BackupService {
|
|
async restoreBackup(backup: EncryptedBackup): Promise<void> {
|
|
// 1. Verify backup integrity
|
|
await this.verifyBackup(backup);
|
|
|
|
// 2. Decrypt backup key
|
|
const backupKey = await this.decryptBackupKey(
|
|
backup.backupKey,
|
|
await this.getRecoveryKey()
|
|
);
|
|
|
|
// 3. Decrypt data
|
|
const data = await this.decryptData(backup.data, backupKey);
|
|
|
|
// 4. Restore database
|
|
await this.restoreDatabase(data);
|
|
|
|
// 5. Verify restoration
|
|
await this.verifyRestoration();
|
|
}
|
|
}
|
|
```
|
|
|
|
## Integration with Existing Security Model
|
|
|
|
1. **Account Security Integration**
|
|
```typescript
|
|
// src/services/security/AccountSecurityService.ts
|
|
export class AccountSecurityService {
|
|
private storageService: StorageService;
|
|
private keyManagement: KeyManagementService;
|
|
|
|
async initialize(): Promise<void> {
|
|
this.storageService = await StorageServiceFactory.create();
|
|
this.keyManagement = KeyManagementService.getInstance();
|
|
|
|
// Link SQLite encryption to account security
|
|
await this.linkAccountSecurity();
|
|
}
|
|
|
|
private async linkAccountSecurity(): Promise<void> {
|
|
const account = await this.storageService.getActiveAccount();
|
|
if (account) {
|
|
// Derive SQLite key from account credentials
|
|
const sqliteKey = await this.deriveSQLiteKey(account);
|
|
|
|
// Store derived key securely
|
|
await this.keyManagement.storeKey(sqliteKey, {
|
|
useBiometrics: true,
|
|
keySize: 256,
|
|
keyAlgorithm: 'AES-GCM'
|
|
});
|
|
}
|
|
}
|
|
|
|
private async deriveSQLiteKey(account: Account): Promise<string> {
|
|
// Use account credentials to derive SQLite key
|
|
const input = `${account.did}:${account.publicKeyHex}`;
|
|
return this.keyManagement.deriveKey(input);
|
|
}
|
|
}
|
|
```
|
|
|
|
2. **Biometric Integration**
|
|
```typescript
|
|
// src/services/security/BiometricSecurityService.ts
|
|
export class BiometricSecurityService {
|
|
async setupBiometricProtection(): Promise<void> {
|
|
const biometrics = new BiometricService();
|
|
const available = await biometrics.isAvailable();
|
|
|
|
if (available) {
|
|
// Enable biometric protection for SQLite
|
|
await this.keyManagement.updateKeyProtection({
|
|
useBiometrics: true,
|
|
requireAuthentication: true
|
|
});
|
|
|
|
// Update app settings
|
|
await this.storageService.updateSettings({
|
|
biometricProtection: true,
|
|
lastBiometricSetup: Date.now()
|
|
});
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
3. **Migration from Existing Security**
|
|
```typescript
|
|
// src/services/security/SecurityMigrationService.ts
|
|
export class SecurityMigrationService {
|
|
async migrateToNewSecurity(): Promise<void> {
|
|
// 1. Export existing secure data
|
|
const existingData = await this.exportExistingData();
|
|
|
|
// 2. Initialize new security model
|
|
await this.initializeNewSecurity();
|
|
|
|
// 3. Import data with new security
|
|
await this.importWithNewSecurity(existingData);
|
|
|
|
// 4. Verify migration
|
|
await this.verifySecurityMigration();
|
|
}
|
|
|
|
private async exportExistingData(): Promise<SecureData> {
|
|
// Export data from existing secure storage
|
|
const accounts = await this.exportAccounts();
|
|
const secrets = await this.exportSecrets();
|
|
const settings = await this.exportSettings();
|
|
|
|
return { accounts, secrets, settings };
|
|
}
|
|
|
|
private async initializeNewSecurity(): Promise<void> {
|
|
// Set up new security infrastructure
|
|
await this.keyManagement.initialize();
|
|
await this.storageService.initialize({
|
|
database: 'timesafari.db',
|
|
encrypted: true,
|
|
version: 1
|
|
});
|
|
}
|
|
}
|
|
```
|
|
|
|
### Detailed Platform Implementations
|
|
|
|
#### Web Platform (Dexie)
|
|
|
|
1. **Database Initialization**
|
|
```typescript
|
|
// src/services/platforms/WebPlatformService.ts
|
|
export class WebPlatformService implements PlatformService {
|
|
private secretDB: Dexie;
|
|
private accountsDB: Dexie | null = null;
|
|
|
|
constructor() {
|
|
// Initialize secret database
|
|
this.secretDB = new Dexie('TimeSafariSecret');
|
|
this.secretDB.version(1).stores({
|
|
secret: 'id, secret'
|
|
});
|
|
}
|
|
|
|
async openAccountsDatabase(): Promise<void> {
|
|
if (!this.accountsDB) {
|
|
this.accountsDB = new Dexie('TimeSafariAccounts');
|
|
this.accountsDB.version(1).stores({
|
|
accounts: '++id, dateCreated, derivationPath, did, $identity, $mnemonic, publicKeyHex',
|
|
settings: '++id, accountDid, *keys',
|
|
contacts: '++id, did, name, *keys'
|
|
});
|
|
|
|
// Apply encryption using master secret
|
|
const secret = await this.getMasterSecret();
|
|
if (secret) {
|
|
encrypted(this.accountsDB, { secretKey: secret.secret });
|
|
}
|
|
}
|
|
await this.accountsDB.open();
|
|
}
|
|
|
|
async getAccountByDid(did: string): Promise<Account | undefined> {
|
|
if (!this.accountsDB) {
|
|
throw new Error('Accounts database not initialized');
|
|
}
|
|
return await this.accountsDB.accounts
|
|
.where('did')
|
|
.equals(did)
|
|
.first();
|
|
}
|
|
|
|
// ... other methods ...
|
|
}
|
|
```
|
|
|
|
2. **Error Handling**
|
|
```typescript
|
|
// src/services/platforms/WebPlatformService.ts
|
|
export class WebPlatformService implements PlatformService {
|
|
private async handleDatabaseError(operation: string, error: unknown): Promise<never> {
|
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
logger.error(`Database error during ${operation}:`, error);
|
|
|
|
if (error instanceof DexieError) {
|
|
switch (error.name) {
|
|
case 'QuotaExceededError':
|
|
throw new Error('Storage quota exceeded. Please clear some space and try again.');
|
|
case 'InvalidTableError':
|
|
throw new Error('Database schema mismatch. Please try clearing your browser data.');
|
|
case 'ConstraintError':
|
|
throw new Error('Operation would violate database constraints.');
|
|
default:
|
|
throw new Error(`Database error: ${errorMessage}`);
|
|
}
|
|
}
|
|
|
|
throw new Error(`Failed to ${operation}: ${errorMessage}`);
|
|
}
|
|
|
|
async addAccount(account: Account): Promise<void> {
|
|
try {
|
|
if (!this.accountsDB) {
|
|
throw new Error('Accounts database not initialized');
|
|
}
|
|
await this.accountsDB.accounts.add(account);
|
|
} catch (error) {
|
|
await this.handleDatabaseError('add account', error);
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Capacitor Platform (SQLite)
|
|
|
|
1. **Database Initialization with Security**
|
|
```typescript
|
|
// src/services/platforms/CapacitorPlatformService.ts
|
|
export class CapacitorPlatformService implements PlatformService {
|
|
private sqliteService: SQLiteService;
|
|
private keyManagement: KeyManagementService;
|
|
private biometricService: BiometricService;
|
|
|
|
constructor() {
|
|
this.sqliteService = SQLiteService.getInstance();
|
|
this.keyManagement = KeyManagementService.getInstance();
|
|
this.biometricService = BiometricService.getInstance();
|
|
}
|
|
|
|
async openAccountsDatabase(): Promise<void> {
|
|
try {
|
|
// Check biometric authentication if required
|
|
if (await this.shouldRequireBiometrics()) {
|
|
const authenticated = await this.biometricService.authenticate();
|
|
if (!authenticated) {
|
|
throw new Error('Biometric authentication required');
|
|
}
|
|
}
|
|
|
|
// Get encryption key from secure storage
|
|
const key = await this.keyManagement.getEncryptionKey();
|
|
|
|
// Initialize SQLite with encryption
|
|
await this.sqliteService.initialize({
|
|
database: 'timesafari_accounts.db',
|
|
encrypted: true,
|
|
version: 1,
|
|
key
|
|
});
|
|
|
|
// Set up database schema
|
|
await this.setupDatabaseSchema();
|
|
} catch (error) {
|
|
await this.handleDatabaseError('open accounts database', error);
|
|
}
|
|
}
|
|
|
|
private async setupDatabaseSchema(): Promise<void> {
|
|
const schema = `
|
|
CREATE TABLE IF NOT EXISTS accounts (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
dateCreated TEXT NOT NULL,
|
|
derivationPath TEXT,
|
|
did TEXT UNIQUE NOT NULL,
|
|
identity TEXT,
|
|
mnemonic TEXT,
|
|
publicKeyHex TEXT NOT NULL
|
|
);
|
|
CREATE TABLE IF NOT EXISTS settings (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
accountDid TEXT NOT NULL,
|
|
key TEXT NOT NULL,
|
|
value TEXT,
|
|
FOREIGN KEY (accountDid) REFERENCES accounts(did)
|
|
);
|
|
CREATE TABLE IF NOT EXISTS contacts (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
did TEXT UNIQUE NOT NULL,
|
|
name TEXT,
|
|
keys TEXT
|
|
);
|
|
`;
|
|
await this.sqliteService.execute(schema);
|
|
}
|
|
|
|
// ... other methods ...
|
|
}
|
|
```
|
|
|
|
2. **Platform-Specific Security**
|
|
```typescript
|
|
// src/services/platforms/CapacitorPlatformService.ts
|
|
export class CapacitorPlatformService implements PlatformService {
|
|
private async shouldRequireBiometrics(): Promise<boolean> {
|
|
const capabilities = this.getCapabilities();
|
|
if (!capabilities.hasBiometrics) {
|
|
return false;
|
|
}
|
|
|
|
// Check user preferences
|
|
const settings = await this.getDefaultSettings();
|
|
return settings?.requireBiometrics ?? false;
|
|
}
|
|
|
|
private async getPlatformKey(): Promise<string> {
|
|
const capabilities = this.getCapabilities();
|
|
|
|
if (capabilities.isIOS) {
|
|
// Use iOS Keychain with Secure Enclave
|
|
return await this.keyManagement.getIOSKey({
|
|
useSecureEnclave: true,
|
|
requireBiometrics: await this.shouldRequireBiometrics()
|
|
});
|
|
} else if (capabilities.isAndroid) {
|
|
// Use Android Keystore
|
|
return await this.keyManagement.getAndroidKey({
|
|
useStrongBox: true,
|
|
requireBiometrics: await this.shouldRequireBiometrics()
|
|
});
|
|
}
|
|
|
|
throw new Error('Unsupported platform for secure key storage');
|
|
}
|
|
}
|
|
```
|
|
|
|
### Usage Examples
|
|
|
|
1. **Account Management**
|
|
```typescript
|
|
// src/services/AccountService.ts
|
|
export class AccountService {
|
|
constructor(private platformService: PlatformService) {}
|
|
|
|
async createNewAccount(mnemonic: string): Promise<Account> {
|
|
try {
|
|
// Generate account details
|
|
const [address, privateHex, publicHex, derivationPath] = deriveAddress(mnemonic);
|
|
const account: Account = {
|
|
dateCreated: new Date().toISOString(),
|
|
derivationPath,
|
|
did: `did:eth:${address}`,
|
|
identity: JSON.stringify({ address, privateHex, publicHex }),
|
|
mnemonic,
|
|
publicKeyHex: publicHex
|
|
};
|
|
|
|
// Save account
|
|
await this.platformService.addAccount(account);
|
|
|
|
// Set as active account
|
|
await this.platformService.updateSettings('activeDid', { value: account.did });
|
|
|
|
return account;
|
|
} catch (error) {
|
|
if (error instanceof Error) {
|
|
throw new Error(`Failed to create account: ${error.message}`);
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async getActiveAccount(): Promise<Account | undefined> {
|
|
const settings = await this.platformService.getDefaultSettings();
|
|
if (!settings?.activeDid) {
|
|
return undefined;
|
|
}
|
|
return await this.platformService.getAccountByDid(settings.activeDid);
|
|
}
|
|
}
|
|
```
|
|
|
|
2. **Settings Management**
|
|
```typescript
|
|
// src/services/SettingsService.ts
|
|
export class SettingsService {
|
|
constructor(private platformService: PlatformService) {}
|
|
|
|
async updateAccountSettings(did: string, changes: Partial<Settings>): Promise<void> {
|
|
try {
|
|
// Verify account exists
|
|
const account = await this.platformService.getAccountByDid(did);
|
|
if (!account) {
|
|
throw new Error(`Account ${did} not found`);
|
|
}
|
|
|
|
// Update settings
|
|
await this.platformService.updateAccountSettings(did, changes);
|
|
|
|
// Log changes for audit
|
|
await this.logSettingsChange(did, changes);
|
|
} catch (error) {
|
|
await this.handleSettingsError('update account settings', error);
|
|
}
|
|
}
|
|
|
|
private async logSettingsChange(did: string, changes: Partial<Settings>): Promise<void> {
|
|
// Implementation for audit logging
|
|
}
|
|
}
|
|
```
|
|
|
|
### Error Handling and Edge Cases
|
|
|
|
1. **Common Error Scenarios**
|
|
```typescript
|
|
// src/services/storage/errors/StorageError.ts
|
|
export class StorageError extends Error {
|
|
constructor(
|
|
message: string,
|
|
public readonly code: string,
|
|
public readonly originalError?: unknown
|
|
) {
|
|
super(message);
|
|
this.name = 'StorageError';
|
|
}
|
|
}
|
|
|
|
export const StorageErrorCodes = {
|
|
DATABASE_NOT_INITIALIZED: 'DB_NOT_INIT',
|
|
ENCRYPTION_FAILED: 'ENC_FAILED',
|
|
DECRYPTION_FAILED: 'DEC_FAILED',
|
|
QUOTA_EXCEEDED: 'QUOTA_EXCEEDED',
|
|
BIOMETRIC_REQUIRED: 'BIOMETRIC_REQUIRED',
|
|
MIGRATION_FAILED: 'MIGRATION_FAILED',
|
|
INVALID_DATA: 'INVALID_DATA'
|
|
} as const;
|
|
```
|
|
|
|
2. **Error Recovery Strategies**
|
|
```typescript
|
|
// src/services/storage/error-recovery/ErrorRecoveryService.ts
|
|
export class ErrorRecoveryService {
|
|
constructor(private platformService: PlatformService) {}
|
|
|
|
async handleStorageError(error: StorageError): Promise<void> {
|
|
switch (error.code) {
|
|
case StorageErrorCodes.DATABASE_NOT_INITIALIZED:
|
|
await this.reinitializeDatabase();
|
|
break;
|
|
|
|
case StorageErrorCodes.ENCRYPTION_FAILED:
|
|
case StorageErrorCodes.DECRYPTION_FAILED:
|
|
await this.handleEncryptionError();
|
|
break;
|
|
|
|
case StorageErrorCodes.QUOTA_EXCEEDED:
|
|
await this.handleQuotaExceeded();
|
|
break;
|
|
|
|
case StorageErrorCodes.BIOMETRIC_REQUIRED:
|
|
await this.handleBiometricRequired();
|
|
break;
|
|
|
|
case StorageErrorCodes.MIGRATION_FAILED:
|
|
await this.handleMigrationFailure();
|
|
break;
|
|
|
|
default:
|
|
throw error; // Re-throw unknown errors
|
|
}
|
|
}
|
|
|
|
private async reinitializeDatabase(): Promise<void> {
|
|
try {
|
|
// Attempt to reinitialize with backup
|
|
const backup = await this.platformService.exportDatabase();
|
|
await this.platformService.deleteDatabase();
|
|
await this.platformService.importDatabase(backup);
|
|
} catch (error) {
|
|
// If reinitialization fails, try clean initialization
|
|
await this.platformService.deleteDatabase();
|
|
await this.platformService.openAccountsDatabase();
|
|
}
|
|
}
|
|
|
|
private async handleEncryptionError(): Promise<void> {
|
|
// Attempt to recover encryption key
|
|
const newKey = await this.keyManagement.regenerateKey();
|
|
await this.platformService.setMasterSecret({
|
|
id: MASTER_SECRET_KEY,
|
|
secret: newKey
|
|
});
|
|
}
|
|
|
|
private async handleQuotaExceeded(): Promise<void> {
|
|
// Implement cleanup strategy
|
|
const accounts = await this.platformService.getAllAccounts();
|
|
const oldAccounts = accounts.filter(acc =>
|
|
new Date(acc.dateCreated).getTime() < Date.now() - 30 * 24 * 60 * 60 * 1000
|
|
);
|
|
|
|
for (const account of oldAccounts) {
|
|
await this.platformService.deleteAccount(account.did);
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
3. **Edge Cases and Mitigations**
|
|
|
|
a. **Concurrent Access**
|
|
```typescript
|
|
// src/services/storage/ConcurrencyManager.ts
|
|
export class ConcurrencyManager {
|
|
private locks: Map<string, Promise<void>> = new Map();
|
|
|
|
async withLock<T>(key: string, operation: () => Promise<T>): Promise<T> {
|
|
const existingLock = this.locks.get(key);
|
|
if (existingLock) {
|
|
await existingLock;
|
|
}
|
|
|
|
const lock = new Promise<void>((resolve) => {
|
|
this.locks.set(key, lock);
|
|
});
|
|
|
|
try {
|
|
return await operation();
|
|
} finally {
|
|
this.locks.delete(key);
|
|
lock.resolve();
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
b. **Data Integrity**
|
|
```typescript
|
|
// src/services/storage/DataIntegrityService.ts
|
|
export class DataIntegrityService {
|
|
async verifyDataIntegrity(): Promise<boolean> {
|
|
const accounts = await this.platformService.getAllAccounts();
|
|
|
|
for (const account of accounts) {
|
|
// Verify account data structure
|
|
if (!this.isValidAccount(account)) {
|
|
await this.repairAccount(account);
|
|
continue;
|
|
}
|
|
|
|
// Verify settings exist
|
|
const settings = await this.platformService.getAccountSettings(account.did);
|
|
if (!settings) {
|
|
await this.createDefaultSettings(account.did);
|
|
}
|
|
|
|
// Verify contacts
|
|
const contacts = await this.platformService.getAllContacts();
|
|
const invalidContacts = contacts.filter(c => !this.isValidContact(c));
|
|
for (const contact of invalidContacts) {
|
|
await this.repairContact(contact);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private isValidAccount(account: Account): boolean {
|
|
return (
|
|
account.did &&
|
|
account.publicKeyHex &&
|
|
account.dateCreated &&
|
|
(!account.identity || this.isValidJSON(account.identity))
|
|
);
|
|
}
|
|
|
|
private async repairAccount(account: Account): Promise<void> {
|
|
// Implementation for account repair
|
|
}
|
|
}
|
|
```
|
|
|
|
c. **Platform Transitions**
|
|
```typescript
|
|
// src/services/storage/PlatformTransitionService.ts
|
|
export class PlatformTransitionService {
|
|
async handlePlatformTransition(): Promise<void> {
|
|
const capabilities = this.platformService.getCapabilities();
|
|
|
|
if (capabilities.isIOS) {
|
|
await this.handleIOSTransition();
|
|
} else if (capabilities.isAndroid) {
|
|
await this.handleAndroidTransition();
|
|
}
|
|
}
|
|
|
|
private async handleIOSTransition(): Promise<void> {
|
|
// Handle iOS-specific transitions
|
|
// e.g., moving from Keychain to Secure Enclave
|
|
}
|
|
|
|
private async handleAndroidTransition(): Promise<void> {
|
|
// Handle Android-specific transitions
|
|
// e.g., upgrading to StrongBox
|
|
}
|
|
}
|
|
``` |