diff --git a/.gitignore b/.gitignore index 0c253ee..6a661f8 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,4 @@ yarn-error.log .env.development.local .env.test.local .env.production.local +node_modules \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index b4d21bc..a63861e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,10 @@ rand = { version = "0.8", features = ["getrandom"] } getrandom = { version = "0.2", features = ["js"] } chacha20poly1305 = "0.10" once_cell = "1.18" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +base64 = "0.21" +sha2 = "0.10" [dependencies.web-sys] version = "0.3" diff --git a/implementation_plan.md b/implementation_plan.md index ba65842..408f8b8 100644 --- a/implementation_plan.md +++ b/implementation_plan.md @@ -1,730 +1,377 @@ -# Detailed Implementation Plan +# Implementation Plan: Multi-Keypair Management with Encrypted Spaces -## 1. Create Directory Structure -``` -src/ -├── lib.rs # Main entry point, exports WASM functions -├── api/ # Public API modules -│ ├── mod.rs # Re-exports public API functions -│ ├── keypair.rs # Public keypair API -│ └── symmetric.rs # Public symmetric encryption API -├── core/ # Internal implementation modules -│ ├── mod.rs # Re-exports core functionality -│ ├── error.rs # Error types and conversions -│ ├── keypair.rs # Core keypair implementation -│ └── symmetric.rs # Core symmetric encryption implementation -└── tests/ # Test modules - ├── keypair_tests.rs # Tests for keypair functionality - └── symmetric_tests.rs # Tests for symmetric encryption +## Overview + +This plan outlines the implementation of a multi-keypair management system with encrypted spaces for the WebAssembly crypto module. The system will allow users to: + +1. Create and access different "spaces" using different passwords +2. Manage multiple named keypairs within each space +3. Select which keypair to use for cryptographic operations +4. Automatically log out after 15 minutes of inactivity + +## Architecture + +```mermaid +graph TD + A[User Interface] --> B[WebAssembly API] + B --> C[Rust Core Implementation] + C --> D[LocalStorage] + + subgraph "Frontend (JavaScript)" + A + E[Session Management] + F[UI Components] + G[Event Handlers] + end + + subgraph "WebAssembly Bridge" + B + end + + subgraph "Backend (Rust)" + C + H[KeySpace Management] + I[KeyPair Management] + J[Encryption/Decryption] + end + + E --> A + F --> A + G --> A + H --> C + I --> C + J --> C ``` -## 2. Implementation Steps +## Data Model -### Step 1: Create Core Error Module (src/core/error.rs) -```rust -//! Error types for cryptographic operations. - -/// Errors that can occur during cryptographic operations. -#[derive(Debug)] -pub enum CryptoError { - /// The keypair has not been initialized. - KeypairNotInitialized, - /// The keypair has already been initialized. - KeypairAlreadyInitialized, - /// Signature verification failed. - SignatureVerificationFailed, - /// The signature format is invalid. - SignatureFormatError, - /// Encryption operation failed. - EncryptionFailed, - /// Decryption operation failed. - DecryptionFailed, - /// The key length is invalid. - InvalidKeyLength, - /// Other error with description. - Other(String), -} - -impl std::fmt::Display for CryptoError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - CryptoError::KeypairNotInitialized => write!(f, "Keypair not initialized"), - CryptoError::KeypairAlreadyInitialized => write!(f, "Keypair already initialized"), - CryptoError::SignatureVerificationFailed => write!(f, "Signature verification failed"), - CryptoError::SignatureFormatError => write!(f, "Invalid signature format"), - CryptoError::EncryptionFailed => write!(f, "Encryption failed"), - CryptoError::DecryptionFailed => write!(f, "Decryption failed"), - CryptoError::InvalidKeyLength => write!(f, "Invalid key length"), - CryptoError::Other(s) => write!(f, "Crypto error: {}", s), - } +```mermaid +classDiagram + class KeySpace { + +String name + +Map~String, KeyPair~ keypairs + +encrypt(password) + +decrypt(password) } -} - -impl std::error::Error for CryptoError {} - -/// Converts a CryptoError to an i32 status code for WebAssembly. -pub fn error_to_status_code(err: CryptoError) -> i32 { - match err { - CryptoError::KeypairNotInitialized => -1, - CryptoError::KeypairAlreadyInitialized => -2, - CryptoError::SignatureVerificationFailed => -3, - CryptoError::SignatureFormatError => -4, - CryptoError::EncryptionFailed => -5, - CryptoError::DecryptionFailed => -6, - CryptoError::InvalidKeyLength => -7, - CryptoError::Other(_) => -99, + + class KeyPair { + +String name + +VerifyingKey publicKey + +SigningKey privateKey + +sign(message) + +verify(message, signature) } -} + + class LocalStorage { + +Map~String, EncryptedKeySpace~ spaces + } + + class Session { + +KeySpace currentSpace + +KeyPair activeKeyPair + +DateTime lastActivity + +Boolean isLoggedIn + } + + KeySpace "1" --> "*" KeyPair: contains + LocalStorage "1" --> "*" KeySpace: stores + Session "1" --> "0..1" KeySpace: references + Session "1" --> "0..1" KeyPair: references ``` -### Step 2: Create Core Keypair Module (src/core/keypair.rs) +## Implementation Steps + +### 1. Backend (Rust) Changes + +#### 1.1 Create New Data Structures + +- Create a `KeyPair` struct that includes a name and the existing keypair functionality +- Create a `KeySpace` struct to manage a collection of named keypairs +- Implement serialization/deserialization for these structures + +#### 1.2 Update Core Functionality + +- Remove the global `KEYPAIR` static variable +- Implement functions to create, retrieve, and manage keypairs by name +- Implement functions to encrypt/decrypt a `KeySpace` using a password +- Add error types for the new functionality + +#### 1.3 Update API Layer + +- Update the keypair API to work with named keypairs +- Add functions for: + - Creating/opening a space with a password + - Listing available spaces + - Creating a new keypair in the current space + - Listing keypairs in the current space + - Selecting a keypair for operations + - Signing/verifying with the selected keypair + +#### 1.4 Update WebAssembly Bindings + +- Expose the new API functions to JavaScript +- Ensure proper error handling and type conversions + +### 2. Frontend (JavaScript/HTML) Changes + +#### 2.1 Update JavaScript Interface + +- Create a session management module to track: + - Current space + - Selected keypair + - Login status + - Last activity timestamp +- Implement auto-logout after 15 minutes of inactivity +- Update the existing functions to work with the selected keypair + +#### 2.2 Update HTML/UI + +- Add a login section with: + - Password input + - Space name input (for creating new spaces) + - Login/Create buttons +- Add space management UI: + - Dropdown to select current space + - Button to create a new space +- Add keypair management UI: + - Dropdown to select current keypair + - Input for new keypair name + - Button to create a new keypair +- Update the existing UI sections to work with the selected keypair + +## Detailed Technical Approach + +### Backend Implementation Details + +1. **KeyPair Structure**: ```rust -//! Core implementation of keypair functionality. - -use k256::ecdsa::{SigningKey, VerifyingKey, signature::{Signer, Verifier}, Signature}; -use once_cell::sync::OnceCell; -use rand::rngs::OsRng; - -use super::error::CryptoError; - -/// A keypair for signing and verifying messages. -#[derive(Debug)] pub struct KeyPair { + pub name: String, pub verifying_key: VerifyingKey, pub signing_key: SigningKey, } +``` -/// Global keypair instance. -static KEYPAIR: OnceCell = OnceCell::new(); - -/// Initializes the global keypair. -/// -/// # Returns -/// -/// * `Ok(())` if the keypair was initialized successfully. -/// * `Err(CryptoError::KeypairAlreadyInitialized)` if the keypair was already initialized. -pub fn keypair_new() -> Result<(), CryptoError> { - let signing_key = SigningKey::random(&mut OsRng); - let verifying_key = VerifyingKey::from(&signing_key); - let keypair = KeyPair { verifying_key, signing_key }; - - KEYPAIR.set(keypair).map_err(|_| CryptoError::KeypairAlreadyInitialized) +2. **KeySpace Structure**: +```rust +pub struct KeySpace { + pub name: String, + pub keypairs: HashMap, } -/// Gets the public key bytes. -/// -/// # Returns -/// -/// * `Ok(Vec)` containing the public key bytes. -/// * `Err(CryptoError::KeypairNotInitialized)` if the keypair has not been initialized. -pub fn keypair_pub_key() -> Result, CryptoError> { - KEYPAIR.get() - .ok_or(CryptoError::KeypairNotInitialized) - .map(|kp| kp.verifying_key.to_sec1_bytes().to_vec()) -} - -/// Signs a message. -/// -/// # Arguments -/// -/// * `message` - The message to sign. -/// -/// # Returns -/// -/// * `Ok(Vec)` containing the signature bytes. -/// * `Err(CryptoError::KeypairNotInitialized)` if the keypair has not been initialized. -pub fn keypair_sign(message: &[u8]) -> Result, CryptoError> { - KEYPAIR.get() - .ok_or(CryptoError::KeypairNotInitialized) - .map(|kp| { - let signature: Signature = kp.signing_key.sign(message); - signature.to_bytes().to_vec() - }) -} - -/// Verifies a message signature. -/// -/// # Arguments -/// -/// * `message` - The message that was signed. -/// * `signature_bytes` - The signature to verify. -/// -/// # Returns -/// -/// * `Ok(true)` if the signature is valid. -/// * `Ok(false)` if the signature is invalid. -/// * `Err(CryptoError::KeypairNotInitialized)` if the keypair has not been initialized. -/// * `Err(CryptoError::SignatureFormatError)` if the signature format is invalid. -pub fn keypair_verify(message: &[u8], signature_bytes: &[u8]) -> Result { - let keypair = KEYPAIR.get().ok_or(CryptoError::KeypairNotInitialized)?; - - let signature = Signature::from_bytes(signature_bytes.into()) - .map_err(|_| CryptoError::SignatureFormatError)?; - - match keypair.verifying_key.verify(message, &signature) { - Ok(_) => Ok(true), - Err(_) => Ok(false), // Verification failed, but operation was successful - } +impl KeySpace { + pub fn new(name: &str) -> Self { ... } + pub fn add_keypair(&mut self, name: &str) -> Result<(), CryptoError> { ... } + pub fn get_keypair(&self, name: &str) -> Option<&KeyPair> { ... } + pub fn list_keypairs(&self) -> Vec { ... } + pub fn encrypt(&self, password: &str) -> Result, CryptoError> { ... } + pub fn decrypt(encrypted: &[u8], password: &str) -> Result { ... } } ``` -### Step 3: Create Core Symmetric Module (src/core/symmetric.rs) +3. **Session Management**: ```rust -//! Core implementation of symmetric encryption functionality. - -use chacha20poly1305::{ChaCha20Poly1305, KeyInit, Nonce}; -use chacha20poly1305::aead::Aead; -use rand::{rngs::OsRng, RngCore}; - -use super::error::CryptoError; - -/// The size of the nonce in bytes. -const NONCE_SIZE: usize = 12; - -/// Generates a random 32-byte symmetric key. -/// -/// # Returns -/// -/// A 32-byte array containing the random key. -pub fn generate_symmetric_key() -> [u8; 32] { - let mut key = [0u8; 32]; - OsRng.fill_bytes(&mut key); - key +pub struct Session { + pub current_space: Option, + pub selected_keypair: Option, } -/// Encrypts data using ChaCha20Poly1305 with an internally generated nonce. -/// -/// The nonce is appended to the ciphertext so it can be extracted during decryption. -/// -/// # Arguments -/// -/// * `key` - The encryption key (should be 32 bytes). -/// * `message` - The message to encrypt. -/// -/// # Returns -/// -/// * `Ok(Vec)` containing the ciphertext with the nonce appended. -/// * `Err(CryptoError::InvalidKeyLength)` if the key length is invalid. -/// * `Err(CryptoError::EncryptionFailed)` if encryption fails. -pub fn encrypt_symmetric(key: &[u8], message: &[u8]) -> Result, CryptoError> { - // Create cipher - let cipher = ChaCha20Poly1305::new_from_slice(key) - .map_err(|_| CryptoError::InvalidKeyLength)?; - - // Generate random nonce - let mut nonce_bytes = [0u8; NONCE_SIZE]; - OsRng.fill_bytes(&mut nonce_bytes); - let nonce = Nonce::from_slice(&nonce_bytes); - - // Encrypt message - let ciphertext = cipher.encrypt(nonce, message) - .map_err(|_| CryptoError::EncryptionFailed)?; - - // Append nonce to ciphertext - let mut result = ciphertext; - result.extend_from_slice(&nonce_bytes); - - Ok(result) -} +static SESSION: OnceCell> = OnceCell::new(); -/// Decrypts data using ChaCha20Poly1305, extracting the nonce from the ciphertext. -/// -/// # Arguments -/// -/// * `key` - The decryption key (should be 32 bytes). -/// * `ciphertext_with_nonce` - The ciphertext with the nonce appended. -/// -/// # Returns -/// -/// * `Ok(Vec)` containing the decrypted message. -/// * `Err(CryptoError::InvalidKeyLength)` if the key length is invalid. -/// * `Err(CryptoError::DecryptionFailed)` if decryption fails or the ciphertext is too short. -pub fn decrypt_symmetric(key: &[u8], ciphertext_with_nonce: &[u8]) -> Result, CryptoError> { - // Check if ciphertext is long enough to contain a nonce - if ciphertext_with_nonce.len() <= NONCE_SIZE { - return Err(CryptoError::DecryptionFailed); - } - - // Extract nonce from the end of ciphertext - let ciphertext_len = ciphertext_with_nonce.len() - NONCE_SIZE; - let ciphertext = &ciphertext_with_nonce[0..ciphertext_len]; - let nonce_bytes = &ciphertext_with_nonce[ciphertext_len..]; - - // Create cipher - let cipher = ChaCha20Poly1305::new_from_slice(key) - .map_err(|_| CryptoError::InvalidKeyLength)?; - - let nonce = Nonce::from_slice(nonce_bytes); - - // Decrypt message - cipher.decrypt(nonce, ciphertext) - .map_err(|_| CryptoError::DecryptionFailed) -} +pub fn initialize_session() { ... } +pub fn login(space_name: &str, password: &str) -> Result<(), CryptoError> { ... } +pub fn create_space(space_name: &str, password: &str) -> Result<(), CryptoError> { ... } +pub fn logout() { ... } +pub fn is_logged_in() -> bool { ... } ``` -### Step 4: Create Core Module (src/core/mod.rs) -```rust -//! Core cryptographic functionality. +### Frontend Implementation Details -pub mod error; -pub mod keypair; -pub mod symmetric; +1. **Session Management**: +```javascript +// Session state +let currentSpace = null; +let selectedKeypair = null; +let lastActivity = Date.now(); +let logoutTimer = null; -// Re-export commonly used items -pub use error::CryptoError; -``` - -### Step 5: Create API Keypair Module (src/api/keypair.rs) -```rust -//! Public API for keypair operations. - -use crate::core::keypair; -use crate::core::error::CryptoError; - -/// Initializes a new keypair for signing and verification. -/// -/// # Returns -/// -/// * `Ok(())` if the keypair was initialized successfully. -/// * `Err(CryptoError::KeypairAlreadyInitialized)` if a keypair was already initialized. -pub fn new() -> Result<(), CryptoError> { - keypair::keypair_new() +// Activity tracking +function updateActivity() { + lastActivity = Date.now(); } -/// Gets the public key of the initialized keypair. -/// -/// # Returns -/// -/// * `Ok(Vec)` containing the public key bytes. -/// * `Err(CryptoError::KeypairNotInitialized)` if no keypair has been initialized. -pub fn pub_key() -> Result, CryptoError> { - keypair::keypair_pub_key() -} - -/// Signs a message using the initialized keypair. -/// -/// # Arguments -/// -/// * `message` - The message to sign. -/// -/// # Returns -/// -/// * `Ok(Vec)` containing the signature bytes. -/// * `Err(CryptoError::KeypairNotInitialized)` if no keypair has been initialized. -pub fn sign(message: &[u8]) -> Result, CryptoError> { - keypair::keypair_sign(message) -} - -/// Verifies a signature against a message. -/// -/// # Arguments -/// -/// * `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::KeypairNotInitialized)` if no keypair has been initialized. -/// * `Err(CryptoError::SignatureFormatError)` if the signature format is invalid. -pub fn verify(message: &[u8], signature: &[u8]) -> Result { - keypair::keypair_verify(message, signature) -} -``` - -### Step 6: Create API Symmetric Module (src/api/symmetric.rs) -```rust -//! Public API for symmetric encryption operations. - -use crate::core::symmetric; -use crate::core::error::CryptoError; - -/// Generates a random 32-byte symmetric key. -/// -/// # Returns -/// -/// A 32-byte array containing the random key. -pub fn generate_key() -> [u8; 32] { - symmetric::generate_symmetric_key() -} - -/// Encrypts data using ChaCha20Poly1305. -/// -/// A random nonce is generated internally and appended to the ciphertext. -/// -/// # Arguments -/// -/// * `key` - The encryption key (should be 32 bytes). -/// * `message` - The message to encrypt. -/// -/// # Returns -/// -/// * `Ok(Vec)` containing the ciphertext with the nonce appended. -/// * `Err(CryptoError::InvalidKeyLength)` if the key length is invalid. -/// * `Err(CryptoError::EncryptionFailed)` if encryption fails. -pub fn encrypt(key: &[u8], message: &[u8]) -> Result, CryptoError> { - symmetric::encrypt_symmetric(key, message) -} - -/// Decrypts data using ChaCha20Poly1305. -/// -/// The nonce is extracted from the end of the ciphertext. -/// -/// # Arguments -/// -/// * `key` - The decryption key (should be 32 bytes). -/// * `ciphertext` - The ciphertext with the nonce appended. -/// -/// # Returns -/// -/// * `Ok(Vec)` containing the decrypted message. -/// * `Err(CryptoError::InvalidKeyLength)` if the key length is invalid. -/// * `Err(CryptoError::DecryptionFailed)` if decryption fails or the ciphertext is too short. -pub fn decrypt(key: &[u8], ciphertext: &[u8]) -> Result, CryptoError> { - symmetric::decrypt_symmetric(key, ciphertext) -} -``` - -### Step 7: Create API Module (src/api/mod.rs) -```rust -//! Public API for cryptographic operations. - -pub mod keypair; -pub mod symmetric; - -// Re-export commonly used items -pub use crate::core::error::CryptoError; -``` - -### Step 8: Update lib.rs -```rust -//! WebAssembly module for cryptographic operations. - -use wasm_bindgen::prelude::*; -use web_sys::console; - -// Import modules -mod api; -mod core; - -// Re-export for internal use -use api::keypair; -use api::symmetric; -use core::error::CryptoError; -use core::error::error_to_status_code; - -// This is like the `main` function, except for JavaScript. -#[wasm_bindgen(start)] -pub fn main_js() -> Result<(), JsValue> { - // This provides better error messages in debug mode. - // It's disabled in release mode so it doesn't bloat up the file size. - #[cfg(debug_assertions)] - console_error_panic_hook::set_once(); - - console::log_1(&JsValue::from_str("Crypto module initialized")); - - Ok(()) -} - -// --- WebAssembly Exports --- - -#[wasm_bindgen] -pub fn keypair_new() -> i32 { - match keypair::new() { - Ok(_) => 0, // Success - Err(e) => error_to_status_code(e), - } -} - -#[wasm_bindgen] -pub fn keypair_pub_key() -> Result, JsValue> { - keypair::pub_key() - .map_err(|e| JsValue::from_str(&e.to_string())) -} - -#[wasm_bindgen] -pub fn keypair_sign(message: &[u8]) -> Result, JsValue> { - keypair::sign(message) - .map_err(|e| JsValue::from_str(&e.to_string())) -} - -#[wasm_bindgen] -pub fn keypair_verify(message: &[u8], signature: &[u8]) -> Result { - keypair::verify(message, signature) - .map_err(|e| JsValue::from_str(&e.to_string())) -} - -#[wasm_bindgen] -pub fn generate_symmetric_key() -> Vec { - symmetric::generate_key().to_vec() -} - -#[wasm_bindgen] -pub fn encrypt_symmetric(key: &[u8], message: &[u8]) -> Result, JsValue> { - symmetric::encrypt(key, message) - .map_err(|e| JsValue::from_str(&e.to_string())) -} - -#[wasm_bindgen] -pub fn decrypt_symmetric(key: &[u8], ciphertext: &[u8]) -> Result, JsValue> { - symmetric::decrypt(key, ciphertext) - .map_err(|e| JsValue::from_str(&e.to_string())) -} -``` - -### Step 9: Create Test Files - -#### src/tests/keypair_tests.rs -```rust -//! Tests for keypair functionality. - -#[cfg(test)] -mod tests { - use crate::core::keypair; - - // Helper to ensure keypair is initialized for tests that need it. - fn ensure_keypair_initialized() { - // Use try_init which doesn't panic if already initialized - let _ = keypair::keypair_new(); - assert!(keypair::KEYPAIR.get().is_some(), "KEYPAIR should be initialized"); - } - - #[test] - fn test_keypair_generation_and_retrieval() { - let _ = keypair::keypair_new(); // Ignore error if already initialized by another test - let pub_key = keypair::keypair_pub_key().expect("Should be able to get pub key after init"); - assert!(!pub_key.is_empty(), "Public key should not be empty"); - // Basic check for SEC1 format (0x02, 0x03, or 0x04 prefix) - assert!(pub_key.len() == 33 || pub_key.len() == 65, "Public key length is incorrect"); - assert!(pub_key[0] == 0x02 || pub_key[0] == 0x03 || pub_key[0] == 0x04, "Invalid SEC1 format start byte"); - } - - #[test] - fn test_sign_verify_valid() { - ensure_keypair_initialized(); - let message = b"this is a test message"; - let signature = keypair::keypair_sign(message).expect("Signing failed"); - assert!(!signature.is_empty(), "Signature should not be empty"); - - let is_valid = keypair::keypair_verify(message, &signature).expect("Verification failed"); - assert!(is_valid, "Signature should be valid"); - } - - #[test] - fn test_verify_invalid_signature() { - ensure_keypair_initialized(); - let message = b"another test message"; - let mut invalid_signature = keypair::keypair_sign(message).expect("Signing failed"); - // Tamper with the signature - invalid_signature[0] = invalid_signature[0].wrapping_add(1); - - let is_valid = keypair::keypair_verify(message, &invalid_signature).expect("Verification process failed"); - assert!(!is_valid, "Tampered signature should be invalid"); - } - - #[test] - fn test_verify_wrong_message() { - ensure_keypair_initialized(); - let message = b"original message"; - let wrong_message = b"different message"; - let signature = keypair::keypair_sign(message).expect("Signing failed"); - - let is_valid = keypair::keypair_verify(wrong_message, &signature).expect("Verification process failed"); - assert!(!is_valid, "Signature should be invalid for a different message"); - } -} -``` - -#### src/tests/symmetric_tests.rs -```rust -//! Tests for symmetric encryption functionality. - -#[cfg(test)] -mod tests { - use crate::core::symmetric; - use crate::core::error::CryptoError; - - #[test] - fn test_generate_symmetric_key() { - let key = symmetric::generate_symmetric_key(); - assert_eq!(key.len(), 32, "Symmetric key length should be 32 bytes"); - // Check if it's not all zeros (highly unlikely for random) - assert!(key.iter().any(|&byte| byte != 0), "Key should not be all zeros"); - } - - #[test] - fn test_encrypt_decrypt_symmetric() { - let key = symmetric::generate_symmetric_key(); - let message = b"super secret data"; - - let ciphertext = symmetric::encrypt_symmetric(&key, message) - .expect("Encryption failed"); - - assert_ne!(message, ciphertext.as_slice(), "Ciphertext should be different from message"); - - let decrypted_message = symmetric::decrypt_symmetric(&key, &ciphertext) - .expect("Decryption failed"); - - assert_eq!(message, decrypted_message.as_slice(), "Decrypted message should match original"); - } - - #[test] - fn test_decrypt_wrong_key() { - let key1 = symmetric::generate_symmetric_key(); - let key2 = symmetric::generate_symmetric_key(); // Different key - let message = b"data for key1"; - - let ciphertext = symmetric::encrypt_symmetric(&key1, message) - .expect("Encryption failed"); - - let result = symmetric::decrypt_symmetric(&key2, &ciphertext); - - assert!(result.is_err(), "Decryption should fail with the wrong key"); - assert!(matches!(result.unwrap_err(), CryptoError::DecryptionFailed), "Error should be DecryptionFailed"); - } - - #[test] - fn test_decrypt_tampered_ciphertext() { - let key = symmetric::generate_symmetric_key(); - let message = b"important data"; - - let mut ciphertext = symmetric::encrypt_symmetric(&key, message) - .expect("Encryption failed"); - - // Tamper with ciphertext (e.g., flip a bit) - if !ciphertext.is_empty() { - ciphertext[0] ^= 0x01; - } else { - panic!("Ciphertext is empty, cannot tamper"); +// Auto-logout +function setupAutoLogout() { + logoutTimer = setInterval(() => { + const inactiveTime = Date.now() - lastActivity; + if (inactiveTime > 15 * 60 * 1000) { // 15 minutes + logout(); } + }, 60000); // Check every minute +} - let result = symmetric::decrypt_symmetric(&key, &ciphertext); - - assert!(result.is_err(), "Decryption should fail with tampered ciphertext"); - assert!(matches!(result.unwrap_err(), CryptoError::DecryptionFailed), "Error should be DecryptionFailed"); +// Login/logout functions +async function login(spaceName, password) { ... } +async function createSpace(spaceName, password) { ... } +async function logout() { ... } +``` + +2. **UI Updates**: +```javascript +// Space management +function updateSpacesList() { ... } +function selectSpace(spaceName) { ... } + +// Keypair management +function updateKeypairsList() { ... } +function selectKeypair(keypairName) { ... } +function createKeypair(keypairName) { ... } + +// Update existing functionality +function signMessage() { + // Check if logged in and keypair selected + if (!selectedKeypair) { + alert("Please select a keypair first"); + return; + } + + // Get message and sign with selected keypair + const message = document.getElementById('sign-message').value; + const messageBytes = new TextEncoder().encode(message); + + try { + const signature = keypair_sign_with_selected(messageBytes); + // ...rest of the function + } catch (e) { + // ...error handling } } ``` -### Step 10: Update README.md -```markdown -# Rust WebAssembly Cryptography Module +### LocalStorage Structure -This project provides a WebAssembly module written in Rust that offers cryptographic functionality for web applications. - -## Features - -- **Asymmetric Cryptography** - - ECDSA keypair generation - - Message signing - - Signature verification - -- **Symmetric Cryptography** - - ChaCha20Poly1305 encryption/decryption - - Secure key generation - -## Prerequisites - -Before you begin, ensure you have the following installed: - -- [Rust](https://www.rust-lang.org/tools/install) (1.70.0 or later) -- [wasm-pack](https://rustwasm.github.io/wasm-pack/installer/) (0.10.0 or later) -- A modern web browser that supports WebAssembly - -## Project Structure - -``` -webassembly/ -├── src/ -│ ├── api/ # Public API modules -│ │ ├── keypair.rs # Public keypair API -│ │ ├── mod.rs # API module exports -│ │ └── symmetric.rs # Public symmetric encryption API -│ ├── core/ # Internal implementation modules -│ │ ├── error.rs # Error types and conversions -│ │ ├── keypair.rs # Core keypair implementation -│ │ ├── mod.rs # Core module exports -│ │ └── symmetric.rs # Core symmetric encryption implementation -│ ├── tests/ # Test modules -│ │ ├── keypair_tests.rs # Tests for keypair functionality -│ │ └── symmetric_tests.rs # Tests for symmetric encryption -│ └── lib.rs # Main entry point, exports WASM functions -├── www/ -│ ├── index.html # Example HTML page -│ └── js/ -│ └── index.js # JavaScript code to load and use the WebAssembly module -├── Cargo.toml # Rust package configuration -└── README.md # This file -``` - -## Building the WebAssembly Module - -To build the WebAssembly module, run: - -```bash -wasm-pack build --target web -``` - -This will create a `pkg` directory containing the compiled WebAssembly module and JavaScript bindings. - -## Running the Example - -After building the WebAssembly module, you can run the example using a local web server: - -```bash -cd www -npm install -npm start -``` - -Then open your browser and navigate to http://localhost:8080. - -## API Reference - -### Keypair Operations +The system will use localStorage to persist encrypted key spaces. The structure will be: ```javascript -// Initialize a new keypair -const result = await wasm.keypair_new(); -if (result === 0) { - console.log("Keypair initialized successfully"); +// localStorage key format: "crypto_space_{spaceName}" +// localStorage value: JSON string of encrypted space data + +// Example localStorage entry: +// Key: "crypto_space_personal" +// Value: { +// "name": "personal", +// "encryptedData": "base64encodedencrypteddata...", +// "createdAt": "2023-04-19T12:00:00Z", +// "lastAccessed": "2023-04-19T15:30:00Z" +// } + +// Helper functions for localStorage access +function saveSpaceToStorage(spaceName, encryptedData) { + const storageKey = `crypto_space_${spaceName}`; + const storageData = { + name: spaceName, + encryptedData: btoa(String.fromCharCode.apply(null, new Uint8Array(encryptedData))), + createdAt: new Date().toISOString(), + lastAccessed: new Date().toISOString() + }; + localStorage.setItem(storageKey, JSON.stringify(storageData)); } -// Get the public key -const pubKey = await wasm.keypair_pub_key(); +function getSpaceFromStorage(spaceName) { + const storageKey = `crypto_space_${spaceName}`; + const storageData = JSON.parse(localStorage.getItem(storageKey)); + if (!storageData) return null; + + // Update last accessed time + storageData.lastAccessed = new Date().toISOString(); + localStorage.setItem(storageKey, JSON.stringify(storageData)); + + // Return the encrypted data as Uint8Array + return new Uint8Array( + atob(storageData.encryptedData) + .split('') + .map(c => c.charCodeAt(0)) + ); +} -// Sign a message -const message = new TextEncoder().encode("Hello, world!"); -const signature = await wasm.keypair_sign(message); +function listSpacesFromStorage() { + const spaces = []; + for (let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i); + if (key.startsWith('crypto_space_')) { + const spaceName = key.substring('crypto_space_'.length); + spaces.push(spaceName); + } + } + return spaces; +} -// Verify a signature -const isValid = await wasm.keypair_verify(message, signature); -console.log("Signature valid:", isValid); +function removeSpaceFromStorage(spaceName) { + const storageKey = `crypto_space_${spaceName}`; + localStorage.removeItem(storageKey); +} ``` -### Symmetric Encryption +## UI Mockup -```javascript -// Generate a symmetric key -const key = wasm.generate_symmetric_key(); +``` ++------------------------------------------+ +| Rust WebAssembly Crypto Example | ++------------------------------------------+ -// Encrypt a message -const message = new TextEncoder().encode("Secret message"); -const ciphertext = await wasm.encrypt_symmetric(key, message); ++------------------------------------------+ +| Space Management | +| | +| Password: [________] Space: [________] | +| | +| [Login] [Create Space] [Logout] | +| | +| Current Space: [Dropdown▼] | ++------------------------------------------+ -// Decrypt a message -const decrypted = await wasm.decrypt_symmetric(key, ciphertext); -const decryptedText = new TextDecoder().decode(decrypted); -console.log("Decrypted:", decryptedText); ++------------------------------------------+ +| Keypair Management | +| | +| Keypair Name: [________] | +| | +| [Create Keypair] | +| | +| Selected Keypair: [Dropdown▼] | ++------------------------------------------+ + +... (existing UI sections, updated to work with + the selected keypair) ... ``` -## Security Considerations +## Testing Strategy -- The keypair is stored in memory and is not persisted between page reloads. -- The symmetric encryption uses ChaCha20Poly1305, which provides authenticated encryption. -- The nonce for symmetric encryption is generated randomly and appended to the ciphertext. +1. **Unit Tests**: + - Test KeyPair and KeySpace functionality + - Test encryption/decryption of spaces + - Test session management -## License +2. **Integration Tests**: + - Test the full flow from login to using keypairs + - Test auto-logout functionality + - Test persistence to localStorage -This project is licensed under the MIT License - see the LICENSE file for details. \ No newline at end of file +3. **UI Testing**: + - Test the UI components for managing spaces and keypairs + - Test error handling and user feedback + +## Implementation Timeline + +1. **Phase 1: Backend Implementation** + - Implement the core data structures and functionality + - Update the API layer + - Update the WebAssembly bindings + +2. **Phase 2: Frontend Implementation** + - Implement session management + - Update the UI for space and keypair management + - Update the existing functionality to work with selected keypairs + +3. **Phase 3: Testing and Refinement** + - Test the implementation + - Fix any issues + - Refine the UI based on feedback \ No newline at end of file diff --git a/src/api/keypair.rs b/src/api/keypair.rs index cd9eeed..0cb1f62 100644 --- a/src/api/keypair.rs +++ b/src/api/keypair.rs @@ -1,29 +1,76 @@ //! Public API for keypair operations. use crate::core::keypair; +use crate::core::symmetric; use crate::core::error::CryptoError; -/// Initializes a new keypair for signing and verification. +/// Creates a new key space with the given name. +/// +/// # Arguments +/// +/// * `name` - The name of the key space. /// /// # Returns /// -/// * `Ok(())` if the keypair was initialized successfully. -/// * `Err(CryptoError::KeypairAlreadyInitialized)` if a keypair was already initialized. -pub fn new() -> Result<(), CryptoError> { - keypair::keypair_new() +/// * `Ok(())` if the key space was created successfully. +/// * `Err(CryptoError)` if an error occurred. +pub fn create_space(name: &str) -> Result<(), CryptoError> { + keypair::create_space(name) } -/// Gets the public key of the initialized keypair. +/// Creates a new keypair in the current space. +/// +/// # Arguments +/// +/// * `name` - The name of the keypair. +/// +/// # Returns +/// +/// * `Ok(())` if the keypair was created successfully. +/// * `Err(CryptoError::NoActiveSpace)` if no space is active. +/// * `Err(CryptoError::KeypairAlreadyExists)` if a keypair with this name already exists. +pub fn create_keypair(name: &str) -> Result<(), CryptoError> { + keypair::create_keypair(name) +} + +/// Selects a keypair for use. +/// +/// # Arguments +/// +/// * `name` - The name of the keypair to select. +/// +/// # Returns +/// +/// * `Ok(())` if the keypair was selected successfully. +/// * `Err(CryptoError::NoActiveSpace)` if no space is active. +/// * `Err(CryptoError::KeypairNotFound)` if the keypair was not found. +pub fn select_keypair(name: &str) -> Result<(), CryptoError> { + keypair::select_keypair(name) +} + +/// Lists all keypair names in the current space. +/// +/// # Returns +/// +/// * `Ok(Vec)` containing the keypair names. +/// * `Err(CryptoError::NoActiveSpace)` if no space is active. +pub fn list_keypairs() -> Result, CryptoError> { + keypair::list_keypairs() +} + +/// Gets the public key of the selected keypair. /// /// # Returns /// /// * `Ok(Vec)` containing the public key bytes. -/// * `Err(CryptoError::KeypairNotInitialized)` if no keypair has been initialized. +/// * `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. pub fn pub_key() -> Result, CryptoError> { keypair::keypair_pub_key() } -/// Signs a message using the initialized keypair. +/// Signs a message using the selected keypair. /// /// # Arguments /// @@ -32,7 +79,9 @@ pub fn pub_key() -> Result, CryptoError> { /// # Returns /// /// * `Ok(Vec)` containing the signature bytes. -/// * `Err(CryptoError::KeypairNotInitialized)` if no keypair has been initialized. +/// * `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. pub fn sign(message: &[u8]) -> Result, CryptoError> { keypair::keypair_sign(message) } @@ -48,8 +97,49 @@ pub fn sign(message: &[u8]) -> Result, CryptoError> { /// /// * `Ok(true)` if the signature is valid. /// * `Ok(false)` if the signature is invalid. -/// * `Err(CryptoError::KeypairNotInitialized)` if no keypair has been initialized. +/// * `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::SignatureFormatError)` if the signature format is invalid. pub fn verify(message: &[u8], signature: &[u8]) -> Result { keypair::keypair_verify(message, signature) +} + +/// Encrypts a key space with a password. +/// +/// # Arguments +/// +/// * `password` - The password to encrypt with. +/// +/// # Returns +/// +/// * `Ok(String)` containing the serialized encrypted key space. +/// * `Err(CryptoError::NoActiveSpace)` if no space is active. +/// * `Err(CryptoError)` if encryption fails. +pub fn encrypt_space(password: &str) -> Result { + let space = keypair::get_current_space()?; + let encrypted = symmetric::encrypt_key_space(&space, password)?; + symmetric::serialize_encrypted_space(&encrypted) +} + +/// Decrypts a key space with a password and sets it as the current space. +/// +/// # Arguments +/// +/// * `encrypted_space` - The serialized encrypted key space. +/// * `password` - The password to decrypt with. +/// +/// # Returns +/// +/// * `Ok(())` if the key space was decrypted and set successfully. +/// * `Err(CryptoError)` if decryption fails. +pub fn decrypt_space(encrypted_space: &str, password: &str) -> Result<(), CryptoError> { + let encrypted = symmetric::deserialize_encrypted_space(encrypted_space)?; + let space = symmetric::decrypt_key_space(&encrypted, password)?; + keypair::set_current_space(space) +} + +/// Clears the current session (logout). +pub fn logout() { + keypair::clear_session(); } \ No newline at end of file diff --git a/src/api/symmetric.rs b/src/api/symmetric.rs index ec17670..36856f9 100644 --- a/src/api/symmetric.rs +++ b/src/api/symmetric.rs @@ -12,6 +12,19 @@ pub fn generate_key() -> [u8; 32] { symmetric::generate_symmetric_key() } +/// Derives a 32-byte key from a password. +/// +/// # Arguments +/// +/// * `password` - The password to derive the key from. +/// +/// # Returns +/// +/// A 32-byte array containing the derived key. +pub fn derive_key_from_password(password: &str) -> [u8; 32] { + symmetric::derive_key_from_password(password) +} + /// Encrypts data using ChaCha20Poly1305. /// /// A random nonce is generated internally and appended to the ciphertext. @@ -46,4 +59,40 @@ pub fn encrypt(key: &[u8], message: &[u8]) -> Result, CryptoError> { /// * `Err(CryptoError::DecryptionFailed)` if decryption fails or the ciphertext is too short. pub fn decrypt(key: &[u8], ciphertext: &[u8]) -> Result, CryptoError> { symmetric::decrypt_symmetric(key, ciphertext) +} + +/// Encrypts data using a password. +/// +/// The password is used to derive a key, which is then used to encrypt the data. +/// +/// # Arguments +/// +/// * `password` - The password to encrypt with. +/// * `message` - The message to encrypt. +/// +/// # Returns +/// +/// * `Ok(Vec)` containing the ciphertext. +/// * `Err(CryptoError)` if encryption fails. +pub fn encrypt_with_password(password: &str, message: &[u8]) -> Result, CryptoError> { + let key = symmetric::derive_key_from_password(password); + symmetric::encrypt_symmetric(&key, message) +} + +/// Decrypts data using a password. +/// +/// The password is used to derive a key, which is then used to decrypt the data. +/// +/// # Arguments +/// +/// * `password` - The password to decrypt with. +/// * `ciphertext` - The ciphertext to decrypt. +/// +/// # Returns +/// +/// * `Ok(Vec)` containing the decrypted message. +/// * `Err(CryptoError)` if decryption fails. +pub fn decrypt_with_password(password: &str, ciphertext: &[u8]) -> Result, CryptoError> { + let key = symmetric::derive_key_from_password(password); + symmetric::decrypt_symmetric(&key, ciphertext) } \ No newline at end of file diff --git a/src/core/error.rs b/src/core/error.rs index 6bc37ec..b070230 100644 --- a/src/core/error.rs +++ b/src/core/error.rs @@ -18,6 +18,22 @@ pub enum CryptoError { DecryptionFailed, /// The key length is invalid. InvalidKeyLength, + /// No space is currently active. + NoActiveSpace, + /// No keypair is currently selected. + NoKeypairSelected, + /// The specified keypair was not found. + KeypairNotFound, + /// A keypair with this name already exists. + KeypairAlreadyExists, + /// The space with the given name was not found. + SpaceNotFound, + /// A space with this name already exists. + SpaceAlreadyExists, + /// Invalid password for the space. + InvalidPassword, + /// Error during serialization or deserialization. + SerializationError, /// Other error with description. #[allow(dead_code)] Other(String), @@ -33,6 +49,14 @@ impl std::fmt::Display for CryptoError { CryptoError::EncryptionFailed => write!(f, "Encryption failed"), CryptoError::DecryptionFailed => write!(f, "Decryption failed"), CryptoError::InvalidKeyLength => write!(f, "Invalid key length"), + CryptoError::NoActiveSpace => write!(f, "No active space"), + CryptoError::NoKeypairSelected => write!(f, "No keypair selected"), + CryptoError::KeypairNotFound => write!(f, "Keypair not found"), + CryptoError::KeypairAlreadyExists => write!(f, "Keypair already exists"), + CryptoError::SpaceNotFound => write!(f, "Space not found"), + CryptoError::SpaceAlreadyExists => write!(f, "Space already exists"), + CryptoError::InvalidPassword => write!(f, "Invalid password"), + CryptoError::SerializationError => write!(f, "Serialization error"), CryptoError::Other(s) => write!(f, "Crypto error: {}", s), } } @@ -50,6 +74,14 @@ pub fn error_to_status_code(err: CryptoError) -> i32 { CryptoError::EncryptionFailed => -5, CryptoError::DecryptionFailed => -6, CryptoError::InvalidKeyLength => -7, + CryptoError::NoActiveSpace => -8, + CryptoError::NoKeypairSelected => -9, + CryptoError::KeypairNotFound => -10, + CryptoError::KeypairAlreadyExists => -11, + CryptoError::SpaceNotFound => -12, + CryptoError::SpaceAlreadyExists => -13, + CryptoError::InvalidPassword => -14, + CryptoError::SerializationError => -15, CryptoError::Other(_) => -99, } } \ No newline at end of file diff --git a/src/core/keypair.rs b/src/core/keypair.rs index 3fb5ba1..13d5c34 100644 --- a/src/core/keypair.rs +++ b/src/core/keypair.rs @@ -1,87 +1,312 @@ //! Core implementation of keypair functionality. use k256::ecdsa::{SigningKey, VerifyingKey, signature::{Signer, Verifier}, Signature}; -use once_cell::sync::OnceCell; use rand::rngs::OsRng; +use serde::{Serialize, Deserialize}; +use std::collections::HashMap; +use once_cell::sync::Lazy; +use std::sync::Mutex; use super::error::CryptoError; /// A keypair for signing and verifying messages. -#[derive(Debug)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct KeyPair { + pub name: String, + #[serde(with = "verifying_key_serde")] pub verifying_key: VerifyingKey, + #[serde(with = "signing_key_serde")] pub signing_key: SigningKey, } -/// Global keypair instance. -pub static KEYPAIR: OnceCell = OnceCell::new(); +// Serialization helpers for VerifyingKey +mod verifying_key_serde { + use super::*; + use serde::{Serializer, Deserializer}; + use serde::de::{self, Visitor}; + use std::fmt; -/// Initializes the global keypair. -/// -/// # Returns -/// -/// * `Ok(())` if the keypair was initialized successfully. -/// * `Err(CryptoError::KeypairAlreadyInitialized)` if the keypair was already initialized. -pub fn keypair_new() -> Result<(), CryptoError> { - let signing_key = SigningKey::random(&mut OsRng); - let verifying_key = VerifyingKey::from(&signing_key); - let keypair = KeyPair { verifying_key, signing_key }; - - KEYPAIR.set(keypair).map_err(|_| CryptoError::KeypairAlreadyInitialized) -} - -/// Gets the public key bytes. -/// -/// # Returns -/// -/// * `Ok(Vec)` containing the public key bytes. -/// * `Err(CryptoError::KeypairNotInitialized)` if the keypair has not been initialized. -pub fn keypair_pub_key() -> Result, CryptoError> { - KEYPAIR.get() - .ok_or(CryptoError::KeypairNotInitialized) - .map(|kp| kp.verifying_key.to_sec1_bytes().to_vec()) -} - -/// Signs a message. -/// -/// # Arguments -/// -/// * `message` - The message to sign. -/// -/// # Returns -/// -/// * `Ok(Vec)` containing the signature bytes. -/// * `Err(CryptoError::KeypairNotInitialized)` if the keypair has not been initialized. -pub fn keypair_sign(message: &[u8]) -> Result, CryptoError> { - KEYPAIR.get() - .ok_or(CryptoError::KeypairNotInitialized) - .map(|kp| { - let signature: Signature = kp.signing_key.sign(message); - signature.to_bytes().to_vec() - }) -} - -/// Verifies a message signature. -/// -/// # Arguments -/// -/// * `message` - The message that was signed. -/// * `signature_bytes` - The signature to verify. -/// -/// # Returns -/// -/// * `Ok(true)` if the signature is valid. -/// * `Ok(false)` if the signature is invalid. -/// * `Err(CryptoError::KeypairNotInitialized)` if the keypair has not been initialized. -/// * `Err(CryptoError::SignatureFormatError)` if the signature format is invalid. -pub fn keypair_verify(message: &[u8], signature_bytes: &[u8]) -> Result { - let keypair = KEYPAIR.get().ok_or(CryptoError::KeypairNotInitialized)?; - - let signature = Signature::from_bytes(signature_bytes.into()) - .map_err(|_| CryptoError::SignatureFormatError)?; - - match keypair.verifying_key.verify(message, &signature) { - Ok(_) => Ok(true), - Err(_) => Ok(false), // Verification failed, but operation was successful + pub fn serialize(key: &VerifyingKey, serializer: S) -> Result + where + S: Serializer, + { + let bytes = key.to_sec1_bytes(); + serializer.serialize_bytes(&bytes) } + + struct VerifyingKeyVisitor; + + impl<'de> Visitor<'de> for VerifyingKeyVisitor { + type Value = VerifyingKey; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a byte array representing a verifying key") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: de::Error, + { + VerifyingKey::from_sec1_bytes(v).map_err(|_| E::custom("invalid verifying key")) + } + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_bytes(VerifyingKeyVisitor) + } +} + +// Serialization helpers for SigningKey +mod signing_key_serde { + use super::*; + use serde::{Serializer, Deserializer}; + use serde::de::{self, Visitor}; + use std::fmt; + + pub fn serialize(key: &SigningKey, serializer: S) -> Result + where + S: Serializer, + { + let bytes = key.to_bytes(); + serializer.serialize_bytes(&bytes) + } + + struct SigningKeyVisitor; + + impl<'de> Visitor<'de> for SigningKeyVisitor { + type Value = SigningKey; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a byte array representing a signing key") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: de::Error, + { + SigningKey::from_bytes(v.into()).map_err(|_| E::custom("invalid signing key")) + } + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_bytes(SigningKeyVisitor) + } +} + +impl KeyPair { + /// Creates a new keypair with the given name. + pub fn new(name: &str) -> Self { + let signing_key = SigningKey::random(&mut OsRng); + let verifying_key = VerifyingKey::from(&signing_key); + + KeyPair { + name: name.to_string(), + verifying_key, + signing_key, + } + } + + /// Gets the public key bytes. + pub fn pub_key(&self) -> Vec { + self.verifying_key.to_sec1_bytes().to_vec() + } + + /// Signs a message. + pub fn sign(&self, message: &[u8]) -> Vec { + let signature: Signature = self.signing_key.sign(message); + signature.to_bytes().to_vec() + } + + /// Verifies a message signature. + pub fn verify(&self, message: &[u8], signature_bytes: &[u8]) -> Result { + let signature = Signature::from_bytes(signature_bytes.into()) + .map_err(|_| CryptoError::SignatureFormatError)?; + + match self.verifying_key.verify(message, &signature) { + Ok(_) => Ok(true), + Err(_) => Ok(false), // Verification failed, but operation was successful + } + } +} + +/// A collection of keypairs. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct KeySpace { + pub name: String, + pub keypairs: HashMap, +} + +impl KeySpace { + /// Creates a new key space with the given name. + pub fn new(name: &str) -> Self { + KeySpace { + name: name.to_string(), + keypairs: HashMap::new(), + } + } + + /// Adds a new keypair to the space. + pub fn add_keypair(&mut self, name: &str) -> Result<(), CryptoError> { + if self.keypairs.contains_key(name) { + return Err(CryptoError::KeypairAlreadyExists); + } + + let keypair = KeyPair::new(name); + self.keypairs.insert(name.to_string(), keypair); + Ok(()) + } + + /// Gets a keypair by name. + pub fn get_keypair(&self, name: &str) -> Result<&KeyPair, CryptoError> { + self.keypairs.get(name).ok_or(CryptoError::KeypairNotFound) + } + + /// Lists all keypair names in the space. + pub fn list_keypairs(&self) -> Vec { + self.keypairs.keys().cloned().collect() + } +} + +/// Session state for the current key space and selected keypair. +pub struct Session { + pub current_space: Option, + pub selected_keypair: Option, +} + +impl Default for Session { + fn default() -> Self { + Session { + current_space: None, + selected_keypair: None, + } + } +} + +/// Global session state. +static SESSION: Lazy> = Lazy::new(|| { + Mutex::new(Session::default()) +}); + +/// Creates a new key space with the given name. +pub fn create_space(name: &str) -> Result<(), CryptoError> { + let mut session = SESSION.lock().unwrap(); + + // Create a new space + let space = KeySpace::new(name); + + // Set as current space + session.current_space = Some(space); + session.selected_keypair = None; + + Ok(()) +} + +/// Sets the current key space. +pub fn set_current_space(space: KeySpace) -> Result<(), CryptoError> { + let mut session = SESSION.lock().unwrap(); + session.current_space = Some(space); + session.selected_keypair = None; + Ok(()) +} + +/// Gets the current key space. +pub fn get_current_space() -> Result { + let session = SESSION.lock().unwrap(); + session.current_space.clone().ok_or(CryptoError::NoActiveSpace) +} + +/// Clears the current session (logout). +pub fn clear_session() { + let mut session = SESSION.lock().unwrap(); + session.current_space = None; + session.selected_keypair = None; +} + +/// Creates a new keypair in the current space. +pub fn create_keypair(name: &str) -> Result<(), CryptoError> { + let mut session = SESSION.lock().unwrap(); + + if let Some(ref mut space) = session.current_space { + if space.keypairs.contains_key(name) { + return Err(CryptoError::KeypairAlreadyExists); + } + + let keypair = KeyPair::new(name); + space.keypairs.insert(name.to_string(), keypair); + + // Automatically select the new keypair + session.selected_keypair = Some(name.to_string()); + + Ok(()) + } else { + Err(CryptoError::NoActiveSpace) + } +} + +/// Selects a keypair for use. +pub fn select_keypair(name: &str) -> Result<(), CryptoError> { + let mut session = SESSION.lock().unwrap(); + + if let Some(ref space) = session.current_space { + if !space.keypairs.contains_key(name) { + return Err(CryptoError::KeypairNotFound); + } + + session.selected_keypair = Some(name.to_string()); + Ok(()) + } else { + Err(CryptoError::NoActiveSpace) + } +} + +/// Gets the currently selected keypair. +pub fn get_selected_keypair() -> Result { + let session = SESSION.lock().unwrap(); + + if let Some(ref space) = session.current_space { + if let Some(ref keypair_name) = session.selected_keypair { + if let Some(keypair) = space.keypairs.get(keypair_name) { + return Ok(keypair.clone()); + } + return Err(CryptoError::KeypairNotFound); + } + return Err(CryptoError::NoKeypairSelected); + } + + Err(CryptoError::NoActiveSpace) +} + +/// Lists all keypair names in the current space. +pub fn list_keypairs() -> Result, CryptoError> { + let session = SESSION.lock().unwrap(); + + if let Some(ref space) = session.current_space { + Ok(space.keypairs.keys().cloned().collect()) + } else { + Err(CryptoError::NoActiveSpace) + } +} + +/// Gets the public key of the selected keypair. +pub fn keypair_pub_key() -> Result, CryptoError> { + let keypair = get_selected_keypair()?; + Ok(keypair.pub_key()) +} + +/// Signs a message with the selected keypair. +pub fn keypair_sign(message: &[u8]) -> Result, CryptoError> { + let keypair = get_selected_keypair()?; + Ok(keypair.sign(message)) +} + +/// Verifies a message signature with the selected keypair. +pub fn keypair_verify(message: &[u8], signature_bytes: &[u8]) -> Result { + let keypair = get_selected_keypair()?; + keypair.verify(message, signature_bytes) } \ No newline at end of file diff --git a/src/core/symmetric.rs b/src/core/symmetric.rs index ce366c2..593ca53 100644 --- a/src/core/symmetric.rs +++ b/src/core/symmetric.rs @@ -3,8 +3,11 @@ use chacha20poly1305::{ChaCha20Poly1305, KeyInit, Nonce}; use chacha20poly1305::aead::Aead; use rand::{rngs::OsRng, RngCore}; +use serde::{Serialize, Deserialize}; +use sha2::{Sha256, Digest}; use super::error::CryptoError; +use super::keypair::KeySpace; /// The size of the nonce in bytes. const NONCE_SIZE: usize = 12; @@ -20,6 +23,25 @@ pub fn generate_symmetric_key() -> [u8; 32] { key } +/// Derives a 32-byte key from a password. +/// +/// # Arguments +/// +/// * `password` - The password to derive the key from. +/// +/// # Returns +/// +/// A 32-byte array containing the derived key. +pub fn derive_key_from_password(password: &str) -> [u8; 32] { + let mut hasher = Sha256::new(); + hasher.update(password.as_bytes()); + let result = hasher.finalize(); + + let mut key = [0u8; 32]; + key.copy_from_slice(&result); + key +} + /// Encrypts data using ChaCha20Poly1305 with an internally generated nonce. /// /// The nonce is appended to the ciphertext so it can be extracted during decryption. @@ -87,4 +109,110 @@ pub fn decrypt_symmetric(key: &[u8], ciphertext_with_nonce: &[u8]) -> Result, +} + +/// Encrypts a key space using a password. +/// +/// # Arguments +/// +/// * `space` - The key space to encrypt. +/// * `password` - The password to encrypt with. +/// +/// # Returns +/// +/// * `Ok(EncryptedKeySpace)` containing the encrypted key space. +/// * `Err(CryptoError)` if encryption fails. +pub fn encrypt_key_space(space: &KeySpace, password: &str) -> Result { + // Serialize the key space + let serialized = serde_json::to_vec(space) + .map_err(|_| CryptoError::SerializationError)?; + + // Derive key from password + let key = derive_key_from_password(password); + + // Encrypt the serialized data + let encrypted_data = encrypt_symmetric(&key, &serialized)?; + + // Create metadata + let now = js_sys::Date::now() as u64; + let metadata = EncryptedKeySpaceMetadata { + name: space.name.clone(), + created_at: now, + last_accessed: now, + }; + + Ok(EncryptedKeySpace { + metadata, + encrypted_data, + }) +} + +/// Decrypts a key space using a password. +/// +/// # Arguments +/// +/// * `encrypted_space` - The encrypted key space. +/// * `password` - The password to decrypt with. +/// +/// # Returns +/// +/// * `Ok(KeySpace)` containing the decrypted key space. +/// * `Err(CryptoError)` if decryption fails. +pub fn decrypt_key_space(encrypted_space: &EncryptedKeySpace, password: &str) -> Result { + // Derive key from password + let key = derive_key_from_password(password); + + // Decrypt the data + let decrypted_data = decrypt_symmetric(&key, &encrypted_space.encrypted_data)?; + + // Deserialize the key space + let space: KeySpace = serde_json::from_slice(&decrypted_data) + .map_err(|_| CryptoError::SerializationError)?; + + Ok(space) +} + +/// Serializes an encrypted key space to a JSON string. +/// +/// # Arguments +/// +/// * `encrypted_space` - The encrypted key space to serialize. +/// +/// # Returns +/// +/// * `Ok(String)` containing the serialized encrypted key space. +/// * `Err(CryptoError)` if serialization fails. +pub fn serialize_encrypted_space(encrypted_space: &EncryptedKeySpace) -> Result { + serde_json::to_string(encrypted_space) + .map_err(|_| CryptoError::SerializationError) +} + +/// Deserializes an encrypted key space from a JSON string. +/// +/// # Arguments +/// +/// * `serialized` - The serialized encrypted key space. +/// +/// # Returns +/// +/// * `Ok(EncryptedKeySpace)` containing the deserialized encrypted key space. +/// * `Err(CryptoError)` if deserialization fails. +pub fn deserialize_encrypted_space(serialized: &str) -> Result { + serde_json::from_str(serialized) + .map_err(|_| CryptoError::SerializationError) } \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index bee8ba7..f980b31 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,16 +26,60 @@ pub fn main_js() -> Result<(), JsValue> { Ok(()) } -// --- WebAssembly Exports --- +// --- WebAssembly Exports for Key Space Management --- #[wasm_bindgen] -pub fn keypair_new() -> i32 { - match keypair::new() { +pub fn create_key_space(name: &str) -> i32 { + match keypair::create_space(name) { Ok(_) => 0, // Success Err(e) => error_to_status_code(e), } } +#[wasm_bindgen] +pub fn encrypt_key_space(password: &str) -> Result { + keypair::encrypt_space(password) + .map_err(|e| JsValue::from_str(&e.to_string())) +} + +#[wasm_bindgen] +pub fn decrypt_key_space(encrypted_space: &str, password: &str) -> i32 { + match keypair::decrypt_space(encrypted_space, password) { + Ok(_) => 0, // Success + Err(e) => error_to_status_code(e), + } +} + +#[wasm_bindgen] +pub fn logout() { + keypair::logout(); +} + +// --- WebAssembly Exports for Keypair Management --- + +#[wasm_bindgen] +pub fn create_keypair(name: &str) -> i32 { + match keypair::create_keypair(name) { + Ok(_) => 0, // Success + Err(e) => error_to_status_code(e), + } +} + +#[wasm_bindgen] +pub fn select_keypair(name: &str) -> i32 { + match keypair::select_keypair(name) { + Ok(_) => 0, // Success + Err(e) => error_to_status_code(e), + } +} + +#[wasm_bindgen] +pub fn list_keypairs() -> Result, JsValue> { + keypair::list_keypairs() + .map(|names| names.into_iter().map(JsValue::from).collect()) + .map_err(|e| JsValue::from_str(&e.to_string())) +} + #[wasm_bindgen] pub fn keypair_pub_key() -> Result, JsValue> { keypair::pub_key() @@ -54,11 +98,18 @@ pub fn keypair_verify(message: &[u8], signature: &[u8]) -> Result .map_err(|e| JsValue::from_str(&e.to_string())) } +// --- WebAssembly Exports for Symmetric Encryption --- + #[wasm_bindgen] pub fn generate_symmetric_key() -> Vec { symmetric::generate_key().to_vec() } +#[wasm_bindgen] +pub fn derive_key_from_password(password: &str) -> Vec { + symmetric::derive_key_from_password(password).to_vec() +} + #[wasm_bindgen] pub fn encrypt_symmetric(key: &[u8], message: &[u8]) -> Result, JsValue> { symmetric::encrypt(key, message) @@ -70,3 +121,15 @@ pub fn decrypt_symmetric(key: &[u8], ciphertext: &[u8]) -> Result, JsVal symmetric::decrypt(key, ciphertext) .map_err(|e| JsValue::from_str(&e.to_string())) } + +#[wasm_bindgen] +pub fn encrypt_with_password(password: &str, message: &[u8]) -> Result, JsValue> { + symmetric::encrypt_with_password(password, message) + .map_err(|e| JsValue::from_str(&e.to_string())) +} + +#[wasm_bindgen] +pub fn decrypt_with_password(password: &str, ciphertext: &[u8]) -> Result, JsValue> { + symmetric::decrypt_with_password(password, ciphertext) + .map_err(|e| JsValue::from_str(&e.to_string())) +} diff --git a/www/index.html b/www/index.html index 74fdcfb..489e924 100644 --- a/www/index.html +++ b/www/index.html @@ -30,7 +30,13 @@ cursor: pointer; border-radius: 4px; } - input, textarea { + button.secondary { + background-color: #6c757d; + } + button.danger { + background-color: #dc3545; + } + input, textarea, select { padding: 8px; margin: 5px; border: 1px solid #ddd; @@ -54,20 +60,93 @@ color: #666; font-size: 14px; } + .form-group { + margin-bottom: 15px; + } + .form-group label { + display: block; + margin-bottom: 5px; + font-weight: bold; + } + .status { + padding: 10px; + margin-bottom: 15px; + border-radius: 4px; + } + .status.logged-in { + background-color: #d4edda; + color: #155724; + } + .status.logged-out { + background-color: #f8d7da; + color: #721c24; + } + .hidden { + display: none; + }

