wasm
This commit is contained in:
parent
298253d6fa
commit
29427d301b
6 changed files with 398 additions and 3 deletions
|
|
@ -261,3 +261,218 @@ pub fn wasm_derive_encryption_key(
|
|||
.map(|key| key.to_vec())
|
||||
.map_err(|e| js_error(&format!("derive_encryption_key failed: {}", e)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use wasm_bindgen_test::*;
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Keyring
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn keyring_from_ed25519_roundtrip() {
|
||||
let sk = vec![0xabu8; 32];
|
||||
let pk = vec![0x42u8; 32];
|
||||
let bytes = keyring_from_ed25519(&sk, &pk).expect("keyring_from_ed25519 failed");
|
||||
|
||||
let restored = WasmKeyring::from_bytes(&bytes).expect("from_bytes failed");
|
||||
let bundle = restored.public_key_bundle();
|
||||
assert_eq!(bundle.sig_cl_public_key(), pk);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn keyring_from_ed25519_wrong_key_length() {
|
||||
let short = vec![0u8; 16];
|
||||
let ok = vec![0u8; 32];
|
||||
assert!(keyring_from_ed25519(&short, &ok).is_err());
|
||||
assert!(keyring_from_ed25519(&ok, &short).is_err());
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// PublicKeyBundle
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn public_key_bundle_roundtrip() {
|
||||
let pk = vec![0x99u8; 32];
|
||||
let bundle = WasmPublicKeyBundle {
|
||||
inner: PublicKeyBundle {
|
||||
kem_public_key: KemPublicKey::new(vec![1, 2, 3]),
|
||||
sig_cl_public_key: SignaturePublicKey::new(pk.clone()),
|
||||
sig_pq_public_key: SignaturePqPublicKey::new(vec![4, 5, 6]),
|
||||
},
|
||||
};
|
||||
|
||||
let bytes = bundle.to_bytes();
|
||||
let restored = WasmPublicKeyBundle::from_bytes(&bytes).expect("from_bytes failed");
|
||||
assert_eq!(restored.sig_cl_public_key(), pk);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// ChaCha20-Poly1305
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn chacha20_encrypt_decrypt_roundtrip() {
|
||||
let key = vec![0x42u8; 32];
|
||||
let cipher = WasmChaCha20Poly1305::new(key).expect("new failed");
|
||||
|
||||
let plaintext = b"hello wasm crypto";
|
||||
let aad = b"test-aad";
|
||||
|
||||
let encrypted = cipher.encrypt(plaintext, aad).expect("encrypt failed");
|
||||
let decrypted = cipher.decrypt(&encrypted, aad).expect("decrypt failed");
|
||||
|
||||
assert_eq!(decrypted, plaintext);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn chacha20_wrong_key_length() {
|
||||
assert!(WasmChaCha20Poly1305::new(vec![0u8; 16]).is_err());
|
||||
assert!(WasmChaCha20Poly1305::new(vec![0u8; 31]).is_err());
|
||||
assert!(WasmChaCha20Poly1305::new(vec![0u8; 33]).is_err());
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn chacha20_decrypt_wrong_key_fails() {
|
||||
let key1 = vec![0x42u8; 32];
|
||||
let key2 = vec![0x43u8; 32];
|
||||
let cipher1 = WasmChaCha20Poly1305::new(key1).expect("new failed");
|
||||
let cipher2 = WasmChaCha20Poly1305::new(key2).expect("new failed");
|
||||
|
||||
let encrypted = cipher1.encrypt(b"secret", b"aad").expect("encrypt failed");
|
||||
let result = cipher2.decrypt(&encrypted, b"aad");
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Ed25519
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn ed25519_sign_verify() {
|
||||
let (_signer, sk, _pk) = Ed25519Signer::generate();
|
||||
|
||||
let signer = WasmEd25519Signer::new(sk.as_bytes().to_vec()).expect("new failed");
|
||||
let message = b"test message for ed25519";
|
||||
|
||||
let signature = signer.sign(message).expect("sign failed");
|
||||
assert!(!signature.is_empty());
|
||||
|
||||
signer.verify(message, &signature).expect("verify failed");
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn ed25519_sign_wrong_message_fails_verify() {
|
||||
let (_signer, sk, _pk) = Ed25519Signer::generate();
|
||||
|
||||
let signer = WasmEd25519Signer::new(sk.as_bytes().to_vec()).expect("new failed");
|
||||
let signature = signer.sign(b"message A").expect("sign failed");
|
||||
|
||||
let result = signer.verify(b"message B", &signature);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn ed25519_generate_returns_valid() {
|
||||
let result = ed25519_generate().expect("generate failed");
|
||||
|
||||
let has_signer = js_sys::Reflect::has(&result, &"signer".into()).unwrap_or(false);
|
||||
let has_sk = js_sys::Reflect::has(&result, &"secretKey".into()).unwrap_or(false);
|
||||
let has_pk = js_sys::Reflect::has(&result, &"publicKey".into()).unwrap_or(false);
|
||||
|
||||
assert!(has_signer);
|
||||
assert!(has_sk);
|
||||
assert!(has_pk);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn ed25519_verify_standalone() {
|
||||
let (_signer, sk, pk) = Ed25519Signer::generate();
|
||||
let signer = WasmEd25519Signer::new(sk.as_bytes().to_vec()).expect("new failed");
|
||||
let msg = b"standalone verify test";
|
||||
let sig = signer.sign(msg).expect("sign failed");
|
||||
|
||||
ed25519_verify(pk.as_bytes().to_vec(), msg, &sig).expect("verify failed");
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn ed25519_verify_bad_signature_fails() {
|
||||
let pk = vec![0x42u8; 32];
|
||||
let msg = b"test";
|
||||
let bad_sig = vec![0x00u8; 64];
|
||||
let result = ed25519_verify(pk, msg, &bad_sig);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Hashing
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn sha256_empty() {
|
||||
let result = wasm_sha256(b"");
|
||||
// SHA-256 of empty string
|
||||
let expected = hex::decode("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")
|
||||
.expect("hex decode");
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn sha256_hello() {
|
||||
let result = wasm_sha256(b"hello");
|
||||
let expected = hex::decode("2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824")
|
||||
.expect("hex decode");
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn sha256_double() {
|
||||
let single = wasm_sha256(b"test");
|
||||
let double = wasm_sha256_double(b"test");
|
||||
let expected = wasm_sha256(&single);
|
||||
assert_eq!(double, expected);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// HKDF / KDF
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn hkdf_expand_produces_correct_length() {
|
||||
let result = wasm_hkdf_expand(b"ikm", b"salt", b"info", 32)
|
||||
.expect("hkdf_expand failed");
|
||||
assert_eq!(result.len(), 32);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn hkdf_expand_different_info() {
|
||||
let r1 = wasm_hkdf_expand(b"ikm", b"salt", b"info1", 16).expect("hkdf failed");
|
||||
let r2 = wasm_hkdf_expand(b"ikm", b"salt", b"info2", 16).expect("hkdf failed");
|
||||
assert_ne!(r1, r2);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn derive_encryption_key_roundtrip() {
|
||||
let key = wasm_derive_encryption_key(b"password", b"salt", b"context")
|
||||
.expect("derive failed");
|
||||
assert_eq!(key.len(), 32);
|
||||
|
||||
// Deterministic: same inputs = same key
|
||||
let key2 = wasm_derive_encryption_key(b"password", b"salt", b"context")
|
||||
.expect("derive failed");
|
||||
assert_eq!(key, key2);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn derive_encryption_key_different_inputs_different_key() {
|
||||
let key = wasm_derive_encryption_key(b"pass1", b"salt", b"context")
|
||||
.expect("derive failed");
|
||||
let key2 = wasm_derive_encryption_key(b"pass2", b"salt", b"context")
|
||||
.expect("derive failed");
|
||||
assert_ne!(key, key2);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,3 +15,48 @@ pub fn from_communication_error(e: mtp_common::CommunicationError) -> JsValue {
|
|||
pub fn from_crypto_error(e: mtp_crypto::CryptoError) -> JsValue {
|
||||
js_error(e.to_string())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use wasm_bindgen_test::*;
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn js_error_contains_message() {
|
||||
let err = js_error("test error message");
|
||||
assert_eq!(err.as_string(), Some("test error message".to_string()));
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn js_error_from_string() {
|
||||
let err = js_error(String::from("owned string error"));
|
||||
assert_eq!(err.as_string(), Some("owned string error".to_string()));
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn from_codec_error_invalid_encoding() {
|
||||
let err = from_codec_error(mtp_common::CodecError::InvalidEncoding);
|
||||
assert!(err.as_string().unwrap().contains("Invalid encoding"));
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn from_codec_error_unknown_version() {
|
||||
let err = from_codec_error(mtp_common::CodecError::UnknownVersion);
|
||||
assert!(err.as_string().unwrap().contains("Unknown version"));
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn from_communication_error_renders() {
|
||||
use mtp_common::CommunicationError;
|
||||
let err = from_communication_error(CommunicationError::ConnectionLost);
|
||||
assert!(err.as_string().unwrap().contains("Connection terminated"));
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn from_crypto_error_renders() {
|
||||
use mtp_crypto::CryptoError;
|
||||
let err = from_crypto_error(CryptoError::InvalidKeyLength);
|
||||
assert!(err.as_string().unwrap().contains("invalid key length"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
pub mod error;
|
||||
pub mod transport;
|
||||
pub mod client;
|
||||
pub mod crypto;
|
||||
pub mod error;
|
||||
pub mod message;
|
||||
pub mod transport;
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[cfg(not(test))]
|
||||
#[wasm_bindgen(start)]
|
||||
pub fn main() {
|
||||
console_error_panic_hook::set_once();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use mtp_codec::{
|
||||
CommunicationType, CommunicationValue, DataType, DataTypeId, DataValue,
|
||||
CommunicationType, CommunicationTypeId, CommunicationValue, DataType, DataTypeId, DataValue,
|
||||
};
|
||||
use mtp_crypto::{
|
||||
ChaCha20Poly1305, Ed25519Signer, Keyring, SigAlgorithm,
|
||||
|
|
@ -133,3 +133,130 @@ pub fn parse_auth_response(response: &[u8]) -> Result<JsValue, JsValue> {
|
|||
|
||||
Ok(obj.into())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use wasm_bindgen_test::*;
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn build_ping_frame_roundtrip() {
|
||||
let bytes = build_ping_frame(42, "test-ping", 1234567890, &[]);
|
||||
let cv = CommunicationValue::from_bytes(&bytes).expect("decode failed");
|
||||
|
||||
assert_eq!(cv.get_type(), CommunicationTypeId(19)); // Ping
|
||||
assert_eq!(cv.get_sender(), 42);
|
||||
assert_eq!(cv.get_data(DataTypeId(4)), &DataValue::Str("test-ping".into()));
|
||||
assert_eq!(cv.get_data(DataTypeId(5)), &DataValue::UnsignedNumber(1234567890));
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn build_ping_frame_with_data() {
|
||||
let payload = b"attachment-data";
|
||||
let bytes = build_ping_frame(99, "with-data", 555, payload);
|
||||
let cv = CommunicationValue::from_bytes(&bytes).expect("decode failed");
|
||||
|
||||
assert_eq!(cv.get_type(), CommunicationTypeId(19));
|
||||
assert_eq!(cv.get_sender(), 99);
|
||||
assert_eq!(cv.get_data(DataTypeId(4)), &DataValue::Str("with-data".into()));
|
||||
assert_eq!(cv.get_data(DataTypeId(5)), &DataValue::UnsignedNumber(555));
|
||||
assert_eq!(cv.get_data(DataTypeId(6)), &DataValue::Bytes(payload.to_vec()));
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn build_ping_frame_client_id_zero() {
|
||||
let bytes = build_ping_frame(0, "zero-id", 0, &[]);
|
||||
let cv = CommunicationValue::from_bytes(&bytes).expect("decode failed");
|
||||
assert_eq!(cv.get_sender(), 0);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn build_demo_message_roundtrip() {
|
||||
let (_signer, sk, pk) = Ed25519Signer::generate();
|
||||
let keyring = Keyring::new(
|
||||
mtp_crypto::KemPublicKey::new(vec![]),
|
||||
mtp_crypto::KemPrivateKey::new(vec![]),
|
||||
mtp_crypto::SignaturePqPublicKey::new(vec![]),
|
||||
mtp_crypto::SignaturePqPrivateKey::new(vec![]),
|
||||
pk,
|
||||
sk,
|
||||
);
|
||||
let keyring_bytes = keyring.to_bytes();
|
||||
|
||||
let result = build_demo_message(7, &keyring_bytes);
|
||||
assert!(result.is_ok());
|
||||
|
||||
let bytes = result.unwrap();
|
||||
let cv = CommunicationValue::from_bytes(&bytes).expect("decode failed");
|
||||
|
||||
assert_eq!(cv.get_type(), CommunicationTypeId(19)); // Ping
|
||||
assert_eq!(cv.get_sender(), 7);
|
||||
assert_eq!(cv.get_data(DataTypeId(4)), &DataValue::Str("MTP WASM Demo".into()));
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn build_demo_message_invalid_keyring() {
|
||||
let result = build_demo_message(1, b"not-a-valid-keyring");
|
||||
assert!(result.is_err());
|
||||
let err = result.unwrap_err();
|
||||
assert!(err.as_string().unwrap().contains("invalid keyring"));
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn parse_auth_response_success() {
|
||||
let resp = CommunicationValue::new(CommunicationType::IdentificationResponse)
|
||||
.add_typed_default(DataType::Connected, DataValue::BoolTrue)
|
||||
.add_typed_default(DataType::ClientNonce, DataValue::UnsignedNumber(999))
|
||||
.add_typed_default(DataType::Id, DataValue::UnsignedNumber(42))
|
||||
.add_typed_default(DataType::Timestamp, DataValue::UnsignedNumber(12345))
|
||||
.to_bytes();
|
||||
|
||||
let result = parse_auth_response(&resp).expect("parse failed");
|
||||
|
||||
let connected = js_sys::Reflect::get(&result, &"connected".into())
|
||||
.ok().and_then(|v| v.as_bool());
|
||||
assert_eq!(connected, Some(true));
|
||||
|
||||
let id = js_sys::Reflect::get(&result, &"assignedId".into())
|
||||
.ok().and_then(|v| v.as_f64());
|
||||
assert_eq!(id, Some(42.0));
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn parse_auth_response_rejected() {
|
||||
let resp = CommunicationValue::new(CommunicationType::IdentificationResponse)
|
||||
.add_typed_default(DataType::Connected, DataValue::BoolFalse)
|
||||
.to_bytes();
|
||||
|
||||
let result = parse_auth_response(&resp).expect("parse failed");
|
||||
|
||||
let connected = js_sys::Reflect::get(&result, &"connected".into())
|
||||
.ok().and_then(|v| v.as_bool());
|
||||
assert_eq!(connected, Some(false));
|
||||
|
||||
// rejected should have no assignedId
|
||||
let has_id = js_sys::Reflect::has(&result, &"assignedId".into()).unwrap_or(false);
|
||||
assert!(!has_id);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn parse_auth_response_with_signature() {
|
||||
let sig_bytes = vec![0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe];
|
||||
let resp = CommunicationValue::new(CommunicationType::IdentificationResponse)
|
||||
.add_typed_default(DataType::Connected, DataValue::BoolTrue)
|
||||
.add_typed_default(DataType::Signature, DataValue::Bytes(sig_bytes.clone()))
|
||||
.to_bytes();
|
||||
|
||||
let result = parse_auth_response(&resp).expect("parse failed");
|
||||
|
||||
let has_sig = js_sys::Reflect::has(&result, &"signature".into()).unwrap_or(false);
|
||||
assert!(has_sig);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn parse_auth_response_invalid_frame() {
|
||||
let result = parse_auth_response(b"garbage-data");
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue