diff --git a/crypto_vault_extension/background.js b/crypto_vault_extension/background.js index 191cd1a..53679b7 100644 --- a/crypto_vault_extension/background.js +++ b/crypto_vault_extension/background.js @@ -5,6 +5,7 @@ let keepAliveInterval = null; let sessionTimeoutDuration = 15; // Default 15 seconds let sessionTimeoutId = null; // Background timer let popupPort = null; // Track popup connection +let sigSocketService = null; // SigSocket service instance // Utility function to convert Uint8Array to hex function toHex(uint8Array) { @@ -138,6 +139,9 @@ async function restoreSession() { // Import WASM module functions import init, * as wasmFunctions from './wasm/wasm_app.js'; +// Import SigSocket service +import SigSocketService from './background/sigsocket.js'; + // Initialize WASM module async function initVault() { try { @@ -151,6 +155,9 @@ async function initVault() { vault = wasmFunctions; isInitialized = true; + // Initialize SigSocket service + await initSigSocketService(); + // Try to restore previous session await restoreSession(); @@ -161,6 +168,17 @@ async function initVault() { } } +// Initialize SigSocket service +async function initSigSocketService() { + try { + sigSocketService = new SigSocketService(); + await sigSocketService.initialize(vault); + console.log('SigSocket service initialized'); + } catch (error) { + console.error('Failed to initialize SigSocket service:', error); + } +} + // Consolidated message handlers const messageHandlers = { @@ -172,6 +190,16 @@ const messageHandlers = { initSession: async (request) => { await vault.init_session(request.keyspace, request.password); await sessionManager.save(request.keyspace); + + // Auto-connect to SigSocket server when session is initialized (only if not already connected) + if (sigSocketService && !sigSocketService.isConnected) { + console.log(`Attempting to connect to SigSocket for keyspace: ${request.keyspace}`); + const connected = await sigSocketService.connectToServer(request.keyspace); + console.log(`SigSocket connection result: ${connected}`); + } else if (sigSocketService && sigSocketService.isConnected) { + console.log('SigSocket already connected, skipping connection attempt'); + } + return { success: true }; }, @@ -261,6 +289,52 @@ const messageHandlers = { await chrome.storage.local.set({ sessionTimeout: request.timeout }); resetSessionTimeout(); // Restart with new duration return { success: true }; + }, + + // SigSocket message handlers + connectSigSocket: async (request) => { + if (!sigSocketService) { + return { success: false, error: 'SigSocket service not available' }; + } + const connected = await sigSocketService.connectToServer(request.workspaceId); + return { success: connected }; + }, + + getPendingRequests: () => { + if (!sigSocketService) { + return { success: false, error: 'SigSocket service not available' }; + } + return { success: true, requests: sigSocketService.getPendingRequests() }; + }, + + keypaceUnlocked: async () => { + if (sigSocketService) { + await sigSocketService.onKeypaceUnlocked(); + } + return { success: true }; + }, + + approveSignRequest: async (request) => { + if (!sigSocketService) { + return { success: false, error: 'SigSocket service not available' }; + } + const approved = await sigSocketService.approveSignRequest(request.requestId); + return { success: approved }; + }, + + rejectSignRequest: async (request) => { + if (!sigSocketService) { + return { success: false, error: 'SigSocket service not available' }; + } + const rejected = await sigSocketService.rejectSignRequest(request.requestId, request.reason); + return { success: rejected }; + }, + + getSigSocketStatus: () => { + if (!sigSocketService) { + return { success: false, error: 'SigSocket service not available' }; + } + return { success: true, status: sigSocketService.getStatus() }; } }; @@ -302,6 +376,11 @@ chrome.runtime.onConnect.addListener((port) => { // Track popup connection popupPort = port; + // Set popup port in SigSocket service + if (sigSocketService) { + sigSocketService.setPopupPort(port); + } + // If we have an active session, ensure keep-alive is running if (currentSession) { startKeepAlive(); @@ -310,6 +389,9 @@ chrome.runtime.onConnect.addListener((port) => { port.onDisconnect.addListener(() => { // Popup closed, clear reference and stop keep-alive popupPort = null; + if (sigSocketService) { + sigSocketService.setPopupPort(null); + } stopKeepAlive(); }); } diff --git a/crypto_vault_extension/background/sigsocket.js b/crypto_vault_extension/background/sigsocket.js new file mode 100644 index 0000000..3f8d687 --- /dev/null +++ b/crypto_vault_extension/background/sigsocket.js @@ -0,0 +1,476 @@ +/** + * SigSocket Service for Browser Extension + * + * Handles SigSocket client functionality including: + * - Auto-connecting to SigSocket server when workspace is created + * - Managing pending sign requests + * - Handling user approval/rejection flow + * - Validating keyspace matches before showing approval UI + */ + +class SigSocketService { + constructor() { + this.connection = null; + this.pendingRequests = new Map(); // requestId -> SignRequestData + this.connectedPublicKey = null; + this.isConnected = false; + this.defaultServerUrl = "ws://localhost:8080/ws"; + + // Initialize WASM module reference + this.wasmModule = null; + + // Reference to popup port for communication + this.popupPort = null; + } + + /** + * Initialize the service with WASM module + * @param {Object} wasmModule - The loaded WASM module + */ + async initialize(wasmModule) { + this.wasmModule = wasmModule; + + // Load server URL from storage + try { + const result = await chrome.storage.local.get(['sigSocketUrl']); + if (result.sigSocketUrl) { + this.defaultServerUrl = result.sigSocketUrl; + } + } catch (error) { + console.warn('Failed to load SigSocket URL from storage:', error); + } + + // Set up global callbacks for WASM + globalThis.onSignRequestReceived = this.handleIncomingRequest.bind(this); + globalThis.onConnectionStateChanged = this.handleConnectionStateChange.bind(this); + } + + /** + * Connect to SigSocket server for a workspace + * @param {string} workspaceId - The workspace/keyspace identifier + * @returns {Promise} - True if connected successfully + */ + async connectToServer(workspaceId) { + try { + if (!this.wasmModule) { + throw new Error('WASM module not initialized'); + } + + // Check if already connected to this workspace + if (this.isConnected && this.connection) { + console.log(`Already connected to SigSocket server for workspace: ${workspaceId}`); + return true; + } + + // Disconnect any existing connection first + if (this.connection) { + this.disconnect(); + } + + // Get the workspace default public key + const publicKeyHex = await this.wasmModule.get_workspace_default_public_key(workspaceId); + if (!publicKeyHex) { + throw new Error('No public key found for workspace'); + } + + console.log(`Connecting to SigSocket server for workspace: ${workspaceId} with key: ${publicKeyHex.substring(0, 16)}...`); + + // Create new SigSocket connection + console.log('Creating new SigSocketConnection instance'); + this.connection = new this.wasmModule.SigSocketConnection(); + console.log('SigSocketConnection instance created'); + + // Connect to server + await this.connection.connect(this.defaultServerUrl, publicKeyHex); + + this.connectedPublicKey = publicKeyHex; + + // Clear pending requests if switching to a different workspace + if (this.currentWorkspace && this.currentWorkspace !== workspaceId) { + console.log(`Switching workspace from ${this.currentWorkspace} to ${workspaceId}, clearing pending requests`); + this.pendingRequests.clear(); + this.updateBadge(); + } + + this.currentWorkspace = workspaceId; + this.isConnected = true; + + console.log(`Successfully connected to SigSocket server for workspace: ${workspaceId}`); + return true; + + } catch (error) { + console.error('Failed to connect to SigSocket server:', error); + this.isConnected = false; + this.connectedPublicKey = null; + this.currentWorkspace = null; + if (this.connection) { + this.connection.disconnect(); + this.connection = null; + } + return false; + } + } + + /** + * Handle incoming sign request from server + * @param {string} requestId - Unique request identifier + * @param {string} messageBase64 - Message to be signed (base64-encoded) + */ + handleIncomingRequest(requestId, messageBase64) { + console.log(`Received sign request: ${requestId}`); + + // Security check: Only accept requests if we have an active connection + if (!this.isConnected || !this.connectedPublicKey || !this.currentWorkspace) { + console.warn(`Rejecting sign request ${requestId}: No active workspace connection`); + return; + } + + // Store the request with workspace info + const requestData = { + id: requestId, + message: messageBase64, + timestamp: Date.now(), + status: 'pending', + workspace: this.currentWorkspace, + connectedPublicKey: this.connectedPublicKey + }; + + this.pendingRequests.set(requestId, requestData); + + console.log(`Stored sign request for workspace: ${this.currentWorkspace}`); + + // Show notification to user + this.showSignRequestNotification(); + + // Update extension badge + this.updateBadge(); + + // Notify popup about new request if it's open and keyspace is unlocked + this.notifyPopupOfNewRequest(); + } + + /** + * Handle connection state changes + * @param {boolean} connected - True if connected, false if disconnected + */ + handleConnectionStateChange(connected) { + this.isConnected = connected; + console.log(`SigSocket connection state changed: ${connected ? 'connected' : 'disconnected'}`); + + if (!connected) { + this.connectedPublicKey = null; + this.currentWorkspace = null; + // Optionally attempt reconnection here + } + } + + /** + * Called when keyspace is unlocked - validate and show/hide approval UI + */ + async onKeypaceUnlocked() { + try { + if (!this.wasmModule) { + return; + } + + // Only check keyspace match if we have a connection + if (!this.isConnected || !this.connectedPublicKey) { + console.log('No SigSocket connection to validate against'); + return; + } + + // Get the currently unlocked workspace name + const unlockedWorkspaceName = this.wasmModule.get_current_keyspace_name(); + + // Get workspace default public key for the UNLOCKED workspace (not connected workspace) + const unlockedWorkspacePublicKey = await this.wasmModule.get_workspace_default_public_key(unlockedWorkspaceName); + + // Check if the unlocked workspace matches the connected workspace + const workspaceMatches = unlockedWorkspaceName === this.currentWorkspace; + const publicKeyMatches = unlockedWorkspacePublicKey === this.connectedPublicKey; + const keypaceMatches = workspaceMatches && publicKeyMatches; + + console.log(`Keyspace unlock validation:`); + console.log(` Connected workspace: ${this.currentWorkspace}`); + console.log(` Unlocked workspace: ${unlockedWorkspaceName}`); + console.log(` Connected public key: ${this.connectedPublicKey}`); + console.log(` Unlocked public key: ${unlockedWorkspacePublicKey}`); + console.log(` Workspace matches: ${workspaceMatches}`); + console.log(` Public key matches: ${publicKeyMatches}`); + console.log(` Overall match: ${keypaceMatches}`); + + // Always get current pending requests (filtered by connected workspace) + const currentPendingRequests = this.getPendingRequests(); + + // Notify popup about keyspace state + console.log(`Sending KEYSPACE_UNLOCKED message to popup: keypaceMatches=${keypaceMatches}, pendingRequests=${currentPendingRequests.length}`); + if (this.popupPort) { + this.popupPort.postMessage({ + type: 'KEYSPACE_UNLOCKED', + keypaceMatches, + pendingRequests: currentPendingRequests + }); + console.log('KEYSPACE_UNLOCKED message sent to popup'); + } else { + console.log('No popup port available to send KEYSPACE_UNLOCKED message'); + } + + } catch (error) { + if (error.message && error.message.includes('Workspace not found')) { + console.log(`Keyspace unlock: Different workspace unlocked (connected to: ${this.currentWorkspace})`); + + // Send message with no match and empty requests + if (this.popupPort) { + this.popupPort.postMessage({ + type: 'KEYSPACE_UNLOCKED', + keypaceMatches: false, + pendingRequests: [] + }); + } + } else { + console.error('Error handling keyspace unlock:', error); + } + } + } + + /** + * Approve a sign request + * @param {string} requestId - Request to approve + * @returns {Promise} - True if approved successfully + */ + async approveSignRequest(requestId) { + try { + const request = this.pendingRequests.get(requestId); + if (!request) { + throw new Error('Request not found'); + } + + // Validate request is for current workspace + if (request.workspace !== this.currentWorkspace) { + throw new Error(`Request is for workspace '${request.workspace}', but current workspace is '${this.currentWorkspace}'`); + } + + if (request.connectedPublicKey !== this.connectedPublicKey) { + throw new Error('Request public key does not match current connection'); + } + + // Validate keyspace is still unlocked and matches + if (!this.wasmModule.is_unlocked()) { + throw new Error('Keyspace is locked'); + } + + const currentPublicKey = await this.wasmModule.get_workspace_default_public_key(this.currentWorkspace); + if (currentPublicKey !== this.connectedPublicKey) { + throw new Error('Keyspace mismatch'); + } + + // Decode message from base64 + const messageBytes = atob(request.message).split('').map(c => c.charCodeAt(0)); + + // Sign the message with default keypair (doesn't require selected keypair) + const signatureHex = await this.wasmModule.sign_with_default_keypair(new Uint8Array(messageBytes)); + + // Send response to server + await this.connection.send_response(requestId, request.message, signatureHex); + + // Update request status + request.status = 'approved'; + request.signature = signatureHex; + + // Remove from pending requests + this.pendingRequests.delete(requestId); + + // Update badge + this.updateBadge(); + + console.log(`Approved sign request: ${requestId}`); + return true; + + } catch (error) { + console.error('Failed to approve sign request:', error); + return false; + } + } + + /** + * Reject a sign request + * @param {string} requestId - Request to reject + * @param {string} reason - Reason for rejection (optional) + * @returns {Promise} - True if rejected successfully + */ + async rejectSignRequest(requestId, reason = 'User rejected') { + try { + const request = this.pendingRequests.get(requestId); + if (!request) { + throw new Error('Request not found'); + } + + // Send rejection to server + await this.connection.send_rejection(requestId, reason); + + // Update request status + request.status = 'rejected'; + request.reason = reason; + + // Remove from pending requests + this.pendingRequests.delete(requestId); + + // Update badge + this.updateBadge(); + + console.log(`Rejected sign request: ${requestId}, reason: ${reason}`); + return true; + + } catch (error) { + console.error('Failed to reject sign request:', error); + return false; + } + } + + /** + * Get all pending requests for the current workspace + * @returns {Array} - Array of pending request data for current workspace + */ + getPendingRequests() { + const allRequests = Array.from(this.pendingRequests.values()); + + // Filter requests to only include those for the current workspace + const filteredRequests = allRequests.filter(request => { + const isCurrentWorkspace = request.workspace === this.currentWorkspace; + const isCurrentPublicKey = request.connectedPublicKey === this.connectedPublicKey; + + if (!isCurrentWorkspace || !isCurrentPublicKey) { + console.log(`Filtering out request ${request.id}: workspace=${request.workspace} (current=${this.currentWorkspace}), publicKey match=${isCurrentPublicKey}`); + } + + return isCurrentWorkspace && isCurrentPublicKey; + }); + + console.log(`getPendingRequests: ${allRequests.length} total, ${filteredRequests.length} for current workspace`); + return filteredRequests; + } + + /** + * Show notification for new sign request + */ + showSignRequestNotification() { + // Create notification + chrome.notifications.create({ + type: 'basic', + iconUrl: 'icons/icon48.png', + title: 'Sign Request', + message: 'New signature request received. Click to review.' + }); + } + + /** + * Notify popup about new request if popup is open and keyspace is unlocked + */ + async notifyPopupOfNewRequest() { + // Only notify if popup is connected + if (!this.popupPort) { + console.log('No popup port available, skipping new request notification'); + return; + } + + // Check if we have WASM module and can validate keyspace + if (!this.wasmModule) { + console.log('WASM module not available, skipping new request notification'); + return; + } + + try { + // Check if keyspace is unlocked + if (!this.wasmModule.is_unlocked()) { + console.log('Keyspace is locked, skipping new request notification'); + return; + } + + // Get the currently unlocked workspace name + const unlockedWorkspaceName = this.wasmModule.get_current_keyspace_name(); + + // Get workspace default public key for the UNLOCKED workspace + const unlockedWorkspacePublicKey = await this.wasmModule.get_workspace_default_public_key(unlockedWorkspaceName); + + // Check if the unlocked workspace matches the connected workspace + const workspaceMatches = unlockedWorkspaceName === this.currentWorkspace; + const publicKeyMatches = unlockedWorkspacePublicKey === this.connectedPublicKey; + const keypaceMatches = workspaceMatches && publicKeyMatches; + + console.log(`New request notification check: keypaceMatches=${keypaceMatches}, workspace=${unlockedWorkspaceName}, connected=${this.currentWorkspace}`); + + // Get current pending requests (filtered by connected workspace) + const currentPendingRequests = this.getPendingRequests(); + + // SECURITY: Only send requests if workspace matches, otherwise send empty array + const requestsToSend = keypaceMatches ? currentPendingRequests : []; + + // Send update to popup + this.popupPort.postMessage({ + type: 'NEW_SIGN_REQUEST', + keypaceMatches, + pendingRequests: requestsToSend + }); + + console.log(`Sent NEW_SIGN_REQUEST message to popup: keypaceMatches=${keypaceMatches}, ${requestsToSend.length} requests (${currentPendingRequests.length} total for connected workspace)`); + + } catch (error) { + console.log('Error in notifyPopupOfNewRequest:', error); + } + } + + /** + * Update extension badge with pending request count for current workspace + */ + updateBadge() { + // Only count requests for the current workspace + const currentWorkspaceRequests = this.getPendingRequests(); + const count = currentWorkspaceRequests.length; + const badgeText = count > 0 ? count.toString() : ''; + + console.log(`Updating badge: ${this.pendingRequests.size} total requests, ${count} for current workspace, badge text: "${badgeText}"`); + + chrome.action.setBadgeText({ text: badgeText }); + chrome.action.setBadgeBackgroundColor({ color: '#ff6b6b' }); + } + + /** + * Disconnect from SigSocket server + */ + disconnect() { + if (this.connection) { + this.connection.disconnect(); + this.connection = null; + } + + this.isConnected = false; + this.connectedPublicKey = null; + this.currentWorkspace = null; + this.pendingRequests.clear(); + this.updateBadge(); + } + + /** + * Get connection status + * @returns {Object} - Connection status information + */ + getStatus() { + return { + isConnected: this.isConnected, + connectedPublicKey: this.connectedPublicKey, + pendingRequestCount: this.getPendingRequests().length, + serverUrl: this.defaultServerUrl + }; + } + + /** + * Set the popup port for communication + * @param {chrome.runtime.Port} port - The popup port + */ + setPopupPort(port) { + this.popupPort = port; + } +} + +// Export for use in background script +export default SigSocketService; diff --git a/crypto_vault_extension/demo/README.md b/crypto_vault_extension/demo/README.md new file mode 100644 index 0000000..5877130 --- /dev/null +++ b/crypto_vault_extension/demo/README.md @@ -0,0 +1,75 @@ +# Mock SigSocket Server Demo + +This directory contains a mock SigSocket server for testing the browser extension functionality. + +## Setup + +1. Install dependencies: + ```bash + npm install + ``` + +2. Start the mock server: + ```bash + npm start + ``` + +The server will listen on `ws://localhost:8080/ws` + +## Usage + +### Interactive Commands + +Once the server is running, you can use these commands: + +- `test` - Send a test sign request to all connected clients +- `status` - Show server status and connected clients +- `quit` - Shutdown the server + +### Testing Flow + +1. Start the mock server +2. Load the browser extension in Chrome +3. Create a keyspace and keypair in the extension +4. The extension should automatically connect to the server +5. The server will send a test sign request after 3 seconds +6. Use the extension popup to approve or reject the request +7. The server will log the response and send another request after 10 seconds + +### Expected Output + +When a client connects: +``` +New WebSocket connection from: ::1 +Received message: 04a8b2c3d4e5f6... +Client registered: client_1234567890_abc123 with public key: 04a8b2c3d4e5f6... +šŸ“ Sending sign request to client_1234567890_abc123: req_1_1234567890 + Message: "Test message 1 - 2024-01-01T12:00:00.000Z" +``` + +When a sign response is received: +``` +Received sign response from client_1234567890_abc123: { + id: 'req_1_1234567890', + message: 'VGVzdCBtZXNzYWdlIDEgLSAyMDI0LTAxLTAxVDEyOjAwOjAwLjAwMFo=', + signature: '3045022100...' +} +āœ… Sign request req_1_1234567890 completed successfully + Signature: 3045022100... +``` + +## Protocol + +The mock server implements a simplified version of the SigSocket protocol: + +1. **Client Introduction**: Client sends hex-encoded public key +2. **Welcome Message**: Server responds with welcome JSON +3. **Sign Requests**: Server sends JSON with `id` and `message` (base64) +4. **Sign Responses**: Client sends JSON with `id`, `message`, and `signature` + +## Troubleshooting + +- **Connection refused**: Make sure the server is running on port 8080 +- **No sign requests**: Check that the extension is properly connected +- **Extension errors**: Check the browser console for JavaScript errors +- **WASM errors**: Ensure the WASM files are properly built and loaded diff --git a/crypto_vault_extension/demo/mock_sigsocket_server.js b/crypto_vault_extension/demo/mock_sigsocket_server.js new file mode 100644 index 0000000..4ecaf96 --- /dev/null +++ b/crypto_vault_extension/demo/mock_sigsocket_server.js @@ -0,0 +1,232 @@ +#!/usr/bin/env node + +/** + * Mock SigSocket Server for Testing Browser Extension + * + * This is a simple WebSocket server that simulates the SigSocket protocol + * for testing the browser extension functionality. + * + * Usage: + * node mock_sigsocket_server.js + * + * The server will listen on ws://localhost:8080/ws + */ + +const WebSocket = require('ws'); +const http = require('http'); + +class MockSigSocketServer { + constructor(port = 8080) { + this.port = port; + this.clients = new Map(); // clientId -> { ws, publicKey } + this.requestCounter = 0; + + this.setupServer(); + } + + setupServer() { + // Create HTTP server + this.httpServer = http.createServer(); + + // Create WebSocket server + this.wss = new WebSocket.Server({ + server: this.httpServer, + path: '/ws' + }); + + this.wss.on('connection', (ws, req) => { + console.log('New WebSocket connection from:', req.socket.remoteAddress); + this.handleConnection(ws); + }); + + this.httpServer.listen(this.port, () => { + console.log(`Mock SigSocket Server listening on ws://localhost:${this.port}/ws`); + console.log('Waiting for browser extension connections...'); + }); + } + + handleConnection(ws) { + let clientId = null; + let publicKey = null; + + ws.on('message', (data) => { + try { + const message = data.toString(); + console.log('Received message:', message); + + // Check if this is a client introduction (hex-encoded public key) + if (!clientId && this.isHexString(message)) { + publicKey = message; + clientId = this.generateClientId(); + + this.clients.set(clientId, { ws, publicKey }); + + console.log(`Client registered: ${clientId} with public key: ${publicKey.substring(0, 16)}...`); + + // Send welcome message + ws.send(JSON.stringify({ + type: 'welcome', + clientId: clientId, + message: 'Connected to Mock SigSocket Server' + })); + + // Schedule a test sign request after 3 seconds + setTimeout(() => { + this.sendTestSignRequest(clientId); + }, 3000); + + return; + } + + // Try to parse as JSON (sign response) + try { + const jsonMessage = JSON.parse(message); + this.handleSignResponse(clientId, jsonMessage); + } catch (e) { + console.log('Received non-JSON message:', message); + } + + } catch (error) { + console.error('Error handling message:', error); + } + }); + + ws.on('close', () => { + if (clientId) { + this.clients.delete(clientId); + console.log(`Client disconnected: ${clientId}`); + } + }); + + ws.on('error', (error) => { + console.error('WebSocket error:', error); + }); + } + + handleSignResponse(clientId, response) { + console.log(`Received sign response from ${clientId}:`, response); + + if (response.id && response.signature) { + console.log(`āœ… Sign request ${response.id} completed successfully`); + console.log(` Signature: ${response.signature.substring(0, 32)}...`); + + // Send another test request after 10 seconds + setTimeout(() => { + this.sendTestSignRequest(clientId); + }, 10000); + } else { + console.log('āŒ Invalid sign response format'); + } + } + + sendTestSignRequest(clientId) { + const client = this.clients.get(clientId); + if (!client) { + console.log(`Client ${clientId} not found`); + return; + } + + this.requestCounter++; + const requestId = `req_${this.requestCounter}_${Date.now()}`; + const testMessage = `Test message ${this.requestCounter} - ${new Date().toISOString()}`; + const messageBase64 = Buffer.from(testMessage).toString('base64'); + + const signRequest = { + id: requestId, + message: messageBase64 + }; + + console.log(`šŸ“ Sending sign request to ${clientId}:`, requestId); + console.log(` Message: "${testMessage}"`); + + try { + client.ws.send(JSON.stringify(signRequest)); + } catch (error) { + console.error(`Failed to send sign request to ${clientId}:`, error); + } + } + + isHexString(str) { + return /^[0-9a-fA-F]+$/.test(str) && str.length >= 32; // At least 16 bytes + } + + generateClientId() { + return `client_${Date.now()}_${Math.random().toString(36).substring(2, 8)}`; + } + + // Send a test request to all connected clients + broadcastTestRequest() { + console.log('\nšŸ“¢ Broadcasting test sign request to all clients...'); + for (const [clientId] of this.clients) { + this.sendTestSignRequest(clientId); + } + } + + // Get server status + getStatus() { + return { + port: this.port, + connectedClients: this.clients.size, + clients: Array.from(this.clients.keys()) + }; + } +} + +// Create and start the server +const server = new MockSigSocketServer(); + +// Handle graceful shutdown +process.on('SIGINT', () => { + console.log('\nšŸ›‘ Shutting down Mock SigSocket Server...'); + server.httpServer.close(() => { + console.log('Server closed'); + process.exit(0); + }); +}); + +// Add some interactive commands +process.stdin.setEncoding('utf8'); +console.log('\nšŸ“‹ Available commands:'); +console.log(' "test" - Send test sign request to all clients'); +console.log(' "status" - Show server status'); +console.log(' "quit" - Shutdown server'); +console.log(' Type a command and press Enter\n'); + +process.stdin.on('readable', () => { + const chunk = process.stdin.read(); + if (chunk !== null) { + const command = chunk.trim().toLowerCase(); + + switch (command) { + case 'test': + server.broadcastTestRequest(); + break; + + case 'status': + const status = server.getStatus(); + console.log('\nšŸ“Š Server Status:'); + console.log(` Port: ${status.port}`); + console.log(` Connected clients: ${status.connectedClients}`); + if (status.clients.length > 0) { + console.log(` Client IDs: ${status.clients.join(', ')}`); + } + console.log(''); + break; + + case 'quit': + case 'exit': + process.emit('SIGINT'); + break; + + case '': + break; + + default: + console.log(`Unknown command: ${command}`); + break; + } + } +}); + +// Export for testing +module.exports = MockSigSocketServer; diff --git a/crypto_vault_extension/demo/package.json b/crypto_vault_extension/demo/package.json new file mode 100644 index 0000000..c189c0e --- /dev/null +++ b/crypto_vault_extension/demo/package.json @@ -0,0 +1,21 @@ +{ + "name": "mock-sigsocket-server", + "version": "1.0.0", + "description": "Mock SigSocket server for testing browser extension", + "main": "mock_sigsocket_server.js", + "scripts": { + "start": "node mock_sigsocket_server.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "dependencies": { + "ws": "^8.14.0" + }, + "keywords": [ + "websocket", + "sigsocket", + "testing", + "mock" + ], + "author": "", + "license": "MIT" +} diff --git a/crypto_vault_extension/manifest.json b/crypto_vault_extension/manifest.json index 814bf08..cda239d 100644 --- a/crypto_vault_extension/manifest.json +++ b/crypto_vault_extension/manifest.json @@ -6,7 +6,8 @@ "permissions": [ "storage", - "activeTab" + "activeTab", + "notifications" ], "icons": { diff --git a/crypto_vault_extension/popup.html b/crypto_vault_extension/popup.html index 551e4ee..7906379 100644 --- a/crypto_vault_extension/popup.html +++ b/crypto_vault_extension/popup.html @@ -27,6 +27,10 @@ seconds +
+ + +
+ + +
+ + +
+ + `; + } + + /** + * Get a preview of the message content + * @param {string} messageBase64 - Base64 encoded message + * @returns {string} - Preview text + */ + getMessagePreview(messageBase64) { + try { + const decoded = atob(messageBase64); + const preview = decoded.length > 50 ? decoded.substring(0, 50) + '...' : decoded; + return preview; + } catch (error) { + return `Base64: ${messageBase64.substring(0, 20)}...`; + } + } + + /** + * Set up event listeners + */ + setupEventListeners() { + if (!this.container) return; + + // Use event delegation for dynamic content + this.container.addEventListener('click', (e) => { + const target = e.target; + + if (target.classList.contains('btn-approve')) { + const requestId = target.getAttribute('data-request-id'); + this.approveRequest(requestId); + } else if (target.classList.contains('btn-reject')) { + const requestId = target.getAttribute('data-request-id'); + this.rejectRequest(requestId); + } else if (target.classList.contains('expand-message')) { + const requestId = target.getAttribute('data-request-id'); + this.toggleMessageExpansion(requestId); + } + }); + } + + /** + * Set up listener for background script messages + */ + setupBackgroundListener() { + // Listen for keyspace unlock events + chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + if (message.type === 'KEYSPACE_UNLOCKED') { + this.isKeypaceUnlocked = true; + this.keypaceMatch = message.keypaceMatches; + this.pendingRequests = message.pendingRequests || []; + this.render(); + } + }); + } + + /** + * Approve a sign request + * @param {string} requestId - Request ID to approve + */ + async approveRequest(requestId) { + try { + const button = this.container.querySelector(`[data-request-id="${requestId}"].btn-approve`); + if (button) { + button.disabled = true; + button.textContent = 'Signing...'; + } + + const response = await this.sendMessage('approveSignRequest', { requestId }); + + if (response?.success) { + // Remove the request from UI + this.pendingRequests = this.pendingRequests.filter(r => r.id !== requestId); + this.render(); + + // Show success message + this.showToast('Sign request approved successfully!', 'success'); + } else { + throw new Error(response?.error || 'Failed to approve request'); + } + + } catch (error) { + console.error('Failed to approve request:', error); + this.showToast('Failed to approve request: ' + error.message, 'error'); + + // Re-enable button + const button = this.container.querySelector(`[data-request-id="${requestId}"].btn-approve`); + if (button) { + button.disabled = false; + button.textContent = 'āœ… Approve & Sign'; + } + } + } + + /** + * Reject a sign request + * @param {string} requestId - Request ID to reject + */ + async rejectRequest(requestId) { + try { + const button = this.container.querySelector(`[data-request-id="${requestId}"].btn-reject`); + if (button) { + button.disabled = true; + button.textContent = 'Rejecting...'; + } + + const response = await this.sendMessage('rejectSignRequest', { + requestId, + reason: 'User rejected' + }); + + if (response?.success) { + // Remove the request from UI + this.pendingRequests = this.pendingRequests.filter(r => r.id !== requestId); + this.render(); + + // Show success message + this.showToast('Sign request rejected', 'info'); + } else { + throw new Error(response?.error || 'Failed to reject request'); + } + + } catch (error) { + console.error('Failed to reject request:', error); + this.showToast('Failed to reject request: ' + error.message, 'error'); + + // Re-enable button + const button = this.container.querySelector(`[data-request-id="${requestId}"].btn-reject`); + if (button) { + button.disabled = false; + button.textContent = 'āŒ Reject'; + } + } + } + + /** + * Toggle message expansion + * @param {string} requestId - Request ID + */ + toggleMessageExpansion(requestId) { + const request = this.pendingRequests.find(r => r.id === requestId); + if (!request) return; + + const card = this.container.querySelector(`[data-request-id="${requestId}"]`); + const messageContent = card.querySelector('.message-content'); + const expandButton = card.querySelector('.expand-message'); + + const isExpanded = messageContent.classList.contains('expanded'); + + if (isExpanded) { + messageContent.classList.remove('expanded'); + messageContent.querySelector('.message-preview').textContent = this.getMessagePreview(request.message); + expandButton.querySelector('.expand-text').textContent = 'Show Full'; + } else { + messageContent.classList.add('expanded'); + try { + const fullMessage = atob(request.message); + messageContent.querySelector('.message-preview').textContent = fullMessage; + } catch (error) { + messageContent.querySelector('.message-preview').textContent = `Base64: ${request.message}`; + } + expandButton.querySelector('.expand-text').textContent = 'Show Less'; + } + } + + /** + * Send message to background script + * @param {string} action - Action to perform + * @param {Object} data - Additional data + * @returns {Promise} - Response from background script + */ + async sendMessage(action, data = {}) { + return new Promise((resolve) => { + chrome.runtime.sendMessage({ action, ...data }, resolve); + }); + } + + /** + * Show toast notification + * @param {string} message - Message to show + * @param {string} type - Toast type (success, error, info) + */ + showToast(message, type = 'info') { + // Use the existing toast system from popup.js + if (typeof showToast === 'function') { + showToast(message, type); + } else { + console.log(`[${type.toUpperCase()}] ${message}`); + } + } + + /** + * Update component state + * @param {Object} newState - New state data + */ + updateState(newState) { + console.log('SignRequestManager.updateState called with:', newState); + console.log('Current state before update:', { + isKeypaceUnlocked: this.isKeypaceUnlocked, + keypaceMatch: this.keypaceMatch, + pendingRequests: this.pendingRequests.length + }); + + Object.assign(this, newState); + + // Fix the property name mismatch + if (newState.keypaceMatches !== undefined) { + this.keypaceMatch = newState.keypaceMatches; + } + + console.log('State after update:', { + isKeypaceUnlocked: this.isKeypaceUnlocked, + keypaceMatch: this.keypaceMatch, + pendingRequests: this.pendingRequests.length + }); + + if (this.initialized) { + console.log('Rendering SignRequestManager with new state'); + this.render(); + } + } + + /** + * Refresh component data + */ + async refresh() { + await this.loadState(); + this.render(); + } +} + +// Export for use in popup +if (typeof module !== 'undefined' && module.exports) { + module.exports = SignRequestManager; +} else { + window.SignRequestManager = SignRequestManager; +} diff --git a/crypto_vault_extension/styles/popup.css b/crypto_vault_extension/styles/popup.css index 9f4f4fb..ace2e0d 100644 --- a/crypto_vault_extension/styles/popup.css +++ b/crypto_vault_extension/styles/popup.css @@ -1069,4 +1069,195 @@ input::placeholder, textarea::placeholder { .verification-icon svg { width: 20px; height: 20px; +} + +/* Sign Request Manager Styles */ +.sign-request-manager { + margin-top: 16px; + padding: 16px; + background: var(--bg-secondary); + border-radius: 8px; + border: 1px solid var(--border-color); +} + +.connection-status { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 12px; + font-size: 12px; + font-weight: 500; +} + +.status-indicator { + width: 8px; + height: 8px; + border-radius: 50%; + background: #ef4444; +} + +.connection-status.connected .status-indicator { + background: #10b981; +} + +.no-requests { + text-align: center; + padding: 20px; + color: var(--text-secondary); +} + +.unlock-prompt, .keyspace-mismatch { + text-align: center; + padding: 20px; +} + +.unlock-prompt h3, .keyspace-mismatch h3 { + margin: 0 0 12px 0; + font-size: 16px; + color: var(--text-primary); +} + +.unlock-prompt p, .keyspace-mismatch p { + margin: 8px 0; + color: var(--text-secondary); + font-size: 14px; +} + +.hint { + font-style: italic; + font-size: 12px !important; + color: var(--text-tertiary) !important; +} + +.requests-header { + margin-bottom: 12px; +} + +.requests-header h3 { + margin: 0; + font-size: 16px; + color: var(--text-primary); +} + +.requests-list { + display: flex; + flex-direction: column; + gap: 12px; +} + +.sign-request-card { + background: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: 6px; + padding: 12px; +} + +.request-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; +} + +.request-id { + font-family: monospace; + font-size: 12px; + color: var(--text-secondary); + font-weight: 500; +} + +.request-time { + font-size: 11px; + color: var(--text-tertiary); +} + +.request-message { + margin-bottom: 12px; +} + +.request-message label { + display: block; + font-size: 12px; + font-weight: 500; + color: var(--text-secondary); + margin-bottom: 4px; +} + +.message-content { + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 4px; + padding: 8px; + position: relative; +} + +.message-preview { + font-family: monospace; + font-size: 11px; + color: var(--text-primary); + word-break: break-all; + line-height: 1.4; + max-height: 60px; + overflow: hidden; +} + +.message-content.expanded .message-preview { + max-height: none; +} + +.expand-message { + position: absolute; + top: 4px; + right: 4px; + background: var(--accent-color); + color: white; + border: none; + border-radius: 3px; + padding: 2px 6px; + font-size: 10px; + cursor: pointer; + transition: background-color 0.2s; +} + +.expand-message:hover { + background: var(--accent-hover); +} + +.request-actions { + display: flex; + gap: 8px; + justify-content: flex-end; +} + +.btn-approve, .btn-reject { + padding: 6px 12px; + border: none; + border-radius: 4px; + font-size: 12px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s; +} + +.btn-approve { + background: #10b981; + color: white; +} + +.btn-approve:hover:not(:disabled) { + background: #059669; +} + +.btn-reject { + background: #ef4444; + color: white; +} + +.btn-reject:hover:not(:disabled) { + background: #dc2626; +} + +.btn-approve:disabled, .btn-reject:disabled { + opacity: 0.6; + cursor: not-allowed; } \ No newline at end of file diff --git a/crypto_vault_extension/test_extension.md b/crypto_vault_extension/test_extension.md new file mode 100644 index 0000000..2932655 --- /dev/null +++ b/crypto_vault_extension/test_extension.md @@ -0,0 +1,114 @@ +# Testing the SigSocket Browser Extension + +## Prerequisites + +1. **SigSocket Server**: You need a running SigSocket server at `ws://localhost:8080/ws` +2. **Browser**: Chrome or Chromium-based browser with developer mode enabled + +## Test Steps + +### 1. Load the Extension + +1. Open Chrome and go to `chrome://extensions/` +2. Enable "Developer mode" in the top right +3. Click "Load unpacked" and select the `crypto_vault_extension` directory +4. The CryptoVault extension should appear in your extensions list + +### 2. Basic Functionality Test + +1. Click the CryptoVault extension icon in the toolbar +2. Create a new keyspace: + - Enter a keyspace name (e.g., "test-workspace") + - Enter a password + - Click "Create New" +3. The extension should automatically connect to the SigSocket server +4. Add a keypair: + - Click "Add Keypair" + - Enter a name for the keypair + - Click "Create Keypair" + +### 3. SigSocket Integration Test + +1. **Check Connection Status**: + - Look for the SigSocket connection status at the bottom of the popup + - It should show "SigSocket: Connected" with a green indicator + +2. **Test Sign Request Flow**: + - Send a sign request to the SigSocket server (you'll need to implement this on the server side) + - The extension should show a notification + - The extension badge should show the number of pending requests + - Open the extension popup to see the sign request + +3. **Test Approval Flow**: + - If keyspace is locked, you should see "Unlock keyspace to see X pending requests" + - Unlock the keyspace using the login form + - You should see the sign request details + - Click "Approve & Sign" to approve the request + - The request should be signed and sent back to the server + +### 4. Settings Test + +1. Click the settings gear icon in the extension popup +2. Change the SigSocket server URL if needed +3. Adjust the session timeout if desired + +## Expected Behavior + +- āœ… Extension loads without errors +- āœ… Can create keyspaces and keypairs +- āœ… SigSocket connection is established automatically +- āœ… Sign requests are received and displayed +- āœ… Approval flow works correctly +- āœ… Settings can be configured + +## Troubleshooting + +### Common Issues + +1. **Extension won't load**: Check the console for JavaScript errors +2. **SigSocket won't connect**: Verify the server is running and the URL is correct +3. **WASM errors**: Check that the WASM files are properly built and copied +4. **Sign requests not appearing**: Check the browser console for callback errors + +### Debug Steps + +1. Open Chrome DevTools +2. Go to the Extensions tab +3. Find CryptoVault and click "Inspect views: background page" +4. Check the console for any errors +5. Also inspect the popup by right-clicking the extension icon and selecting "Inspect popup" + +## Server-Side Testing + +To fully test the extension, you'll need a SigSocket server that can: + +1. Accept WebSocket connections at `/ws` +2. Handle client introduction messages (hex-encoded public keys) +3. Send sign requests in the format: + + ```json + { + "id": "unique-request-id", + "message": "base64-encoded-message" + } + ``` + +4. Receive sign responses in the format: + + ```json + { + "id": "request-id", + "message": "base64-encoded-message", + "signature": "base64-encoded-signature" + } + ``` + +## Next Steps + +If basic functionality works: + +1. Test with multiple concurrent sign requests +2. Test connection recovery after network issues +3. Test with different keyspace configurations +4. Test the rejection flow +5. Test session timeout behavior diff --git a/crypto_vault_extension/wasm/wasm_app.js b/crypto_vault_extension/wasm/wasm_app.js index 57c87b8..cc23e70 100644 --- a/crypto_vault_extension/wasm/wasm_app.js +++ b/crypto_vault_extension/wasm/wasm_app.js @@ -202,6 +202,33 @@ function debugString(val) { // TODO we could test for more things here, like `Set`s and `Map`s. return className; } +/** + * Initialize the scripting environment (must be called before run_rhai) + */ +export function init_rhai_env() { + wasm.init_rhai_env(); +} + +function takeFromExternrefTable0(idx) { + const value = wasm.__wbindgen_export_2.get(idx); + wasm.__externref_table_dealloc(idx); + return value; +} +/** + * Securely run a Rhai script in the extension context (must be called only after user approval) + * @param {string} script + * @returns {any} + */ +export function run_rhai(script) { + const ptr0 = passStringToWasm0(script, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.run_rhai(ptr0, len0); + if (ret[2]) { + throw takeFromExternrefTable0(ret[1]); + } + return takeFromExternrefTable0(ret[0]); +} + /** * Create and unlock a new keyspace with the given name and password * @param {string} keyspace @@ -239,11 +266,6 @@ export function lock_session() { wasm.lock_session(); } -function takeFromExternrefTable0(idx) { - const value = wasm.__wbindgen_export_2.get(idx); - wasm.__externref_table_dealloc(idx); - return value; -} /** * Get metadata of the currently selected keypair * @returns {any} @@ -277,6 +299,42 @@ export function is_unlocked() { return ret !== 0; } +/** + * Get the default public key for a workspace (keyspace) + * This returns the public key of the first keypair in the keyspace + * @param {string} workspace_id + * @returns {Promise} + */ +export function get_workspace_default_public_key(workspace_id) { + const ptr0 = passStringToWasm0(workspace_id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.get_workspace_default_public_key(ptr0, len0); + return ret; +} + +/** + * Get the current unlocked public key as hex string + * @returns {string} + */ +export function get_current_unlocked_public_key() { + let deferred2_0; + let deferred2_1; + try { + const ret = wasm.get_current_unlocked_public_key(); + var ptr1 = ret[0]; + var len1 = ret[1]; + if (ret[3]) { + ptr1 = 0; len1 = 0; + throw takeFromExternrefTable0(ret[2]); + } + deferred2_0 = ptr1; + deferred2_1 = len1; + return getStringFromWasm0(ptr1, len1); + } finally { + wasm.__wbindgen_free(deferred2_0, deferred2_1, 1); + } +} + /** * Get all keypairs from the current session * Returns an array of keypair objects with id, type, and metadata @@ -323,7 +381,7 @@ function passArray8ToWasm0(arg, malloc) { return ptr; } /** - * Sign message with current session + * Sign message with current session (requires selected keypair) * @param {Uint8Array} message * @returns {Promise} */ @@ -334,6 +392,41 @@ export function sign(message) { return ret; } +/** + * Get the current keyspace name + * @returns {string} + */ +export function get_current_keyspace_name() { + let deferred2_0; + let deferred2_1; + try { + const ret = wasm.get_current_keyspace_name(); + var ptr1 = ret[0]; + var len1 = ret[1]; + if (ret[3]) { + ptr1 = 0; len1 = 0; + throw takeFromExternrefTable0(ret[2]); + } + deferred2_0 = ptr1; + deferred2_1 = len1; + return getStringFromWasm0(ptr1, len1); + } finally { + wasm.__wbindgen_free(deferred2_0, deferred2_1, 1); + } +} + +/** + * Sign message with default keypair (first keypair in keyspace) without changing session state + * @param {Uint8Array} message + * @returns {Promise} + */ +export function sign_with_default_keypair(message) { + const ptr0 = passArray8ToWasm0(message, wasm.__wbindgen_malloc); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.sign_with_default_keypair(ptr0, len0); + return ret; +} + /** * Verify a signature with the current session's selected keypair * @param {Uint8Array} message @@ -373,46 +466,162 @@ export function decrypt_data(encrypted) { return ret; } -/** - * Initialize the scripting environment (must be called before run_rhai) - */ -export function init_rhai_env() { - wasm.init_rhai_env(); +function __wbg_adapter_34(arg0, arg1, arg2) { + wasm.closure135_externref_shim(arg0, arg1, arg2); } -/** - * Securely run a Rhai script in the extension context (must be called only after user approval) - * @param {string} script - * @returns {any} - */ -export function run_rhai(script) { - const ptr0 = passStringToWasm0(script, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); - const len0 = WASM_VECTOR_LEN; - const ret = wasm.run_rhai(ptr0, len0); - if (ret[2]) { - throw takeFromExternrefTable0(ret[1]); - } - return takeFromExternrefTable0(ret[0]); +function __wbg_adapter_39(arg0, arg1) { + wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__ha4436a3f79fb1a0f(arg0, arg1); } -function __wbg_adapter_32(arg0, arg1, arg2) { - wasm.closure121_externref_shim(arg0, arg1, arg2); +function __wbg_adapter_44(arg0, arg1, arg2) { + wasm.closure199_externref_shim(arg0, arg1, arg2); } -function __wbg_adapter_35(arg0, arg1, arg2) { - wasm.closure150_externref_shim(arg0, arg1, arg2); +function __wbg_adapter_49(arg0, arg1) { + wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hf148c54a4a246cea(arg0, arg1); } -function __wbg_adapter_38(arg0, arg1, arg2) { - wasm.closure227_externref_shim(arg0, arg1, arg2); +function __wbg_adapter_52(arg0, arg1, arg2) { + wasm.closure264_externref_shim(arg0, arg1, arg2); } -function __wbg_adapter_138(arg0, arg1, arg2, arg3) { - wasm.closure1879_externref_shim(arg0, arg1, arg2, arg3); +function __wbg_adapter_55(arg0, arg1, arg2) { + wasm.closure349_externref_shim(arg0, arg1, arg2); } +function __wbg_adapter_195(arg0, arg1, arg2, arg3) { + wasm.closure2004_externref_shim(arg0, arg1, arg2, arg3); +} + +const __wbindgen_enum_BinaryType = ["blob", "arraybuffer"]; + const __wbindgen_enum_IdbTransactionMode = ["readonly", "readwrite", "versionchange", "readwriteflush", "cleanup"]; +const SigSocketConnectionFinalization = (typeof FinalizationRegistry === 'undefined') + ? { register: () => {}, unregister: () => {} } + : new FinalizationRegistry(ptr => wasm.__wbg_sigsocketconnection_free(ptr >>> 0, 1)); +/** + * WASM-bindgen wrapper for SigSocket client + * + * This provides a clean JavaScript API for the browser extension to: + * - Connect to SigSocket servers + * - Send responses to sign requests + * - Manage connection state + */ +export class SigSocketConnection { + + __destroy_into_raw() { + const ptr = this.__wbg_ptr; + this.__wbg_ptr = 0; + SigSocketConnectionFinalization.unregister(this); + return ptr; + } + + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_sigsocketconnection_free(ptr, 0); + } + /** + * Create a new SigSocket connection + */ + constructor() { + const ret = wasm.sigsocketconnection_new(); + this.__wbg_ptr = ret >>> 0; + SigSocketConnectionFinalization.register(this, this.__wbg_ptr, this); + return this; + } + /** + * Connect to a SigSocket server + * + * # Arguments + * * `server_url` - WebSocket server URL (e.g., "ws://localhost:8080/ws") + * * `public_key_hex` - Client's public key as hex string + * + * # Returns + * * `Ok(())` - Successfully connected + * * `Err(error)` - Connection failed + * @param {string} server_url + * @param {string} public_key_hex + * @returns {Promise} + */ + connect(server_url, public_key_hex) { + const ptr0 = passStringToWasm0(server_url, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ptr1 = passStringToWasm0(public_key_hex, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + const ret = wasm.sigsocketconnection_connect(this.__wbg_ptr, ptr0, len0, ptr1, len1); + return ret; + } + /** + * Send a response to a sign request + * + * This should be called by the extension after the user has approved + * a sign request and the message has been signed. + * + * # Arguments + * * `request_id` - ID of the original request + * * `message_base64` - Original message (base64-encoded) + * * `signature_hex` - Signature as hex string + * + * # Returns + * * `Ok(())` - Response sent successfully + * * `Err(error)` - Failed to send response + * @param {string} request_id + * @param {string} message_base64 + * @param {string} signature_hex + * @returns {Promise} + */ + send_response(request_id, message_base64, signature_hex) { + const ptr0 = passStringToWasm0(request_id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ptr1 = passStringToWasm0(message_base64, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + const ptr2 = passStringToWasm0(signature_hex, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len2 = WASM_VECTOR_LEN; + const ret = wasm.sigsocketconnection_send_response(this.__wbg_ptr, ptr0, len0, ptr1, len1, ptr2, len2); + return ret; + } + /** + * Send a rejection for a sign request + * + * This should be called when the user rejects a sign request. + * + * # Arguments + * * `request_id` - ID of the request to reject + * * `reason` - Reason for rejection (optional) + * + * # Returns + * * `Ok(())` - Rejection sent successfully + * * `Err(error)` - Failed to send rejection + * @param {string} request_id + * @param {string} reason + * @returns {Promise} + */ + send_rejection(request_id, reason) { + const ptr0 = passStringToWasm0(request_id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ptr1 = passStringToWasm0(reason, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + const ret = wasm.sigsocketconnection_send_rejection(this.__wbg_ptr, ptr0, len0, ptr1, len1); + return ret; + } + /** + * Disconnect from the SigSocket server + */ + disconnect() { + wasm.sigsocketconnection_disconnect(this.__wbg_ptr); + } + /** + * Check if connected to the server + * @returns {boolean} + */ + is_connected() { + const ret = wasm.sigsocketconnection_is_connected(this.__wbg_ptr); + return ret !== 0; + } +} + async function __wbg_load(module, imports) { if (typeof Response === 'function' && module instanceof Response) { if (typeof WebAssembly.instantiateStreaming === 'function') { @@ -467,6 +676,10 @@ function __wbg_get_imports() { const ret = arg0.crypto; return ret; }; + imports.wbg.__wbg_data_432d9c3df2630942 = function(arg0) { + const ret = arg0.data; + return ret; + }; imports.wbg.__wbg_error_524f506f44df1645 = function(arg0) { console.error(arg0); }; @@ -539,10 +752,23 @@ function __wbg_get_imports() { const ret = result; return ret; }; + imports.wbg.__wbg_instanceof_Window_def73ea0955fc569 = function(arg0) { + let result; + try { + result = arg0 instanceof Window; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }; imports.wbg.__wbg_length_52b6c4580c5ec934 = function(arg0) { const ret = arg0.length; return ret; }; + imports.wbg.__wbg_log_c222819a41e063d3 = function(arg0) { + console.log(arg0); + }; imports.wbg.__wbg_msCrypto_a61aeb35a24c1329 = function(arg0) { const ret = arg0.msCrypto; return ret; @@ -558,7 +784,7 @@ function __wbg_get_imports() { const a = state0.a; state0.a = 0; try { - return __wbg_adapter_138(a, state0.b, arg0, arg1); + return __wbg_adapter_195(a, state0.b, arg0, arg1); } finally { state0.a = a; } @@ -577,6 +803,10 @@ function __wbg_get_imports() { const ret = new Array(); return ret; }; + imports.wbg.__wbg_new_92c54fc74574ef55 = function() { return handleError(function (arg0, arg1) { + const ret = new WebSocket(getStringFromWasm0(arg0, arg1)); + return ret; + }, arguments) }; imports.wbg.__wbg_new_a12002a7f91c75be = function(arg0) { const ret = new Uint8Array(arg0); return ret; @@ -609,6 +839,12 @@ function __wbg_get_imports() { const ret = arg0.objectStore(getStringFromWasm0(arg1, arg2)); return ret; }, arguments) }; + imports.wbg.__wbg_onConnectionStateChanged_b0dc098522afadba = function(arg0) { + onConnectionStateChanged(arg0 !== 0); + }; + imports.wbg.__wbg_onSignRequestReceived_93232ba7a0919705 = function(arg0, arg1, arg2, arg3) { + onSignRequestReceived(getStringFromWasm0(arg0, arg1), getStringFromWasm0(arg2, arg3)); + }; imports.wbg.__wbg_open_88b1390d99a7c691 = function() { return handleError(function (arg0, arg1, arg2) { const ret = arg0.open(getStringFromWasm0(arg1, arg2)); return ret; @@ -643,6 +879,10 @@ function __wbg_get_imports() { imports.wbg.__wbg_randomFillSync_ac0988aba3254290 = function() { return handleError(function (arg0, arg1) { arg0.randomFillSync(arg1); }, arguments) }; + imports.wbg.__wbg_readyState_7ef6e63c349899ed = function(arg0) { + const ret = arg0.readyState; + return ret; + }; imports.wbg.__wbg_require_60cc747a6bc5215a = function() { return handleError(function () { const ret = module.require; return ret; @@ -655,12 +895,34 @@ function __wbg_get_imports() { const ret = arg0.result; return ret; }, arguments) }; + imports.wbg.__wbg_send_0293179ba074ffb4 = function() { return handleError(function (arg0, arg1, arg2) { + arg0.send(getStringFromWasm0(arg1, arg2)); + }, arguments) }; + imports.wbg.__wbg_setTimeout_f2fe5af8e3debeb3 = function() { return handleError(function (arg0, arg1, arg2) { + const ret = arg0.setTimeout(arg1, arg2); + return ret; + }, arguments) }; imports.wbg.__wbg_set_65595bdd868b3009 = function(arg0, arg1, arg2) { arg0.set(arg1, arg2 >>> 0); }; + imports.wbg.__wbg_setbinaryType_92fa1ffd873b327c = function(arg0, arg1) { + arg0.binaryType = __wbindgen_enum_BinaryType[arg1]; + }; + imports.wbg.__wbg_setonclose_14fc475a49d488fc = function(arg0, arg1) { + arg0.onclose = arg1; + }; + imports.wbg.__wbg_setonerror_8639efe354b947cd = function(arg0, arg1) { + arg0.onerror = arg1; + }; imports.wbg.__wbg_setonerror_d7e3056cc6e56085 = function(arg0, arg1) { arg0.onerror = arg1; }; + imports.wbg.__wbg_setonmessage_6eccab530a8fb4c7 = function(arg0, arg1) { + arg0.onmessage = arg1; + }; + imports.wbg.__wbg_setonopen_2da654e1f39745d5 = function(arg0, arg1) { + arg0.onopen = arg1; + }; imports.wbg.__wbg_setonsuccess_afa464ee777a396d = function(arg0, arg1) { arg0.onsuccess = arg1; }; @@ -695,6 +957,10 @@ function __wbg_get_imports() { const ret = arg0.then(arg1); return ret; }; + imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) { + const ret = arg0.then(arg1, arg2); + return ret; + }; imports.wbg.__wbg_transaction_d6d07c3c9963c49e = function() { return handleError(function (arg0, arg1, arg2) { const ret = arg0.transaction(arg1, __wbindgen_enum_IdbTransactionMode[arg2]); return ret; @@ -703,6 +969,9 @@ function __wbg_get_imports() { const ret = arg0.versions; return ret; }; + imports.wbg.__wbg_warn_4ca3906c248c47c4 = function(arg0) { + console.warn(arg0); + }; imports.wbg.__wbindgen_cb_drop = function(arg0) { const obj = arg0.original; if (obj.cnt-- == 1) { @@ -712,16 +981,40 @@ function __wbg_get_imports() { const ret = false; return ret; }; - imports.wbg.__wbindgen_closure_wrapper378 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 122, __wbg_adapter_32); + imports.wbg.__wbindgen_closure_wrapper1181 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 350, __wbg_adapter_55); return ret; }; - imports.wbg.__wbindgen_closure_wrapper549 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 151, __wbg_adapter_35); + imports.wbg.__wbindgen_closure_wrapper335 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 133, __wbg_adapter_34); return ret; }; - imports.wbg.__wbindgen_closure_wrapper857 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 228, __wbg_adapter_38); + imports.wbg.__wbindgen_closure_wrapper336 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 133, __wbg_adapter_34); + return ret; + }; + imports.wbg.__wbindgen_closure_wrapper337 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 133, __wbg_adapter_39); + return ret; + }; + imports.wbg.__wbindgen_closure_wrapper340 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 133, __wbg_adapter_34); + return ret; + }; + imports.wbg.__wbindgen_closure_wrapper657 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 200, __wbg_adapter_44); + return ret; + }; + imports.wbg.__wbindgen_closure_wrapper658 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 200, __wbg_adapter_44); + return ret; + }; + imports.wbg.__wbindgen_closure_wrapper661 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 200, __wbg_adapter_49); + return ret; + }; + imports.wbg.__wbindgen_closure_wrapper876 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 265, __wbg_adapter_52); return ret; }; imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { @@ -778,6 +1071,14 @@ function __wbg_get_imports() { const ret = wasm.memory; return ret; }; + imports.wbg.__wbindgen_string_get = function(arg0, arg1) { + const obj = arg1; + const ret = typeof(obj) === 'string' ? obj : undefined; + var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }; imports.wbg.__wbindgen_string_new = function(arg0, arg1) { const ret = getStringFromWasm0(arg0, arg1); return ret; diff --git a/crypto_vault_extension/wasm/wasm_app_bg.wasm b/crypto_vault_extension/wasm/wasm_app_bg.wasm index 6ededfd..331168a 100644 Binary files a/crypto_vault_extension/wasm/wasm_app_bg.wasm and b/crypto_vault_extension/wasm/wasm_app_bg.wasm differ