This commit is contained in:
despiegk 2025-04-19 19:16:58 +02:00
parent 8d707e61a2
commit 66555fcb0d
13 changed files with 2374 additions and 794 deletions

1
.gitignore vendored
View File

@ -34,3 +34,4 @@ yarn-error.log
.env.development.local
.env.test.local
.env.production.local
node_modules

View File

@ -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"

File diff suppressed because it is too large Load Diff

View File

@ -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<String>)` containing the keypair names.
/// * `Err(CryptoError::NoActiveSpace)` if no space is active.
pub fn list_keypairs() -> Result<Vec<String>, CryptoError> {
keypair::list_keypairs()
}
/// Gets the public key of the selected keypair.
///
/// # Returns
///
/// * `Ok(Vec<u8>)` 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<Vec<u8>, 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<Vec<u8>, CryptoError> {
/// # Returns
///
/// * `Ok(Vec<u8>)` 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<Vec<u8>, CryptoError> {
keypair::keypair_sign(message)
}
@ -48,8 +97,49 @@ pub fn sign(message: &[u8]) -> Result<Vec<u8>, 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<bool, CryptoError> {
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<String, CryptoError> {
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();
}

View File

@ -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.
@ -47,3 +60,39 @@ pub fn encrypt(key: &[u8], message: &[u8]) -> Result<Vec<u8>, CryptoError> {
pub fn decrypt(key: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>, 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<u8>)` containing the ciphertext.
/// * `Err(CryptoError)` if encryption fails.
pub fn encrypt_with_password(password: &str, message: &[u8]) -> Result<Vec<u8>, 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<u8>)` containing the decrypted message.
/// * `Err(CryptoError)` if decryption fails.
pub fn decrypt_with_password(password: &str, ciphertext: &[u8]) -> Result<Vec<u8>, CryptoError> {
let key = symmetric::derive_key_from_password(password);
symmetric::decrypt_symmetric(&key, ciphertext)
}

View File

@ -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,
}
}

View File

@ -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<KeyPair> = 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> {
pub fn serialize<S>(key: &VerifyingKey, serializer: S) -> Result<S::Ok, S::Error>
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<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: de::Error,
{
VerifyingKey::from_sec1_bytes(v).map_err(|_| E::custom("invalid verifying key"))
}
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<VerifyingKey, D::Error>
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<S>(key: &SigningKey, serializer: S) -> Result<S::Ok, S::Error>
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<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: de::Error,
{
SigningKey::from_bytes(v.into()).map_err(|_| E::custom("invalid signing key"))
}
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<SigningKey, D::Error>
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);
let keypair = KeyPair { verifying_key, signing_key };
KEYPAIR.set(keypair).map_err(|_| CryptoError::KeypairAlreadyInitialized)
KeyPair {
name: name.to_string(),
verifying_key,
signing_key,
}
}
/// Gets the public key bytes.
///
/// # Returns
///
/// * `Ok(Vec<u8>)` containing the public key bytes.
/// * `Err(CryptoError::KeypairNotInitialized)` if the keypair has not been initialized.
pub fn keypair_pub_key() -> Result<Vec<u8>, CryptoError> {
KEYPAIR.get()
.ok_or(CryptoError::KeypairNotInitialized)
.map(|kp| kp.verifying_key.to_sec1_bytes().to_vec())
pub fn pub_key(&self) -> Vec<u8> {
self.verifying_key.to_sec1_bytes().to_vec()
}
/// Signs a message.
///
/// # Arguments
///
/// * `message` - The message to sign.
///
/// # Returns
///
/// * `Ok(Vec<u8>)` containing the signature bytes.
/// * `Err(CryptoError::KeypairNotInitialized)` if the keypair has not been initialized.
pub fn keypair_sign(message: &[u8]) -> Result<Vec<u8>, CryptoError> {
KEYPAIR.get()
.ok_or(CryptoError::KeypairNotInitialized)
.map(|kp| {
let signature: Signature = kp.signing_key.sign(message);
pub fn sign(&self, message: &[u8]) -> Vec<u8> {
let signature: Signature = self.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<bool, CryptoError> {
let keypair = KEYPAIR.get().ok_or(CryptoError::KeypairNotInitialized)?;
pub fn verify(&self, message: &[u8], signature_bytes: &[u8]) -> Result<bool, CryptoError> {
let signature = Signature::from_bytes(signature_bytes.into())
.map_err(|_| CryptoError::SignatureFormatError)?;
match keypair.verifying_key.verify(message, &signature) {
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<String, KeyPair>,
}
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<String> {
self.keypairs.keys().cloned().collect()
}
}
/// Session state for the current key space and selected keypair.
pub struct Session {
pub current_space: Option<KeySpace>,
pub selected_keypair: Option<String>,
}
impl Default for Session {
fn default() -> Self {
Session {
current_space: None,
selected_keypair: None,
}
}
}
/// Global session state.
static SESSION: Lazy<Mutex<Session>> = 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<KeySpace, CryptoError> {
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<KeyPair, CryptoError> {
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<Vec<String>, 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<Vec<u8>, CryptoError> {
let keypair = get_selected_keypair()?;
Ok(keypair.pub_key())
}
/// Signs a message with the selected keypair.
pub fn keypair_sign(message: &[u8]) -> Result<Vec<u8>, 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<bool, CryptoError> {
let keypair = get_selected_keypair()?;
keypair.verify(message, signature_bytes)
}

View File

@ -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.
@ -88,3 +110,109 @@ pub fn decrypt_symmetric(key: &[u8], ciphertext_with_nonce: &[u8]) -> Result<Vec
cipher.decrypt(nonce, ciphertext)
.map_err(|_| CryptoError::DecryptionFailed)
}
/// Metadata for an encrypted key space.
#[derive(Serialize, Deserialize)]
pub struct EncryptedKeySpaceMetadata {
pub name: String,
pub created_at: u64,
pub last_accessed: u64,
}
/// An encrypted key space with metadata.
#[derive(Serialize, Deserialize)]
pub struct EncryptedKeySpace {
pub metadata: EncryptedKeySpaceMetadata,
pub encrypted_data: Vec<u8>,
}
/// 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<EncryptedKeySpace, CryptoError> {
// 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<KeySpace, CryptoError> {
// 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<String, CryptoError> {
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<EncryptedKeySpace, CryptoError> {
serde_json::from_str(serialized)
.map_err(|_| CryptoError::SerializationError)
}

View File

@ -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<String, JsValue> {
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<Vec<JsValue>, 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<Vec<u8>, JsValue> {
keypair::pub_key()
@ -54,11 +98,18 @@ pub fn keypair_verify(message: &[u8], signature: &[u8]) -> Result<bool, JsValue>
.map_err(|e| JsValue::from_str(&e.to_string()))
}
// --- WebAssembly Exports for Symmetric Encryption ---
#[wasm_bindgen]
pub fn generate_symmetric_key() -> Vec<u8> {
symmetric::generate_key().to_vec()
}
#[wasm_bindgen]
pub fn derive_key_from_password(password: &str) -> Vec<u8> {
symmetric::derive_key_from_password(password).to_vec()
}
#[wasm_bindgen]
pub fn encrypt_symmetric(key: &[u8], message: &[u8]) -> Result<Vec<u8>, JsValue> {
symmetric::encrypt(key, message)
@ -70,3 +121,15 @@ pub fn decrypt_symmetric(key: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>, 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<Vec<u8>, 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<Vec<u8>, JsValue> {
symmetric::decrypt_with_password(password, ciphertext)
.map_err(|e| JsValue::from_str(&e.to_string()))
}

View File

@ -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,18 +60,91 @@
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;
}
</style>
</head>
<body>
<h1>Rust WebAssembly Crypto Example</h1>
<div class="container">
<h2>Keypair Generation</h2>
<div>
<button id="keypair-button">Generate Keypair</button>
<!-- Login/Space Management Section -->
<div class="container" id="login-container">
<h2>Key Space Management</h2>
<div id="login-status" class="status logged-out">
Status: Not logged in
</div>
<div class="result" id="keypair-result">Result will appear here</div>
<div class="key-display" id="pubkey-display"></div>
<div id="login-form">
<div class="form-group">
<label for="space-name">Space Name:</label>
<input type="text" id="space-name" placeholder="Enter space name" />
</div>
<div class="form-group">
<label for="space-password">Password:</label>
<input type="password" id="space-password" placeholder="Enter password" />
</div>
<div>
<button id="login-button">Login</button>
<button id="create-space-button">Create New Space</button>
</div>
</div>
<div id="logout-form" class="hidden">
<div class="form-group">
<label>Current Space: <span id="current-space-name"></span></label>
</div>
<button id="logout-button" class="danger">Logout</button>
</div>
<div class="result" id="space-result">Result will appear here</div>
</div>
<!-- Keypair Management Section -->
<div class="container" id="keypair-management-container">
<h2>Keypair Management</h2>
<div id="keypair-form">
<div class="form-group">
<label for="keypair-name">Keypair Name:</label>
<input type="text" id="keypair-name" placeholder="Enter keypair name" />
<button id="create-keypair-button">Create Keypair</button>
</div>
<div class="form-group">
<label for="select-keypair">Select Keypair:</label>
<select id="select-keypair">
<option value="">-- Select a keypair --</option>
</select>
</div>
</div>
<div class="result" id="keypair-management-result">Result will appear here</div>
<div class="key-display" id="selected-pubkey-display"></div>
</div>
<div class="container">
@ -109,6 +188,32 @@
<div class="result" id="decrypt-result">Decrypted data will appear here</div>
</div>
<div class="container">
<h2>Password-Based Encryption</h2>
<div>
<div class="form-group">
<label for="password-encrypt-password">Password:</label>
<input type="password" id="password-encrypt-password" placeholder="Enter password" />
</div>
<textarea id="password-encrypt-message" placeholder="Enter message to encrypt" rows="3">This message will be encrypted with a password</textarea>
<button id="password-encrypt-button">Encrypt with Password</button>
</div>
<div class="result" id="password-encrypt-result">Encrypted data will appear here</div>
</div>
<div class="container">
<h2>Password-Based Decryption</h2>
<div>
<div class="form-group">
<label for="password-decrypt-password">Password:</label>
<input type="password" id="password-decrypt-password" placeholder="Enter password" />
</div>
<textarea id="password-decrypt-ciphertext" placeholder="Enter ciphertext (hex)" rows="3"></textarea>
<button id="password-decrypt-button">Decrypt with Password</button>
</div>
<div class="result" id="password-decrypt-result">Decrypted data will appear here</div>
</div>
<script type="module" src="./js/index.js"></script>
</body>
</html>

View File

@ -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!';
// 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);
// 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 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);

832
www/package-lock.json generated Normal file
View File

@ -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"
}
}
}
}

12
www/package.json Normal file
View File

@ -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"
}
}