...
This commit is contained in:
parent
bf2f7b57bb
commit
d8a314df41
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user