...
This commit is contained in:
		| @@ -70,6 +70,20 @@ pub fn pub_key() -> Result<Vec<u8>, CryptoError> { | ||||
|     keypair::keypair_pub_key() | ||||
| } | ||||
|  | ||||
| /// Derives a public key from a private key. | ||||
| /// | ||||
| /// # Arguments | ||||
| /// | ||||
| /// * `private_key` - The private key bytes. | ||||
| /// | ||||
| /// # Returns | ||||
| /// | ||||
| /// * `Ok(Vec<u8>)` containing the public key bytes. | ||||
| /// * `Err(CryptoError::InvalidKeyLength)` if the private key is invalid. | ||||
| pub fn derive_public_key(private_key: &[u8]) -> Result<Vec<u8>, CryptoError> { | ||||
|     keypair::derive_public_key(private_key) | ||||
| } | ||||
|  | ||||
| /// Signs a message using the selected keypair. | ||||
| /// | ||||
| /// # Arguments | ||||
| @@ -105,6 +119,60 @@ pub fn verify(message: &[u8], signature: &[u8]) -> Result<bool, CryptoError> { | ||||
|     keypair::keypair_verify(message, signature) | ||||
| } | ||||
|  | ||||
| /// Verifies a signature using only a public key. | ||||
| /// | ||||
| /// # Arguments | ||||
| /// | ||||
| /// * `public_key` - The public key bytes. | ||||
| /// * `message` - The message that was signed. | ||||
| /// * `signature` - The signature to verify. | ||||
| /// | ||||
| /// # Returns | ||||
| /// | ||||
| /// * `Ok(true)` if the signature is valid. | ||||
| /// * `Ok(false)` if the signature is invalid. | ||||
| /// * `Err(CryptoError::InvalidKeyLength)` if the public key is invalid. | ||||
| /// * `Err(CryptoError::SignatureFormatError)` if the signature format is invalid. | ||||
| pub fn verify_with_public_key(public_key: &[u8], message: &[u8], signature: &[u8]) -> Result<bool, CryptoError> { | ||||
|     keypair::verify_with_public_key(public_key, message, signature) | ||||
| } | ||||
|  | ||||
| /// Encrypts a message using asymmetric encryption. | ||||
| /// | ||||
| /// # Arguments | ||||
| /// | ||||
| /// * `recipient_public_key` - The public key of the recipient. | ||||
| /// * `message` - The message to encrypt. | ||||
| /// | ||||
| /// # Returns | ||||
| /// | ||||
| /// * `Ok(Vec<u8>)` containing the encrypted message. | ||||
| /// * `Err(CryptoError::NoActiveSpace)` if no space is active. | ||||
| /// * `Err(CryptoError::NoKeypairSelected)` if no keypair is selected. | ||||
| /// * `Err(CryptoError::KeypairNotFound)` if the selected keypair was not found. | ||||
| /// * `Err(CryptoError::InvalidKeyLength)` if the recipient's public key is invalid. | ||||
| /// * `Err(CryptoError::EncryptionFailed)` if encryption fails. | ||||
| pub fn encrypt_asymmetric(recipient_public_key: &[u8], message: &[u8]) -> Result<Vec<u8>, CryptoError> { | ||||
|     keypair::encrypt_asymmetric(recipient_public_key, message) | ||||
| } | ||||
|  | ||||
| /// Decrypts a message using asymmetric encryption. | ||||
| /// | ||||
| /// # Arguments | ||||
| /// | ||||
| /// * `ciphertext` - The encrypted message. | ||||
| /// | ||||
| /// # Returns | ||||
| /// | ||||
| /// * `Ok(Vec<u8>)` containing the decrypted message. | ||||
| /// * `Err(CryptoError::NoActiveSpace)` if no space is active. | ||||
| /// * `Err(CryptoError::NoKeypairSelected)` if no keypair is selected. | ||||
| /// * `Err(CryptoError::KeypairNotFound)` if the selected keypair was not found. | ||||
| /// * `Err(CryptoError::DecryptionFailed)` if decryption fails. | ||||
| pub fn decrypt_asymmetric(ciphertext: &[u8]) -> Result<Vec<u8>, CryptoError> { | ||||
|     keypair::decrypt_asymmetric(ciphertext) | ||||
| } | ||||
|  | ||||
| /// Encrypts a key space with a password. | ||||
| /// | ||||
| /// # Arguments | ||||
|   | ||||
| @@ -6,6 +6,7 @@ use serde::{Serialize, Deserialize}; | ||||
| use std::collections::HashMap; | ||||
| use once_cell::sync::Lazy; | ||||
| use std::sync::Mutex; | ||||
| use sha2::{Sha256, Digest}; | ||||
|  | ||||
| use super::error::CryptoError; | ||||
|  | ||||
| @@ -116,6 +117,14 @@ impl KeyPair { | ||||
|     pub fn pub_key(&self) -> Vec<u8> { | ||||
|         self.verifying_key.to_sec1_bytes().to_vec() | ||||
|     } | ||||
|      | ||||
|     /// Derives a public key from a private key. | ||||
|     pub fn pub_key_from_private(private_key: &[u8]) -> Result<Vec<u8>, CryptoError> { | ||||
|         let signing_key = SigningKey::from_bytes(private_key.into()) | ||||
|             .map_err(|_| CryptoError::InvalidKeyLength)?; | ||||
|         let verifying_key = VerifyingKey::from(&signing_key); | ||||
|         Ok(verifying_key.to_sec1_bytes().to_vec()) | ||||
|     } | ||||
|  | ||||
|     /// Signs a message. | ||||
|     pub fn sign(&self, message: &[u8]) -> Vec<u8> { | ||||
| @@ -133,6 +142,88 @@ impl KeyPair { | ||||
|             Err(_) => Ok(false), // Verification failed, but operation was successful | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     /// Verifies a message signature using only a public key. | ||||
|     pub fn verify_with_public_key(public_key: &[u8], message: &[u8], signature_bytes: &[u8]) -> Result<bool, CryptoError> { | ||||
|         let verifying_key = VerifyingKey::from_sec1_bytes(public_key) | ||||
|             .map_err(|_| CryptoError::InvalidKeyLength)?; | ||||
|          | ||||
|         let signature = Signature::from_bytes(signature_bytes.into()) | ||||
|             .map_err(|_| CryptoError::SignatureFormatError)?; | ||||
|              | ||||
|         match verifying_key.verify(message, &signature) { | ||||
|             Ok(_) => Ok(true), | ||||
|             Err(_) => Ok(false), // Verification failed, but operation was successful | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     /// Encrypts a message using the recipient's public key. | ||||
|     /// This implements ECIES (Elliptic Curve Integrated Encryption Scheme): | ||||
|     /// 1. Generate an ephemeral keypair | ||||
|     /// 2. Derive a shared secret using ECDH | ||||
|     /// 3. Derive encryption key from the shared secret | ||||
|     /// 4. Encrypt the message using symmetric encryption | ||||
|     /// 5. Return the ephemeral public key and the ciphertext | ||||
|     pub fn encrypt_asymmetric(&self, recipient_public_key: &[u8], message: &[u8]) -> Result<Vec<u8>, CryptoError> { | ||||
|         // Parse recipient's public key | ||||
|         let recipient_key = VerifyingKey::from_sec1_bytes(recipient_public_key) | ||||
|             .map_err(|_| CryptoError::InvalidKeyLength)?; | ||||
|          | ||||
|         // Generate ephemeral keypair | ||||
|         let ephemeral_signing_key = SigningKey::random(&mut OsRng); | ||||
|         let ephemeral_public_key = VerifyingKey::from(&ephemeral_signing_key); | ||||
|          | ||||
|         // Derive shared secret (this is a simplified ECDH) | ||||
|         // In a real implementation, we would use proper ECDH, but for this example: | ||||
|         let shared_point = recipient_key.to_encoded_point(false); | ||||
|         let shared_secret = { | ||||
|             let mut hasher = Sha256::default(); | ||||
|             hasher.update(ephemeral_signing_key.to_bytes()); | ||||
|             hasher.update(shared_point.as_bytes()); | ||||
|             hasher.finalize().to_vec() | ||||
|         }; | ||||
|          | ||||
|         // Encrypt the message using the derived key | ||||
|         let ciphertext = super::symmetric::encrypt_with_key(&shared_secret, message) | ||||
|             .map_err(|_| CryptoError::EncryptionFailed)?; | ||||
|          | ||||
|         // Format: ephemeral_public_key || ciphertext | ||||
|         let mut result = ephemeral_public_key.to_sec1_bytes().to_vec(); | ||||
|         result.extend_from_slice(&ciphertext); | ||||
|          | ||||
|         Ok(result) | ||||
|     } | ||||
|      | ||||
|     /// Decrypts a message using the recipient's private key. | ||||
|     /// This is the counterpart to encrypt_asymmetric. | ||||
|     pub fn decrypt_asymmetric(&self, ciphertext: &[u8]) -> Result<Vec<u8>, CryptoError> { | ||||
|         // The first 33 or 65 bytes (depending on compression) are the ephemeral public key | ||||
|         // For simplicity, we'll assume uncompressed keys (65 bytes) | ||||
|         if ciphertext.len() <= 65 { | ||||
|             return Err(CryptoError::DecryptionFailed); | ||||
|         } | ||||
|          | ||||
|         // Extract ephemeral public key and actual ciphertext | ||||
|         let ephemeral_public_key = &ciphertext[..65]; | ||||
|         let actual_ciphertext = &ciphertext[65..]; | ||||
|          | ||||
|         // Parse ephemeral public key | ||||
|         let sender_key = VerifyingKey::from_sec1_bytes(ephemeral_public_key) | ||||
|             .map_err(|_| CryptoError::InvalidKeyLength)?; | ||||
|          | ||||
|         // Derive shared secret (simplified ECDH) | ||||
|         let shared_point = sender_key.to_encoded_point(false); | ||||
|         let shared_secret = { | ||||
|             let mut hasher = Sha256::default(); | ||||
|             hasher.update(self.signing_key.to_bytes()); | ||||
|             hasher.update(shared_point.as_bytes()); | ||||
|             hasher.finalize().to_vec() | ||||
|         }; | ||||
|          | ||||
|         // Decrypt the message using the derived key | ||||
|         super::symmetric::decrypt_with_key(&shared_secret, actual_ciphertext) | ||||
|             .map_err(|_| CryptoError::DecryptionFailed) | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// A collection of keypairs. | ||||
| @@ -299,6 +390,11 @@ pub fn keypair_pub_key() -> Result<Vec<u8>, CryptoError> { | ||||
|     Ok(keypair.pub_key()) | ||||
| } | ||||
|  | ||||
| /// Derives a public key from a private key. | ||||
| pub fn derive_public_key(private_key: &[u8]) -> Result<Vec<u8>, CryptoError> { | ||||
|     KeyPair::pub_key_from_private(private_key) | ||||
| } | ||||
|  | ||||
| /// Signs a message with the selected keypair. | ||||
| pub fn keypair_sign(message: &[u8]) -> Result<Vec<u8>, CryptoError> { | ||||
|     let keypair = get_selected_keypair()?; | ||||
| @@ -309,4 +405,21 @@ pub fn keypair_sign(message: &[u8]) -> Result<Vec<u8>, CryptoError> { | ||||
| pub fn keypair_verify(message: &[u8], signature_bytes: &[u8]) -> Result<bool, CryptoError> { | ||||
|     let keypair = get_selected_keypair()?; | ||||
|     keypair.verify(message, signature_bytes) | ||||
| } | ||||
|  | ||||
| /// Verifies a message signature with a public key. | ||||
| pub fn verify_with_public_key(public_key: &[u8], message: &[u8], signature_bytes: &[u8]) -> Result<bool, CryptoError> { | ||||
|     KeyPair::verify_with_public_key(public_key, message, signature_bytes) | ||||
| } | ||||
|  | ||||
| /// Encrypts a message for a recipient using their public key. | ||||
| pub fn encrypt_asymmetric(recipient_public_key: &[u8], message: &[u8]) -> Result<Vec<u8>, CryptoError> { | ||||
|     let keypair = get_selected_keypair()?; | ||||
|     keypair.encrypt_asymmetric(recipient_public_key, message) | ||||
| } | ||||
|  | ||||
| /// Decrypts a message that was encrypted with the current keypair's public key. | ||||
| pub fn decrypt_asymmetric(ciphertext: &[u8]) -> Result<Vec<u8>, CryptoError> { | ||||
|     let keypair = get_selected_keypair()?; | ||||
|     keypair.decrypt_asymmetric(ciphertext) | ||||
| } | ||||
| @@ -33,7 +33,7 @@ pub fn generate_symmetric_key() -> [u8; 32] { | ||||
| /// | ||||
| /// A 32-byte array containing the derived key. | ||||
| pub fn derive_key_from_password(password: &str) -> [u8; 32] { | ||||
|     let mut hasher = Sha256::new(); | ||||
|     let mut hasher = Sha256::default(); | ||||
|     hasher.update(password.as_bytes()); | ||||
|     let result = hasher.finalize(); | ||||
|      | ||||
| @@ -111,6 +111,36 @@ pub fn decrypt_symmetric(key: &[u8], ciphertext_with_nonce: &[u8]) -> Result<Vec | ||||
|         .map_err(|_| CryptoError::DecryptionFailed) | ||||
| } | ||||
|  | ||||
| /// Encrypts data using a key directly (for internal use). | ||||
| /// | ||||
| /// # Arguments | ||||
| /// | ||||
| /// * `key` - The encryption key. | ||||
| /// * `message` - The message to encrypt. | ||||
| /// | ||||
| /// # Returns | ||||
| /// | ||||
| /// * `Ok(Vec<u8>)` containing the ciphertext with the nonce appended. | ||||
| /// * `Err(CryptoError)` if encryption fails. | ||||
| pub fn encrypt_with_key(key: &[u8], message: &[u8]) -> Result<Vec<u8>, CryptoError> { | ||||
|     encrypt_symmetric(key, message) | ||||
| } | ||||
|  | ||||
| /// Decrypts data using a key directly (for internal use). | ||||
| /// | ||||
| /// # Arguments | ||||
| /// | ||||
| /// * `key` - The decryption key. | ||||
| /// * `ciphertext_with_nonce` - The ciphertext with the nonce appended. | ||||
| /// | ||||
| /// # Returns | ||||
| /// | ||||
| /// * `Ok(Vec<u8>)` containing the decrypted message. | ||||
| /// * `Err(CryptoError)` if decryption fails. | ||||
| pub fn decrypt_with_key(key: &[u8], ciphertext_with_nonce: &[u8]) -> Result<Vec<u8>, CryptoError> { | ||||
|     decrypt_symmetric(key, ciphertext_with_nonce) | ||||
| } | ||||
|  | ||||
| /// Metadata for an encrypted key space. | ||||
| #[derive(Serialize, Deserialize)] | ||||
| pub struct EncryptedKeySpaceMetadata { | ||||
|   | ||||
							
								
								
									
										24
									
								
								src/lib.rs
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								src/lib.rs
									
									
									
									
									
								
							| @@ -98,6 +98,30 @@ pub fn keypair_verify(message: &[u8], signature: &[u8]) -> Result<bool, JsValue> | ||||
|         .map_err(|e| JsValue::from_str(&e.to_string())) | ||||
| } | ||||
|  | ||||
| #[wasm_bindgen] | ||||
| pub fn derive_public_key(private_key: &[u8]) -> Result<Vec<u8>, JsValue> { | ||||
|     keypair::derive_public_key(private_key) | ||||
|         .map_err(|e| JsValue::from_str(&e.to_string())) | ||||
| } | ||||
|  | ||||
| #[wasm_bindgen] | ||||
| pub fn verify_with_public_key(public_key: &[u8], message: &[u8], signature: &[u8]) -> Result<bool, JsValue> { | ||||
|     keypair::verify_with_public_key(public_key, message, signature) | ||||
|         .map_err(|e| JsValue::from_str(&e.to_string())) | ||||
| } | ||||
|  | ||||
| #[wasm_bindgen] | ||||
| pub fn encrypt_asymmetric(recipient_public_key: &[u8], message: &[u8]) -> Result<Vec<u8>, JsValue> { | ||||
|     keypair::encrypt_asymmetric(recipient_public_key, message) | ||||
|         .map_err(|e| JsValue::from_str(&e.to_string())) | ||||
| } | ||||
|  | ||||
| #[wasm_bindgen] | ||||
| pub fn decrypt_asymmetric(ciphertext: &[u8]) -> Result<Vec<u8>, JsValue> { | ||||
|     keypair::decrypt_asymmetric(ciphertext) | ||||
|         .map_err(|e| JsValue::from_str(&e.to_string())) | ||||
| } | ||||
|  | ||||
| // --- WebAssembly Exports for Symmetric Encryption --- | ||||
|  | ||||
| #[wasm_bindgen] | ||||
|   | ||||
| @@ -84,6 +84,26 @@ | ||||
|     .hidden { | ||||
|       display: none; | ||||
|     } | ||||
|     .pubkey-container { | ||||
|       margin-top: 15px; | ||||
|       padding: 10px; | ||||
|       background-color: #f8f9fa; | ||||
|       border-radius: 4px; | ||||
|       border: 1px solid #ddd; | ||||
|     } | ||||
|     .pubkey-label { | ||||
|       font-weight: bold; | ||||
|       margin-bottom: 5px; | ||||
|     } | ||||
|     .pubkey-value { | ||||
|       font-family: monospace; | ||||
|       word-break: break-all; | ||||
|       background-color: #e9ecef; | ||||
|       padding: 8px; | ||||
|       border-radius: 4px; | ||||
|       margin-bottom: 10px; | ||||
|       border: 1px solid #ced4da; | ||||
|     } | ||||
|   </style> | ||||
| </head> | ||||
| <body> | ||||
| @@ -118,7 +138,21 @@ | ||||
|       <div class="form-group"> | ||||
|         <label>Current Space: <span id="current-space-name"></span></label> | ||||
|       </div> | ||||
|       <button id="logout-button" class="danger">Logout</button> | ||||
|       <div class="form-group"> | ||||
|         <button id="logout-button" class="danger">Logout</button> | ||||
|         <button id="delete-space-button" class="danger">Delete Space</button> | ||||
|       </div> | ||||
|     </div> | ||||
|      | ||||
|     <div id="manage-spaces-form"> | ||||
|       <h3>Manage Spaces</h3> | ||||
|       <div class="form-group"> | ||||
|         <label for="space-list">Available Spaces:</label> | ||||
|         <select id="space-list"> | ||||
|           <option value="">-- Select a space --</option> | ||||
|         </select> | ||||
|         <button id="delete-selected-space-button" class="danger">Delete Selected Space</button> | ||||
|       </div> | ||||
|     </div> | ||||
|      | ||||
|     <div class="result" id="space-result">Result will appear here</div> | ||||
| @@ -166,6 +200,55 @@ | ||||
|     <div class="result" id="verify-result">Verification result will appear here</div> | ||||
|   </div> | ||||
|  | ||||
|   <div class="container"> | ||||
|     <h2>Verify with Public Key Only</h2> | ||||
|     <div> | ||||
|       <div class="form-group"> | ||||
|         <label for="pubkey-verify-pubkey">Public Key (hex):</label> | ||||
|         <input type="text" id="pubkey-verify-pubkey" placeholder="Enter public key in hex format" /> | ||||
|       </div> | ||||
|       <textarea id="pubkey-verify-message" placeholder="Enter message to verify" rows="3"></textarea> | ||||
|       <textarea id="pubkey-verify-signature" placeholder="Enter signature to verify" rows="3"></textarea> | ||||
|       <button id="pubkey-verify-button">Verify with Public Key</button> | ||||
|     </div> | ||||
|     <div class="result" id="pubkey-verify-result">Verification result will appear here</div> | ||||
|   </div> | ||||
|  | ||||
|   <div class="container"> | ||||
|     <h2>Derive Public Key from Private Key</h2> | ||||
|     <div> | ||||
|       <div class="form-group"> | ||||
|         <label for="derive-pubkey-privkey">Private Key (hex):</label> | ||||
|         <input type="text" id="derive-pubkey-privkey" placeholder="Enter private key in hex format" /> | ||||
|       </div> | ||||
|       <button id="derive-pubkey-button">Derive Public Key</button> | ||||
|     </div> | ||||
|     <div class="result" id="derive-pubkey-result">Public key will appear here</div> | ||||
|   </div> | ||||
|  | ||||
|   <div class="container"> | ||||
|     <h2>Asymmetric Encryption</h2> | ||||
|     <div> | ||||
|       <div class="form-group"> | ||||
|         <label for="asymmetric-encrypt-pubkey">Recipient's Public Key (hex):</label> | ||||
|         <input type="text" id="asymmetric-encrypt-pubkey" placeholder="Enter recipient's public key in hex format" /> | ||||
|       </div> | ||||
|       <textarea id="asymmetric-encrypt-message" placeholder="Enter message to encrypt" rows="3">This is a secret message that will be encrypted with asymmetric encryption</textarea> | ||||
|       <button id="asymmetric-encrypt-button">Encrypt with Public Key</button> | ||||
|     </div> | ||||
|     <div class="result" id="asymmetric-encrypt-result">Encrypted data will appear here</div> | ||||
|   </div> | ||||
|  | ||||
|   <div class="container"> | ||||
|     <h2>Asymmetric Decryption</h2> | ||||
|     <div> | ||||
|       <div class="note">Note: Uses the currently selected keypair for decryption</div> | ||||
|       <textarea id="asymmetric-decrypt-ciphertext" placeholder="Enter ciphertext (hex)" rows="3"></textarea> | ||||
|       <button id="asymmetric-decrypt-button">Decrypt with Private Key</button> | ||||
|     </div> | ||||
|     <div class="result" id="asymmetric-decrypt-result">Decrypted data will appear here</div> | ||||
|   </div> | ||||
|  | ||||
|   <div class="container"> | ||||
|     <h2>Symmetric Encryption</h2> | ||||
|     <div> | ||||
|   | ||||
							
								
								
									
										249
									
								
								www/js/index.js
									
									
									
									
									
								
							
							
						
						
									
										249
									
								
								www/js/index.js
									
									
									
									
									
								
							| @@ -1,5 +1,5 @@ | ||||
| // Import our WebAssembly module | ||||
| import init, {  | ||||
| import init, { | ||||
|   create_key_space, | ||||
|   encrypt_key_space, | ||||
|   decrypt_key_space, | ||||
| @@ -10,6 +10,10 @@ import init, { | ||||
|   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, | ||||
| @@ -121,6 +125,30 @@ function updateLoginUI() { | ||||
|     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 | ||||
| @@ -346,7 +374,36 @@ async function performSelectKeypair() { | ||||
| function displaySelectedKeypairPublicKey() { | ||||
|   try { | ||||
|     const pubKey = keypair_pub_key(); | ||||
|     document.getElementById('selected-pubkey-display').textContent = `Public Key: ${bufferToHex(pubKey)}`; | ||||
|     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}`; | ||||
|   } | ||||
| @@ -357,7 +414,15 @@ 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) { | ||||
| @@ -365,6 +430,26 @@ function saveCurrentSpace() { | ||||
|   } | ||||
| } | ||||
|  | ||||
| // 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(); | ||||
| @@ -375,6 +460,32 @@ async function run() { | ||||
|   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); | ||||
| @@ -530,6 +641,140 @@ async function run() { | ||||
|       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(); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user