Rust WebAssembly Crypto Example

-
-

Keypair Generation

-
- + +
+

Key Space Management

+ +
+ Status: Not logged in
-
Result will appear here
-
+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+
+ + + +
Result will appear here
+ +
+

Keypair Management

+ +
+
+ + + +
+ +
+ + +
+
+ +
Result will appear here
+
+
+

Message Signing

@@ -109,6 +188,32 @@
Decrypted data will appear here
+
+

Password-Based Encryption

+
+
+ + +
+ + +
+
Encrypted data will appear here
+
+ +
+

Password-Based Decryption

+
+
+ + +
+ + +
+
Decrypted data will appear here
+
+ \ No newline at end of file diff --git a/www/js/index.js b/www/js/index.js index 6b136e5..8d4b746 100644 --- a/www/js/index.js +++ b/www/js/index.js @@ -1,12 +1,21 @@ // Import our WebAssembly module import init, { - keypair_new, + create_key_space, + encrypt_key_space, + decrypt_key_space, + logout, + create_keypair, + select_keypair, + list_keypairs, keypair_pub_key, keypair_sign, keypair_verify, generate_symmetric_key, + derive_key_from_password, encrypt_symmetric, - decrypt_symmetric + decrypt_symmetric, + encrypt_with_password, + decrypt_with_password } from '../../pkg/webassembly.js'; // Helper function to convert ArrayBuffer to hex string @@ -25,36 +34,364 @@ function hexToBuffer(hex) { return bytes; } +// Session management +let lastActivity = Date.now(); +let logoutTimer = null; +const AUTO_LOGOUT_TIME = 15 * 60 * 1000; // 15 minutes + +// Update last activity timestamp +function updateActivity() { + lastActivity = Date.now(); +} + +// Check for inactivity and logout if needed +function checkInactivity() { + const inactiveTime = Date.now() - lastActivity; + if (inactiveTime > AUTO_LOGOUT_TIME) { + performLogout(); + alert('You have been logged out due to inactivity.'); + } +} + +// Setup auto-logout timer +function setupAutoLogout() { + logoutTimer = setInterval(checkInactivity, 60000); // Check every minute +} + +// Clear auto-logout timer +function clearAutoLogout() { + if (logoutTimer) { + clearInterval(logoutTimer); + logoutTimer = null; + } +} + +// LocalStorage functions for key spaces +const STORAGE_PREFIX = 'crypto_space_'; + +// Save encrypted space to localStorage +function saveSpaceToStorage(spaceName, encryptedData) { + localStorage.setItem(`${STORAGE_PREFIX}${spaceName}`, encryptedData); +} + +// Get encrypted space from localStorage +function getSpaceFromStorage(spaceName) { + return localStorage.getItem(`${STORAGE_PREFIX}${spaceName}`); +} + +// List all spaces in localStorage +function listSpacesFromStorage() { + const spaces = []; + for (let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i); + if (key.startsWith(STORAGE_PREFIX)) { + spaces.push(key.substring(STORAGE_PREFIX.length)); + } + } + return spaces; +} + +// Remove space from localStorage +function removeSpaceFromStorage(spaceName) { + localStorage.removeItem(`${STORAGE_PREFIX}${spaceName}`); +} + +// Session state +let isLoggedIn = false; +let currentSpace = null; +let selectedKeypair = null; + +// Update UI based on login state +function updateLoginUI() { + const loginForm = document.getElementById('login-form'); + const logoutForm = document.getElementById('logout-form'); + const loginStatus = document.getElementById('login-status'); + const currentSpaceName = document.getElementById('current-space-name'); + + if (isLoggedIn) { + loginForm.classList.add('hidden'); + logoutForm.classList.remove('hidden'); + loginStatus.textContent = 'Status: Logged in'; + loginStatus.className = 'status logged-in'; + currentSpaceName.textContent = currentSpace; + } else { + loginForm.classList.remove('hidden'); + logoutForm.classList.add('hidden'); + loginStatus.textContent = 'Status: Not logged in'; + loginStatus.className = 'status logged-out'; + currentSpaceName.textContent = ''; + } +} + +// Login to a space +async function performLogin() { + const spaceName = document.getElementById('space-name').value.trim(); + const password = document.getElementById('space-password').value; + + if (!spaceName || !password) { + document.getElementById('space-result').textContent = 'Please enter both space name and password'; + return; + } + + try { + // Get encrypted space from localStorage + const encryptedSpace = getSpaceFromStorage(spaceName); + if (!encryptedSpace) { + document.getElementById('space-result').textContent = `Space "${spaceName}" not found`; + return; + } + + // Decrypt the space + const result = decrypt_key_space(encryptedSpace, password); + if (result === 0) { + isLoggedIn = true; + currentSpace = spaceName; + updateLoginUI(); + updateKeypairsList(); + document.getElementById('space-result').textContent = `Successfully logged in to space "${spaceName}"`; + + // Setup auto-logout + updateActivity(); + setupAutoLogout(); + + // Add activity listeners + document.addEventListener('click', updateActivity); + document.addEventListener('keypress', updateActivity); + } else { + document.getElementById('space-result').textContent = `Error logging in: ${result}`; + } + } catch (e) { + document.getElementById('space-result').textContent = `Error: ${e}`; + } +} + +// Create a new space +async function performCreateSpace() { + const spaceName = document.getElementById('space-name').value.trim(); + const password = document.getElementById('space-password').value; + + if (!spaceName || !password) { + document.getElementById('space-result').textContent = 'Please enter both space name and password'; + return; + } + + // Check if space already exists + if (getSpaceFromStorage(spaceName)) { + document.getElementById('space-result').textContent = `Space "${spaceName}" already exists`; + return; + } + + try { + // Create new space + const result = create_key_space(spaceName); + if (result === 0) { + // Encrypt and save the space + const encryptedSpace = encrypt_key_space(password); + saveSpaceToStorage(spaceName, encryptedSpace); + + isLoggedIn = true; + currentSpace = spaceName; + updateLoginUI(); + updateKeypairsList(); + document.getElementById('space-result').textContent = `Successfully created space "${spaceName}"`; + + // Setup auto-logout + updateActivity(); + setupAutoLogout(); + + // Add activity listeners + document.addEventListener('click', updateActivity); + document.addEventListener('keypress', updateActivity); + } else { + document.getElementById('space-result').textContent = `Error creating space: ${result}`; + } + } catch (e) { + document.getElementById('space-result').textContent = `Error: ${e}`; + } +} + +// Logout from current space +function performLogout() { + logout(); + isLoggedIn = false; + currentSpace = null; + selectedKeypair = null; + updateLoginUI(); + clearKeypairsList(); + document.getElementById('space-result').textContent = 'Logged out successfully'; + + // Clear auto-logout + clearAutoLogout(); + + // Remove activity listeners + document.removeEventListener('click', updateActivity); + document.removeEventListener('keypress', updateActivity); +} + +// Update the keypairs dropdown list +function updateKeypairsList() { + const selectKeypair = document.getElementById('select-keypair'); + + // Clear existing options + while (selectKeypair.options.length > 1) { + selectKeypair.remove(1); + } + + try { + // Get keypairs list + const keypairs = list_keypairs(); + + // Add options for each keypair + keypairs.forEach(keypairName => { + const option = document.createElement('option'); + option.value = keypairName; + option.textContent = keypairName; + selectKeypair.appendChild(option); + }); + + // If there's a selected keypair, select it in the dropdown + if (selectedKeypair) { + selectKeypair.value = selectedKeypair; + } + } catch (e) { + console.error('Error updating keypairs list:', e); + } +} + +// Clear the keypairs dropdown list +function clearKeypairsList() { + const selectKeypair = document.getElementById('select-keypair'); + + // Clear existing options + while (selectKeypair.options.length > 1) { + selectKeypair.remove(1); + } + + // Clear selected keypair display + document.getElementById('selected-pubkey-display').textContent = ''; +} + +// Create a new keypair +async function performCreateKeypair() { + if (!isLoggedIn) { + document.getElementById('keypair-management-result').textContent = 'Please login first'; + return; + } + + const keypairName = document.getElementById('keypair-name').value.trim(); + + if (!keypairName) { + document.getElementById('keypair-management-result').textContent = 'Please enter a keypair name'; + return; + } + + try { + // Create new keypair + const result = create_keypair(keypairName); + if (result === 0) { + document.getElementById('keypair-management-result').textContent = `Successfully created keypair "${keypairName}"`; + + // Update keypairs list + updateKeypairsList(); + + // Select the new keypair + selectedKeypair = keypairName; + document.getElementById('select-keypair').value = keypairName; + + // Display public key + displaySelectedKeypairPublicKey(); + + // Save the updated space to localStorage + saveCurrentSpace(); + } else { + document.getElementById('keypair-management-result').textContent = `Error creating keypair: ${result}`; + } + } catch (e) { + document.getElementById('keypair-management-result').textContent = `Error: ${e}`; + } +} + +// Select a keypair +async function performSelectKeypair() { + if (!isLoggedIn) { + document.getElementById('keypair-management-result').textContent = 'Please login first'; + return; + } + + const keypairName = document.getElementById('select-keypair').value; + + if (!keypairName) { + document.getElementById('keypair-management-result').textContent = 'Please select a keypair'; + return; + } + + try { + // Select keypair + const result = select_keypair(keypairName); + if (result === 0) { + selectedKeypair = keypairName; + document.getElementById('keypair-management-result').textContent = `Selected keypair "${keypairName}"`; + + // Display public key + displaySelectedKeypairPublicKey(); + } else { + document.getElementById('keypair-management-result').textContent = `Error selecting keypair: ${result}`; + } + } catch (e) { + document.getElementById('keypair-management-result').textContent = `Error: ${e}`; + } +} + +// Display the public key of the selected keypair +function displaySelectedKeypairPublicKey() { + try { + const pubKey = keypair_pub_key(); + document.getElementById('selected-pubkey-display').textContent = `Public Key: ${bufferToHex(pubKey)}`; + } catch (e) { + document.getElementById('selected-pubkey-display').textContent = `Error getting public key: ${e}`; + } +} + +// Save the current space to localStorage +function saveCurrentSpace() { + if (!isLoggedIn || !currentSpace) return; + + try { + const password = document.getElementById('space-password').value; + const encryptedSpace = encrypt_key_space(password); + saveSpaceToStorage(currentSpace, encryptedSpace); + } catch (e) { + console.error('Error saving space:', e); + } +} + async function run() { // Initialize the WebAssembly module await init(); console.log('WebAssembly crypto module initialized!'); - // Set up the keypair generation example - document.getElementById('keypair-button').addEventListener('click', () => { - try { - const result = keypair_new(); - if (result === 0) { - document.getElementById('keypair-result').textContent = 'Keypair generated successfully!'; - - // Get and display the public key - try { - const pubKey = keypair_pub_key(); - document.getElementById('pubkey-display').textContent = `Public Key: ${bufferToHex(pubKey)}`; - } catch (e) { - document.getElementById('pubkey-display').textContent = `Error getting public key: ${e}`; - } - } else { - document.getElementById('keypair-result').textContent = `Error generating keypair: ${result}`; - } - } catch (e) { - document.getElementById('keypair-result').textContent = `Error: ${e}`; - } - }); + // 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); + + // 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); @@ -73,6 +410,16 @@ async function run() { // 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; @@ -105,7 +452,6 @@ async function run() { const messageBytes = new TextEncoder().encode(message); try { - // New API: encrypt_symmetric only takes key and message const ciphertext = encrypt_symmetric(key, messageBytes); const ciphertextHex = bufferToHex(ciphertext); document.getElementById('encrypt-result').textContent = `Ciphertext: ${ciphertextHex}`; @@ -130,7 +476,6 @@ async function run() { const ciphertext = hexToBuffer(ciphertextHex); try { - // New API: decrypt_symmetric only takes key and ciphertext const plaintext = decrypt_symmetric(key, ciphertext); const decodedText = new TextDecoder().decode(plaintext); document.getElementById('decrypt-result').textContent = `Decrypted: ${decodedText}`; @@ -141,6 +486,53 @@ async function run() { 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}`; + } + }); + + // Initialize UI + updateLoginUI(); } run().catch(console.error); \ No newline at end of file diff --git a/www/package-lock.json b/www/package-lock.json new file mode 100644 index 0000000..ef12e5e --- /dev/null +++ b/www/package-lock.json @@ -0,0 +1,832 @@ +{ + "name": "webassembly-crypto-example", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "webassembly-crypto-example", + "version": "1.0.0", + "dependencies": { + "express": "^4.18.2" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + } + } +} diff --git a/www/package.json b/www/package.json new file mode 100644 index 0000000..5d6b966 --- /dev/null +++ b/www/package.json @@ -0,0 +1,12 @@ +{ + "name": "webassembly-crypto-example", + "version": "1.0.0", + "description": "WebAssembly Crypto Example", + "main": "server.js", + "scripts": { + "start": "node server.js" + }, + "dependencies": { + "express": "^4.18.2" + } +} \ No newline at end of file