// Import our WebAssembly module import init, { create_key_space, encrypt_key_space, decrypt_key_space, logout, create_keypair, select_keypair, list_keypairs, keypair_pub_key, keypair_sign, keypair_verify, derive_public_key, verify_with_public_key, encrypt_asymmetric, decrypt_asymmetric, generate_symmetric_key, derive_key_from_password, encrypt_symmetric, decrypt_symmetric, encrypt_with_password, decrypt_with_password } from '../../pkg/webassembly.js'; // Helper function to convert ArrayBuffer to hex string function bufferToHex(buffer) { return Array.from(new Uint8Array(buffer)) .map(b => b.toString(16).padStart(2, '0')) .join(''); } // Helper function to convert hex string to Uint8Array function hexToBuffer(hex) { const bytes = new Uint8Array(hex.length / 2); for (let i = 0; i < hex.length; i += 2) { bytes[i / 2] = parseInt(hex.substr(i, 2), 16); } return bytes; } // Session management let lastActivity = Date.now(); let logoutTimer = null; const AUTO_LOGOUT_TIME = 15 * 60 * 1000; // 15 minutes // Update last activity timestamp function updateActivity() { lastActivity = Date.now(); } // Check for inactivity and logout if needed function checkInactivity() { const inactiveTime = Date.now() - lastActivity; if (inactiveTime > AUTO_LOGOUT_TIME) { performLogout(); alert('You have been logged out due to inactivity.'); } } // Setup auto-logout timer function setupAutoLogout() { logoutTimer = setInterval(checkInactivity, 60000); // Check every minute } // Clear auto-logout timer function clearAutoLogout() { if (logoutTimer) { clearInterval(logoutTimer); logoutTimer = null; } } // LocalStorage functions for key spaces const STORAGE_PREFIX = 'crypto_space_'; // Save encrypted space to localStorage function saveSpaceToStorage(spaceName, encryptedData) { localStorage.setItem(`${STORAGE_PREFIX}${spaceName}`, encryptedData); } // Get encrypted space from localStorage function getSpaceFromStorage(spaceName) { return localStorage.getItem(`${STORAGE_PREFIX}${spaceName}`); } // List all spaces in localStorage function listSpacesFromStorage() { const spaces = []; for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); if (key.startsWith(STORAGE_PREFIX)) { spaces.push(key.substring(STORAGE_PREFIX.length)); } } return spaces; } // Remove space from localStorage function removeSpaceFromStorage(spaceName) { localStorage.removeItem(`${STORAGE_PREFIX}${spaceName}`); } // Session state let isLoggedIn = false; let currentSpace = null; let selectedKeypair = null; // Update UI based on login state function updateLoginUI() { const loginForm = document.getElementById('login-form'); const logoutForm = document.getElementById('logout-form'); const loginStatus = document.getElementById('login-status'); const currentSpaceName = document.getElementById('current-space-name'); if (isLoggedIn) { loginForm.classList.add('hidden'); logoutForm.classList.remove('hidden'); loginStatus.textContent = 'Status: Logged in'; loginStatus.className = 'status logged-in'; currentSpaceName.textContent = currentSpace; } else { loginForm.classList.remove('hidden'); logoutForm.classList.add('hidden'); loginStatus.textContent = 'Status: Not logged in'; loginStatus.className = 'status logged-out'; currentSpaceName.textContent = ''; } // Update the spaces list updateSpacesList(); } // Update the spaces dropdown list function updateSpacesList() { const spacesList = document.getElementById('space-list'); // Clear existing options while (spacesList.options.length > 1) { spacesList.remove(1); } // Get spaces list const spaces = listSpacesFromStorage(); // Add options for each space spaces.forEach(spaceName => { const option = document.createElement('option'); option.value = spaceName; option.textContent = spaceName; spacesList.appendChild(option); }); } // Login to a space async function performLogin() { const spaceName = document.getElementById('space-name').value.trim(); const password = document.getElementById('space-password').value; if (!spaceName || !password) { document.getElementById('space-result').textContent = 'Please enter both space name and password'; return; } try { // Get encrypted space from localStorage const encryptedSpace = getSpaceFromStorage(spaceName); if (!encryptedSpace) { document.getElementById('space-result').textContent = `Space "${spaceName}" not found`; return; } // Decrypt the space const result = decrypt_key_space(encryptedSpace, password); if (result === 0) { isLoggedIn = true; currentSpace = spaceName; updateLoginUI(); updateKeypairsList(); document.getElementById('space-result').textContent = `Successfully logged in to space "${spaceName}"`; // Setup auto-logout updateActivity(); setupAutoLogout(); // Add activity listeners document.addEventListener('click', updateActivity); document.addEventListener('keypress', updateActivity); } else { document.getElementById('space-result').textContent = `Error logging in: ${result}`; } } catch (e) { document.getElementById('space-result').textContent = `Error: ${e}`; } } // Create a new space async function performCreateSpace() { const spaceName = document.getElementById('space-name').value.trim(); const password = document.getElementById('space-password').value; if (!spaceName || !password) { document.getElementById('space-result').textContent = 'Please enter both space name and password'; return; } // Check if space already exists if (getSpaceFromStorage(spaceName)) { document.getElementById('space-result').textContent = `Space "${spaceName}" already exists`; return; } try { // Create new space const result = create_key_space(spaceName); if (result === 0) { // Encrypt and save the space const encryptedSpace = encrypt_key_space(password); saveSpaceToStorage(spaceName, encryptedSpace); isLoggedIn = true; currentSpace = spaceName; updateLoginUI(); updateKeypairsList(); document.getElementById('space-result').textContent = `Successfully created space "${spaceName}"`; // Setup auto-logout updateActivity(); setupAutoLogout(); // Add activity listeners document.addEventListener('click', updateActivity); document.addEventListener('keypress', updateActivity); } else { document.getElementById('space-result').textContent = `Error creating space: ${result}`; } } catch (e) { document.getElementById('space-result').textContent = `Error: ${e}`; } } // Logout from current space function performLogout() { logout(); isLoggedIn = false; currentSpace = null; selectedKeypair = null; updateLoginUI(); clearKeypairsList(); document.getElementById('space-result').textContent = 'Logged out successfully'; // Clear auto-logout clearAutoLogout(); // Remove activity listeners document.removeEventListener('click', updateActivity); document.removeEventListener('keypress', updateActivity); } // Update the keypairs dropdown list function updateKeypairsList() { const selectKeypair = document.getElementById('select-keypair'); // Clear existing options while (selectKeypair.options.length > 1) { selectKeypair.remove(1); } try { // Get keypairs list const keypairs = list_keypairs(); // Add options for each keypair keypairs.forEach(keypairName => { const option = document.createElement('option'); option.value = keypairName; option.textContent = keypairName; selectKeypair.appendChild(option); }); // If there's a selected keypair, select it in the dropdown if (selectedKeypair) { selectKeypair.value = selectedKeypair; } } catch (e) { console.error('Error updating keypairs list:', e); } } // Clear the keypairs dropdown list function clearKeypairsList() { const selectKeypair = document.getElementById('select-keypair'); // Clear existing options while (selectKeypair.options.length > 1) { selectKeypair.remove(1); } // Clear selected keypair display document.getElementById('selected-pubkey-display').textContent = ''; } // Create a new keypair async function performCreateKeypair() { if (!isLoggedIn) { document.getElementById('keypair-management-result').textContent = 'Please login first'; return; } const keypairName = document.getElementById('keypair-name').value.trim(); if (!keypairName) { document.getElementById('keypair-management-result').textContent = 'Please enter a keypair name'; return; } try { // Create new keypair const result = create_keypair(keypairName); if (result === 0) { document.getElementById('keypair-management-result').textContent = `Successfully created keypair "${keypairName}"`; // Update keypairs list updateKeypairsList(); // Select the new keypair selectedKeypair = keypairName; document.getElementById('select-keypair').value = keypairName; // Display public key displaySelectedKeypairPublicKey(); // Save the updated space to localStorage saveCurrentSpace(); } else { document.getElementById('keypair-management-result').textContent = `Error creating keypair: ${result}`; } } catch (e) { document.getElementById('keypair-management-result').textContent = `Error: ${e}`; } } // Select a keypair async function performSelectKeypair() { if (!isLoggedIn) { document.getElementById('keypair-management-result').textContent = 'Please login first'; return; } const keypairName = document.getElementById('select-keypair').value; if (!keypairName) { document.getElementById('keypair-management-result').textContent = 'Please select a keypair'; return; } try { // Select keypair const result = select_keypair(keypairName); if (result === 0) { selectedKeypair = keypairName; document.getElementById('keypair-management-result').textContent = `Selected keypair "${keypairName}"`; // Display public key displaySelectedKeypairPublicKey(); } else { document.getElementById('keypair-management-result').textContent = `Error selecting keypair: ${result}`; } } catch (e) { document.getElementById('keypair-management-result').textContent = `Error: ${e}`; } } // Display the public key of the selected keypair function displaySelectedKeypairPublicKey() { try { const pubKey = keypair_pub_key(); const pubKeyHex = bufferToHex(pubKey); // Create a more user-friendly display with copy button const pubKeyDisplay = document.getElementById('selected-pubkey-display'); pubKeyDisplay.innerHTML = `
Public Key (hex):
${pubKeyHex}
`; // Add event listener for the copy button document.getElementById('copy-pubkey-button').addEventListener('click', () => { const pubKeyText = document.getElementById('pubkey-hex-value').textContent; navigator.clipboard.writeText(pubKeyText) .then(() => { alert('Public key copied to clipboard!'); }) .catch(err => { console.error('Could not copy text: ', err); }); }); // Also populate the public key field in the verify with public key section document.getElementById('pubkey-verify-pubkey').value = pubKeyHex; // And in the asymmetric encryption section document.getElementById('asymmetric-encrypt-pubkey').value = pubKeyHex; } catch (e) { document.getElementById('selected-pubkey-display').textContent = `Error getting public key: ${e}`; } } // Save the current space to localStorage function saveCurrentSpace() { if (!isLoggedIn || !currentSpace) return; try { // Store the password in a session variable when logging in // and use it here to avoid issues when the password field is cleared const password = document.getElementById('space-password').value; if (!password) { console.error('Password not available for saving space'); alert('Please re-enter your password to save changes'); return; } const encryptedSpace = encrypt_key_space(password); saveSpaceToStorage(currentSpace, encryptedSpace); } catch (e) { console.error('Error saving space:', e); } } // Delete a space from localStorage function deleteSpace(spaceName) { if (!spaceName) return false; // Check if space exists if (!getSpaceFromStorage(spaceName)) { return false; } // Remove from localStorage removeSpaceFromStorage(spaceName); // If this was the current space, logout if (isLoggedIn && currentSpace === spaceName) { performLogout(); } return true; } async function run() { // Initialize the WebAssembly module await init(); console.log('WebAssembly crypto module initialized!'); // Set up the login/space management document.getElementById('login-button').addEventListener('click', performLogin); document.getElementById('create-space-button').addEventListener('click', performCreateSpace); document.getElementById('logout-button').addEventListener('click', performLogout); document.getElementById('delete-space-button').addEventListener('click', () => { if (confirm(`Are you sure you want to delete the space "${currentSpace}"? This action cannot be undone.`)) { if (deleteSpace(currentSpace)) { document.getElementById('space-result').textContent = `Space "${currentSpace}" deleted successfully`; } else { document.getElementById('space-result').textContent = `Error deleting space "${currentSpace}"`; } } }); document.getElementById('delete-selected-space-button').addEventListener('click', () => { const selectedSpace = document.getElementById('space-list').value; if (!selectedSpace) { document.getElementById('space-result').textContent = 'Please select a space to delete'; return; } if (confirm(`Are you sure you want to delete the space "${selectedSpace}"? This action cannot be undone.`)) { if (deleteSpace(selectedSpace)) { document.getElementById('space-result').textContent = `Space "${selectedSpace}" deleted successfully`; updateSpacesList(); } else { document.getElementById('space-result').textContent = `Error deleting space "${selectedSpace}"`; } } }); // Set up the keypair management document.getElementById('create-keypair-button').addEventListener('click', performCreateKeypair); document.getElementById('select-keypair').addEventListener('change', performSelectKeypair); // Set up the signing example document.getElementById('sign-button').addEventListener('click', () => { if (!isLoggedIn) { document.getElementById('signature-result').textContent = 'Please login first'; return; } if (!selectedKeypair) { document.getElementById('signature-result').textContent = 'Please select a keypair first'; return; } const message = document.getElementById('sign-message').value; const messageBytes = new TextEncoder().encode(message); try { const signature = keypair_sign(messageBytes); const signatureHex = bufferToHex(signature); document.getElementById('signature-result').textContent = `Signature: ${signatureHex}`; // Store the signature for verification document.getElementById('verify-signature').value = signatureHex; document.getElementById('verify-message').value = message; } catch (e) { document.getElementById('signature-result').textContent = `Error signing: ${e}`; } }); // Set up the verification example document.getElementById('verify-button').addEventListener('click', () => { if (!isLoggedIn) { document.getElementById('verify-result').textContent = 'Please login first'; return; } if (!selectedKeypair) { document.getElementById('verify-result').textContent = 'Please select a keypair first'; return; } const message = document.getElementById('verify-message').value; const messageBytes = new TextEncoder().encode(message); const signatureHex = document.getElementById('verify-signature').value; const signatureBytes = hexToBuffer(signatureHex); try { const isValid = keypair_verify(messageBytes, signatureBytes); document.getElementById('verify-result').textContent = isValid ? 'Signature is valid!' : 'Signature is NOT valid!'; } catch (e) { document.getElementById('verify-result').textContent = `Error verifying: ${e}`; } }); // Set up the symmetric encryption example document.getElementById('encrypt-button').addEventListener('click', () => { try { // Generate key const key = generate_symmetric_key(); // Display key const keyHex = bufferToHex(key); document.getElementById('sym-key-display').textContent = `Key: ${keyHex}`; // Store for decryption document.getElementById('decrypt-key').value = keyHex; // Encrypt the message const message = document.getElementById('encrypt-message').value; const messageBytes = new TextEncoder().encode(message); try { const ciphertext = encrypt_symmetric(key, messageBytes); const ciphertextHex = bufferToHex(ciphertext); document.getElementById('encrypt-result').textContent = `Ciphertext: ${ciphertextHex}`; // Store for decryption document.getElementById('decrypt-ciphertext').value = ciphertextHex; } catch (e) { document.getElementById('encrypt-result').textContent = `Error encrypting: ${e}`; } } catch (e) { document.getElementById('encrypt-result').textContent = `Error: ${e}`; } }); // Set up the symmetric decryption example document.getElementById('decrypt-button').addEventListener('click', () => { try { const keyHex = document.getElementById('decrypt-key').value; const ciphertextHex = document.getElementById('decrypt-ciphertext').value; const key = hexToBuffer(keyHex); const ciphertext = hexToBuffer(ciphertextHex); try { const plaintext = decrypt_symmetric(key, ciphertext); const decodedText = new TextDecoder().decode(plaintext); document.getElementById('decrypt-result').textContent = `Decrypted: ${decodedText}`; } catch (e) { document.getElementById('decrypt-result').textContent = `Error decrypting: ${e}`; } } catch (e) { document.getElementById('decrypt-result').textContent = `Error: ${e}`; } }); // Set up the password-based encryption example document.getElementById('password-encrypt-button').addEventListener('click', () => { try { const password = document.getElementById('password-encrypt-password').value; if (!password) { document.getElementById('password-encrypt-result').textContent = 'Please enter a password'; return; } const message = document.getElementById('password-encrypt-message').value; const messageBytes = new TextEncoder().encode(message); const ciphertext = encrypt_with_password(password, messageBytes); const ciphertextHex = bufferToHex(ciphertext); document.getElementById('password-encrypt-result').textContent = `Ciphertext: ${ciphertextHex}`; // Store for decryption document.getElementById('password-decrypt-ciphertext').value = ciphertextHex; document.getElementById('password-decrypt-password').value = password; } catch (e) { document.getElementById('password-encrypt-result').textContent = `Error: ${e}`; } }); // Set up the password-based decryption example document.getElementById('password-decrypt-button').addEventListener('click', () => { try { const password = document.getElementById('password-decrypt-password').value; if (!password) { document.getElementById('password-decrypt-result').textContent = 'Please enter a password'; return; } const ciphertextHex = document.getElementById('password-decrypt-ciphertext').value; const ciphertext = hexToBuffer(ciphertextHex); const plaintext = decrypt_with_password(password, ciphertext); const decodedText = new TextDecoder().decode(plaintext); document.getElementById('password-decrypt-result').textContent = `Decrypted: ${decodedText}`; } catch (e) { document.getElementById('password-decrypt-result').textContent = `Error: ${e}`; } }); // Set up the public key verification example document.getElementById('pubkey-verify-button').addEventListener('click', () => { try { const publicKeyHex = document.getElementById('pubkey-verify-pubkey').value.trim(); if (!publicKeyHex) { document.getElementById('pubkey-verify-result').textContent = 'Please enter a public key'; return; } const message = document.getElementById('pubkey-verify-message').value; const messageBytes = new TextEncoder().encode(message); const signatureHex = document.getElementById('pubkey-verify-signature').value; const signatureBytes = hexToBuffer(signatureHex); const publicKeyBytes = hexToBuffer(publicKeyHex); try { const isValid = verify_with_public_key(publicKeyBytes, messageBytes, signatureBytes); document.getElementById('pubkey-verify-result').textContent = isValid ? 'Signature is valid!' : 'Signature is NOT valid!'; } catch (e) { document.getElementById('pubkey-verify-result').textContent = `Error verifying: ${e}`; } } catch (e) { document.getElementById('pubkey-verify-result').textContent = `Error: ${e}`; } }); // Set up the derive public key example document.getElementById('derive-pubkey-button').addEventListener('click', () => { try { const privateKeyHex = document.getElementById('derive-pubkey-privkey').value.trim(); if (!privateKeyHex) { document.getElementById('derive-pubkey-result').textContent = 'Please enter a private key'; return; } const privateKeyBytes = hexToBuffer(privateKeyHex); try { const publicKey = derive_public_key(privateKeyBytes); const publicKeyHex = bufferToHex(publicKey); // Create a more user-friendly display with copy button const pubKeyDisplay = document.getElementById('derive-pubkey-result'); pubKeyDisplay.innerHTML = `
Derived Public Key (hex):
${publicKeyHex}
`; // Add event listener for the copy button document.getElementById('copy-derived-pubkey-button').addEventListener('click', () => { const pubKeyText = document.getElementById('derived-pubkey-hex-value').textContent; navigator.clipboard.writeText(pubKeyText) .then(() => { alert('Public key copied to clipboard!'); }) .catch(err => { console.error('Could not copy text: ', err); }); }); // Also populate the public key field in the verify with public key section document.getElementById('pubkey-verify-pubkey').value = publicKeyHex; // And in the asymmetric encryption section document.getElementById('asymmetric-encrypt-pubkey').value = publicKeyHex; } catch (e) { document.getElementById('derive-pubkey-result').textContent = `Error deriving public key: ${e}`; } } catch (e) { document.getElementById('derive-pubkey-result').textContent = `Error: ${e}`; } }); // Set up the asymmetric encryption example document.getElementById('asymmetric-encrypt-button').addEventListener('click', () => { try { const publicKeyHex = document.getElementById('asymmetric-encrypt-pubkey').value.trim(); if (!publicKeyHex) { document.getElementById('asymmetric-encrypt-result').textContent = 'Please enter a recipient public key'; return; } const message = document.getElementById('asymmetric-encrypt-message').value; const messageBytes = new TextEncoder().encode(message); const publicKeyBytes = hexToBuffer(publicKeyHex); try { const ciphertext = encrypt_asymmetric(publicKeyBytes, messageBytes); const ciphertextHex = bufferToHex(ciphertext); document.getElementById('asymmetric-encrypt-result').textContent = `Ciphertext: ${ciphertextHex}`; // Store for decryption document.getElementById('asymmetric-decrypt-ciphertext').value = ciphertextHex; } catch (e) { document.getElementById('asymmetric-encrypt-result').textContent = `Error encrypting: ${e}`; } } catch (e) { document.getElementById('asymmetric-encrypt-result').textContent = `Error: ${e}`; } }); // Set up the asymmetric decryption example document.getElementById('asymmetric-decrypt-button').addEventListener('click', () => { if (!isLoggedIn) { document.getElementById('asymmetric-decrypt-result').textContent = 'Please login first'; return; } if (!selectedKeypair) { document.getElementById('asymmetric-decrypt-result').textContent = 'Please select a keypair first'; return; } try { const ciphertextHex = document.getElementById('asymmetric-decrypt-ciphertext').value; const ciphertext = hexToBuffer(ciphertextHex); try { const plaintext = decrypt_asymmetric(ciphertext); const decodedText = new TextDecoder().decode(plaintext); document.getElementById('asymmetric-decrypt-result').textContent = `Decrypted: ${decodedText}`; } catch (e) { document.getElementById('asymmetric-decrypt-result').textContent = `Error decrypting: ${e}`; } } catch (e) { document.getElementById('asymmetric-decrypt-result').textContent = `Error: ${e}`; } }); // Initialize UI updateLoginUI(); } run().catch(console.error);