...
This commit is contained in:
55
src/core/error.rs
Normal file
55
src/core/error.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
//! 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.
|
||||
#[allow(dead_code)]
|
||||
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.
|
||||
#[allow(dead_code)]
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
87
src/core/keypair.rs
Normal file
87
src/core/keypair.rs
Normal file
@@ -0,0 +1,87 @@
|
||||
//! 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 verifying_key: VerifyingKey,
|
||||
pub signing_key: SigningKey,
|
||||
}
|
||||
|
||||
/// Global keypair instance.
|
||||
pub static KEYPAIR: OnceCell<KeyPair> = 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)
|
||||
}
|
||||
|
||||
/// 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())
|
||||
}
|
||||
|
||||
/// 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);
|
||||
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)?;
|
||||
|
||||
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
|
||||
}
|
||||
}
|
10
src/core/mod.rs
Normal file
10
src/core/mod.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
//! Core cryptographic functionality.
|
||||
|
||||
pub mod error;
|
||||
pub mod keypair;
|
||||
pub mod symmetric;
|
||||
|
||||
// Re-export commonly used items for internal use
|
||||
// (Keeping this even though it's currently unused, as it's good practice for internal modules)
|
||||
#[allow(unused_imports)]
|
||||
pub use error::CryptoError;
|
90
src/core/symmetric.rs
Normal file
90
src/core/symmetric.rs
Normal file
@@ -0,0 +1,90 @@
|
||||
//! 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
|
||||
}
|
||||
|
||||
/// 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<u8>)` 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<Vec<u8>, 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)
|
||||
}
|
||||
|
||||
/// 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<u8>)` 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<Vec<u8>, 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)
|
||||
}
|
Reference in New Issue
Block a user