1001 lines
33 KiB
JavaScript
1001 lines
33 KiB
JavaScript
// 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;
|
|
}
|
|
}
|
|
|
|
// IndexedDB setup and functions
|
|
const DB_NAME = 'CryptoSpaceDB';
|
|
const DB_VERSION = 1;
|
|
const STORE_NAME = 'keySpaces';
|
|
|
|
// Initialize the database
|
|
function initDatabase() {
|
|
return new Promise((resolve, reject) => {
|
|
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
|
|
|
request.onerror = (event) => {
|
|
console.error('Error opening database:', event.target.error);
|
|
reject('Error opening database: ' + event.target.error);
|
|
};
|
|
|
|
request.onsuccess = (event) => {
|
|
const db = event.target.result;
|
|
resolve(db);
|
|
};
|
|
|
|
request.onupgradeneeded = (event) => {
|
|
const db = event.target.result;
|
|
// Create object store for key spaces if it doesn't exist
|
|
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
const store = db.createObjectStore(STORE_NAME, { keyPath: 'name' });
|
|
store.createIndex('name', 'name', { unique: true });
|
|
store.createIndex('lastAccessed', 'lastAccessed', { unique: false });
|
|
}
|
|
};
|
|
});
|
|
}
|
|
|
|
// Get database connection
|
|
function getDB() {
|
|
return initDatabase();
|
|
}
|
|
|
|
// Save encrypted space to IndexedDB
|
|
async function saveSpaceToStorage(spaceName, encryptedData) {
|
|
const db = await getDB();
|
|
return new Promise((resolve, reject) => {
|
|
const transaction = db.transaction([STORE_NAME], 'readwrite');
|
|
const store = transaction.objectStore(STORE_NAME);
|
|
|
|
const space = {
|
|
name: spaceName,
|
|
encryptedData: encryptedData,
|
|
created: new Date(),
|
|
lastAccessed: new Date()
|
|
};
|
|
|
|
const request = store.put(space);
|
|
|
|
request.onsuccess = () => {
|
|
resolve();
|
|
};
|
|
|
|
request.onerror = (event) => {
|
|
console.error('Error saving space:', event.target.error);
|
|
reject('Error saving space: ' + event.target.error);
|
|
};
|
|
|
|
transaction.oncomplete = () => {
|
|
db.close();
|
|
};
|
|
});
|
|
}
|
|
|
|
// Get encrypted space from IndexedDB
|
|
async function getSpaceFromStorage(spaceName) {
|
|
try {
|
|
const db = await getDB();
|
|
return new Promise((resolve, reject) => {
|
|
const transaction = db.transaction([STORE_NAME], 'readonly');
|
|
const store = transaction.objectStore(STORE_NAME);
|
|
const request = store.get(spaceName);
|
|
|
|
request.onsuccess = (event) => {
|
|
const space = event.target.result;
|
|
if (space) {
|
|
// Update last accessed timestamp
|
|
updateLastAccessed(spaceName).catch(console.error);
|
|
resolve(space.encryptedData);
|
|
} else {
|
|
resolve(null);
|
|
}
|
|
};
|
|
|
|
request.onerror = (event) => {
|
|
console.error('Error retrieving space:', event.target.error);
|
|
reject('Error retrieving space: ' + event.target.error);
|
|
};
|
|
|
|
transaction.oncomplete = () => {
|
|
db.close();
|
|
};
|
|
});
|
|
} catch (error) {
|
|
console.error('Database error in getSpaceFromStorage:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Update last accessed timestamp
|
|
async function updateLastAccessed(spaceName) {
|
|
const db = await getDB();
|
|
return new Promise((resolve, reject) => {
|
|
const transaction = db.transaction([STORE_NAME], 'readwrite');
|
|
const store = transaction.objectStore(STORE_NAME);
|
|
const request = store.get(spaceName);
|
|
|
|
request.onsuccess = (event) => {
|
|
const space = event.target.result;
|
|
if (space) {
|
|
space.lastAccessed = new Date();
|
|
store.put(space);
|
|
resolve();
|
|
} else {
|
|
resolve();
|
|
}
|
|
};
|
|
|
|
transaction.oncomplete = () => {
|
|
db.close();
|
|
};
|
|
});
|
|
}
|
|
|
|
// List all spaces in IndexedDB
|
|
async function listSpacesFromStorage() {
|
|
const db = await getDB();
|
|
return new Promise((resolve, reject) => {
|
|
const transaction = db.transaction([STORE_NAME], 'readonly');
|
|
const store = transaction.objectStore(STORE_NAME);
|
|
const request = store.openCursor();
|
|
|
|
const spaces = [];
|
|
|
|
request.onsuccess = (event) => {
|
|
const cursor = event.target.result;
|
|
if (cursor) {
|
|
spaces.push(cursor.value.name);
|
|
cursor.continue();
|
|
} else {
|
|
resolve(spaces);
|
|
}
|
|
};
|
|
|
|
request.onerror = (event) => {
|
|
console.error('Error listing spaces:', event.target.error);
|
|
reject('Error listing spaces: ' + event.target.error);
|
|
};
|
|
|
|
transaction.oncomplete = () => {
|
|
db.close();
|
|
};
|
|
});
|
|
}
|
|
|
|
// Remove space from IndexedDB
|
|
async function removeSpaceFromStorage(spaceName) {
|
|
const db = await getDB();
|
|
return new Promise((resolve, reject) => {
|
|
const transaction = db.transaction([STORE_NAME], 'readwrite');
|
|
const store = transaction.objectStore(STORE_NAME);
|
|
const request = store.delete(spaceName);
|
|
|
|
request.onsuccess = () => {
|
|
resolve();
|
|
};
|
|
|
|
request.onerror = (event) => {
|
|
console.error('Error removing space:', event.target.error);
|
|
reject('Error removing space: ' + event.target.error);
|
|
};
|
|
|
|
transaction.oncomplete = () => {
|
|
db.close();
|
|
};
|
|
});
|
|
}
|
|
|
|
// Session state
|
|
let isLoggedIn = false;
|
|
let currentSpace = null;
|
|
let selectedKeypair = null;
|
|
|
|
// Update UI based on login state
|
|
async 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
|
|
try {
|
|
await updateSpacesList();
|
|
} catch (e) {
|
|
console.error('Error updating spaces list in UI:', e);
|
|
}
|
|
}
|
|
|
|
// Update the spaces dropdown list
|
|
async function updateSpacesList() {
|
|
const spacesList = document.getElementById('space-list');
|
|
|
|
// Clear existing options
|
|
while (spacesList.options.length > 1) {
|
|
spacesList.remove(1);
|
|
}
|
|
|
|
try {
|
|
// Get spaces list
|
|
const spaces = await listSpacesFromStorage();
|
|
|
|
// Add options for each space
|
|
spaces.forEach(spaceName => {
|
|
const option = document.createElement('option');
|
|
option.value = spaceName;
|
|
option.textContent = spaceName;
|
|
spacesList.appendChild(option);
|
|
});
|
|
} catch (e) {
|
|
console.error('Error updating spaces list:', e);
|
|
}
|
|
}
|
|
|
|
// 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 {
|
|
// Show loading state
|
|
document.getElementById('space-result').textContent = 'Loading...';
|
|
|
|
// Get encrypted space from IndexedDB
|
|
const encryptedSpace = await getSpaceFromStorage(spaceName);
|
|
if (!encryptedSpace) {
|
|
document.getElementById('space-result').textContent = `Space "${spaceName}" not found`;
|
|
return;
|
|
}
|
|
|
|
console.log('Retrieved space from IndexedDB:', { spaceName, encryptedDataLength: encryptedSpace.length });
|
|
|
|
try {
|
|
// Decrypt the space - this is a synchronous WebAssembly function
|
|
const result = decrypt_key_space(encryptedSpace, password);
|
|
console.log('Decrypt result:', result);
|
|
|
|
if (result === 0) {
|
|
isLoggedIn = true;
|
|
currentSpace = spaceName;
|
|
await 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 (decryptErr) {
|
|
console.error('Decryption error:', decryptErr);
|
|
document.getElementById('space-result').textContent = `Decryption error: ${decryptErr}`;
|
|
}
|
|
} catch (e) {
|
|
console.error('Login error:', 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;
|
|
}
|
|
|
|
try {
|
|
// Show loading state
|
|
document.getElementById('space-result').textContent = 'Loading...';
|
|
|
|
// Check if space already exists
|
|
const existingSpace = await getSpaceFromStorage(spaceName);
|
|
if (existingSpace) {
|
|
document.getElementById('space-result').textContent = `Space "${spaceName}" already exists`;
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Create new space
|
|
console.log('Creating new space:', spaceName);
|
|
const result = create_key_space(spaceName);
|
|
console.log('Create space result:', result);
|
|
|
|
if (result === 0) {
|
|
try {
|
|
// Encrypt and save the space
|
|
console.log('Encrypting space with password');
|
|
const encryptedSpace = encrypt_key_space(password);
|
|
console.log('Encrypted space length:', encryptedSpace.length);
|
|
|
|
// Save to IndexedDB
|
|
console.log('Saving to IndexedDB');
|
|
await saveSpaceToStorage(spaceName, encryptedSpace);
|
|
console.log('Save completed');
|
|
|
|
isLoggedIn = true;
|
|
currentSpace = spaceName;
|
|
await 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);
|
|
} catch (encryptError) {
|
|
console.error('Error encrypting or saving space:', encryptError);
|
|
document.getElementById('space-result').textContent = `Error saving space: ${encryptError}`;
|
|
}
|
|
} else {
|
|
document.getElementById('space-result').textContent = `Error creating space: ${result}`;
|
|
}
|
|
} catch (createError) {
|
|
console.error('Error in WebAssembly create_key_space:', createError);
|
|
document.getElementById('space-result').textContent = `Error creating key space: ${createError}`;
|
|
}
|
|
} catch (e) {
|
|
console.error('Error checking existing space:', 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 IndexedDB
|
|
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 = `
|
|
<div class="pubkey-container">
|
|
<div class="pubkey-label">Public Key (hex):</div>
|
|
<div class="pubkey-value" id="pubkey-hex-value">${pubKeyHex}</div>
|
|
<button id="copy-pubkey-button" class="secondary">Copy Public Key</button>
|
|
</div>
|
|
`;
|
|
|
|
// 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 IndexedDB
|
|
async 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);
|
|
await saveSpaceToStorage(currentSpace, encryptedSpace);
|
|
} catch (e) {
|
|
console.error('Error saving space:', e);
|
|
alert('Error saving space: ' + e);
|
|
}
|
|
}
|
|
|
|
// Delete a space from IndexedDB
|
|
async function deleteSpace(spaceName) {
|
|
if (!spaceName) return false;
|
|
|
|
try {
|
|
// Check if space exists
|
|
const existingSpace = await getSpaceFromStorage(spaceName);
|
|
if (!existingSpace) {
|
|
return false;
|
|
}
|
|
|
|
// Remove from IndexedDB
|
|
await removeSpaceFromStorage(spaceName);
|
|
|
|
// If this was the current space, logout
|
|
if (isLoggedIn && currentSpace === spaceName) {
|
|
performLogout();
|
|
}
|
|
|
|
return true;
|
|
} catch (e) {
|
|
console.error('Error deleting space:', e);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
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', async () => {
|
|
if (confirm(`Are you sure you want to delete the space "${currentSpace}"? This action cannot be undone.`)) {
|
|
document.getElementById('space-result').textContent = 'Deleting...';
|
|
try {
|
|
const result = await deleteSpace(currentSpace);
|
|
if (result) {
|
|
document.getElementById('space-result').textContent = `Space "${currentSpace}" deleted successfully`;
|
|
} else {
|
|
document.getElementById('space-result').textContent = `Error deleting space "${currentSpace}"`;
|
|
}
|
|
} catch (e) {
|
|
console.error('Error during space deletion:', e);
|
|
document.getElementById('space-result').textContent = `Error: ${e}`;
|
|
}
|
|
}
|
|
});
|
|
|
|
document.getElementById('delete-selected-space-button').addEventListener('click', async () => {
|
|
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.`)) {
|
|
document.getElementById('space-result').textContent = 'Deleting...';
|
|
try {
|
|
const result = await deleteSpace(selectedSpace);
|
|
if (result) {
|
|
document.getElementById('space-result').textContent = `Space "${selectedSpace}" deleted successfully`;
|
|
await updateSpacesList();
|
|
} else {
|
|
document.getElementById('space-result').textContent = `Error deleting space "${selectedSpace}"`;
|
|
}
|
|
} catch (e) {
|
|
console.error('Error during space deletion:', e);
|
|
document.getElementById('space-result').textContent = `Error: ${e}`;
|
|
}
|
|
}
|
|
});
|
|
|
|
// 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 = `
|
|
<div class="pubkey-container">
|
|
<div class="pubkey-label">Derived Public Key (hex):</div>
|
|
<div class="pubkey-value" id="derived-pubkey-hex-value">${publicKeyHex}</div>
|
|
<button id="copy-derived-pubkey-button" class="secondary">Copy Public Key</button>
|
|
</div>
|
|
`;
|
|
|
|
// 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); |