more tweaks

This commit is contained in:
Cassandra Heart 2024-12-30 19:15:35 -06:00
parent a9666a9058
commit 5ccd96e47b
No known key found for this signature in database
GPG Key ID: 6352152859385958
4 changed files with 573 additions and 6 deletions

64
Cargo.lock generated
View File

@ -231,12 +231,22 @@ version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
dependencies = [
"block-padding",
"block-padding 0.1.5",
"byte-tools",
"byteorder",
"generic-array 0.12.4",
]
[[package]]
name = "block-buffer"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
dependencies = [
"block-padding 0.2.1",
"generic-array 0.14.7",
]
[[package]]
name = "block-buffer"
version = "0.10.4"
@ -255,6 +265,12 @@ dependencies = [
"byte-tools",
]
[[package]]
name = "block-padding"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae"
[[package]]
name = "bls48581"
version = "0.1.0"
@ -358,10 +374,17 @@ dependencies = [
name = "channelwasm"
version = "0.1.0"
dependencies = [
"aes-gcm",
"base64",
"channel",
"ed448-goldilocks-plus",
"ed448-rust",
"getrandom",
"hkdf",
"rand",
"serde",
"serde_json",
"sha2 0.10.8",
"wasm-bindgen",
]
@ -664,6 +687,15 @@ dependencies = [
"generic-array 0.12.4",
]
[[package]]
name = "digest"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
dependencies = [
"generic-array 0.14.7",
]
[[package]]
name = "digest"
version = "0.10.7"
@ -685,11 +717,27 @@ dependencies = [
"hex 0.4.3",
"rand_core",
"serde",
"sha3",
"sha3 0.10.8",
"subtle",
"zeroize",
]
[[package]]
name = "ed448-rust"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5428b9b8138c483798ceb6a21004de0f24e3a2311195f13829ce1d059037d703"
dependencies = [
"lazy_static",
"num-bigint",
"num-integer",
"num-traits",
"opaque-debug 0.3.1",
"rand_core",
"sha3 0.9.1",
"subtle",
]
[[package]]
name = "either"
version = "1.12.0"
@ -1464,6 +1512,18 @@ dependencies = [
"digest 0.10.7",
]
[[package]]
name = "sha3"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809"
dependencies = [
"block-buffer 0.9.0",
"digest 0.9.0",
"keccak",
"opaque-debug 0.3.1",
]
[[package]]
name = "sha3"
version = "0.10.8"

View File

@ -13,3 +13,10 @@ channel = { path = "../channel", version = "^0.1.0" }
getrandom = { version = "0.2", features = ["js"] }
serde = "1.0.208"
serde_json = "1.0.117"
base64 = "0.22.1"
ed448-goldilocks-plus = "0.11.2"
ed448-rust = "0.1.1"
rand = "0.8.5"
sha2 = "0.10.8"
hkdf = "0.12.4"
aes-gcm = "0.10.3"

View File

@ -1,6 +1,14 @@
use aes_gcm::{Aes256Gcm, Nonce};
use aes_gcm::aead::{Aead, Payload};
use ed448_rust::Ed448Error;
use hkdf::Hkdf;
use rand::{rngs::OsRng, RngCore};
use sha2::Sha512;
use wasm_bindgen::prelude::*;
use base64::prelude::*;
use channel::*;
use serde::{Deserialize, Serialize};
use ed448_goldilocks_plus::{elliptic_curve::Group, CompressedEdwardsY, EdwardsPoint, Scalar};
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub struct NewDoubleRatchetParameters {
@ -22,6 +30,418 @@ pub struct NewTripleRatchetParameters {
pub async_dkg_ratchet: bool
}
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub struct EncryptionKeyPair {
pub public_key: Vec<u8>,
pub private_key: Vec<u8>,
}
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub struct SigningKeyPair {
pub public_key: Vec<u8>,
pub private_key: Vec<u8>,
}
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub struct SenderX3DH {
pub sending_identity_private_key: Vec<u8>,
pub sending_ephemeral_private_key: Vec<u8>,
pub receiving_identity_key: Vec<u8>,
pub receiving_signed_pre_key: Vec<u8>,
pub session_key_length: usize,
}
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub struct ReceiverX3DH {
pub sending_identity_private_key: Vec<u8>,
pub sending_signed_private_key: Vec<u8>,
pub receiving_identity_key: Vec<u8>,
pub receiving_ephemeral_key: Vec<u8>,
pub session_key_length: usize,
}
#[derive(Clone, PartialEq, Serialize, Deserialize, Debug)]
pub struct MessageCiphertext {
pub ciphertext: String,
pub initialization_vector: String,
pub associated_data: Option<String>,
}
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub struct SealedInboxMessageDecryptRequest {
pub inbox_private_key: Vec<u8>,
pub ephemeral_public_key: Vec<u8>,
pub ciphertext: MessageCiphertext,
}
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub struct SealedInboxMessageEncryptRequest {
pub inbox_public_key: Vec<u8>,
pub ephemeral_private_key: Vec<u8>,
pub plaintext: Vec<u8>,
}
fn encrypt(plaintext: &[u8], key: &[u8])
-> Result<MessageCiphertext, Box<dyn std::error::Error>> {
use aes_gcm::KeyInit;
let mut iv = [0u8; 12];
OsRng.fill_bytes(&mut iv);
let cipher = Aes256Gcm::new_from_slice(key).unwrap();
let nonce = Nonce::from_slice(&iv);
let mut aad = [0u8; 32];
OsRng.fill_bytes(&mut aad);
let ciphertext = cipher.encrypt(nonce, Payload{
msg: plaintext,
aad: &aad,
}).map_err(|e| format!("Encryption failed: {}", e))?;
Ok(MessageCiphertext {
ciphertext: BASE64_STANDARD.encode(ciphertext),
initialization_vector: BASE64_STANDARD.encode(iv.to_vec()),
associated_data: Some(BASE64_STANDARD.encode(aad.to_vec())),
})
}
fn decrypt(ciphertext: &MessageCiphertext, key: &[u8])
-> Result<Vec<u8>, Box<dyn std::error::Error>> {
use aes_gcm::KeyInit;
if key.len() != 32 {
return Err(format!("Invalid key length").into());
}
let cipher = Aes256Gcm::new_from_slice(key).unwrap();
let maybe_iv = BASE64_STANDARD.decode(&ciphertext.initialization_vector);
if maybe_iv.is_err() {
return Err(format!("Decryption failed: {}", maybe_iv.unwrap_err()).into());
}
let iv = maybe_iv.unwrap();
let nonce = Nonce::from_slice(&iv);
let ad = &ciphertext.associated_data;
let mut associated_data = Vec::<u8>::new();
if ad.is_some() {
let aad = BASE64_STANDARD.decode(&ad.clone().unwrap());
if aad.is_err() {
return Err(format!("Decryption failed: {}", aad.unwrap_err()).into());
}
associated_data = aad.unwrap();
}
let ciphertext = BASE64_STANDARD.decode(&ciphertext.ciphertext);
if ciphertext.is_err() {
return Err(format!("Decryption failed: {}", ciphertext.unwrap_err()).into());
}
cipher.decrypt(nonce, Payload{
msg: &ciphertext.unwrap(),
aad: &associated_data,
}).map_err(|e| format!("Decryption failed: {}", e).into())
}
#[wasm_bindgen]
pub fn js_decrypt_inbox_message(input: &str) -> String {
let json: Result<SealedInboxMessageDecryptRequest, serde_json::Error> = serde_json::from_str(input);
match json {
Ok(params) => {
let ephemeral_key = params.ephemeral_public_key;
if ephemeral_key.len() != 57 {
return "invalid ephemeral key length".to_string();
}
let inbox_key = params.inbox_private_key;
if inbox_key.len() != 56 {
return "invalid inbox key length".to_string();
}
let ephemeral_key_bytes: [u8; 57] = ephemeral_key.try_into().unwrap();
let inbox_key_bytes: [u8; 56] = inbox_key.try_into().unwrap();
let priv_key = Scalar::from_bytes(&inbox_key_bytes);
let maybe_eph_key = CompressedEdwardsY(ephemeral_key_bytes).decompress();
if maybe_eph_key.is_none().into() {
return "invalid ephemeral key".to_string();
}
let dh_output = priv_key * maybe_eph_key.unwrap();
// TODO: To support authorized inbox sending, change None to Some when an authorization_key is present.
let hkdf = Hkdf::<Sha512>::new(None, &dh_output.compress().to_bytes());
let mut derived = [0u8; 32];
let err = hkdf.expand(b"quilibrium-sealed-sender", &mut derived);
if err.is_err() {
return "invalid length".into();
}
let result = decrypt(&params.ciphertext, &derived);
if result.is_err() {
return result.unwrap_err().to_string();
}
match serde_json::to_string(&result.unwrap()) {
Ok(result) => result,
Err(e) => e.to_string(),
}
}
Err(e) => {
return e.to_string();
}
}
}
#[wasm_bindgen]
pub fn js_encrypt_inbox_message(input: &str) -> String {
let json: Result<SealedInboxMessageEncryptRequest, serde_json::Error> = serde_json::from_str(input);
match json {
Ok(params) => {
let key = params.ephemeral_private_key;
if key.len() != 56 {
return "invalid ephemeral key length".to_string();
}
let inbox_key = params.inbox_public_key;
if inbox_key.len() != 57 {
return "invalid inbox key length".to_string();
}
let key_bytes: [u8; 56] = key.try_into().unwrap();
let inbox_key_bytes: [u8; 57] = inbox_key.try_into().unwrap();
let priv_key = Scalar::from_bytes(&key_bytes);
let maybe_pub_key = CompressedEdwardsY(inbox_key_bytes).decompress();
if maybe_pub_key.is_none().into() {
return "invalid inbox key".to_string();
}
let dh_output = priv_key * maybe_pub_key.unwrap();
// TODO: To support authorized inbox sending, change None to Some when an authorization_key is present.
let hkdf = Hkdf::<Sha512>::new(None, &dh_output.compress().to_bytes());
let mut derived = [0u8; 32];
let err = hkdf.expand(b"quilibrium-sealed-sender", &mut derived);
if err.is_err() {
return "invalid length".into();
}
let result = encrypt(&params.plaintext, &derived);
if result.is_err() {
return result.unwrap_err().to_string();
}
match serde_json::to_string(&result.unwrap()) {
Ok(result) => result,
Err(e) => e.to_string(),
}
}
Err(e) => {
return e.to_string();
}
}
}
#[wasm_bindgen]
pub fn js_sender_x3dh(input: &str) -> String {
let json: Result<SenderX3DH, serde_json::Error> = serde_json::from_str(input);
match json {
Ok(params) => {
return sender_x3dh(&params.sending_identity_private_key, &params.sending_ephemeral_private_key, &params.receiving_identity_key, &params.receiving_signed_pre_key, params.session_key_length);
}
Err(e) => {
return e.to_string();
}
}
}
#[wasm_bindgen]
pub fn js_receiver_x3dh(input: &str) -> String {
let json: Result<ReceiverX3DH, serde_json::Error> = serde_json::from_str(input);
match json {
Ok(params) => {
return receiver_x3dh(&params.sending_identity_private_key, &params.sending_signed_private_key, &params.receiving_identity_key, &params.receiving_ephemeral_key, params.session_key_length);
}
Err(e) => {
return e.to_string();
}
}
}
#[wasm_bindgen]
pub fn js_generate_x448() -> String {
let priv_key = Scalar::random(&mut rand::thread_rng());
let pub_key = EdwardsPoint::generator() * priv_key;
let output = serde_json::to_string(&EncryptionKeyPair{
public_key: pub_key.compress().to_bytes().to_vec(),
private_key: priv_key.to_bytes().to_vec(),
});
match output {
Ok(result) => {
result
}
Err(e) => {
e.to_string()
}
}
}
#[wasm_bindgen]
pub fn js_generate_ed448() -> String {
let priv_key = ed448_rust::PrivateKey::new(&mut rand::thread_rng());
let pub_key = ed448_rust::PublicKey::from(&priv_key);
let output = serde_json::to_string(&EncryptionKeyPair{
public_key: pub_key.as_byte().to_vec(),
private_key: priv_key.as_bytes().to_vec(),
});
match output {
Ok(result) => {
result
}
Err(e) => {
e.to_string()
}
}
}
#[wasm_bindgen]
pub fn js_get_pubkey_ed448(key: &str) -> String {
let maybe_key = BASE64_STANDARD.decode(key);
if maybe_key.is_err() {
return maybe_key.unwrap_err().to_string();
}
let key = maybe_key.unwrap();
if key.len() != 57 {
return "invalid key length".to_string();
}
let key_bytes: [u8; 57] = key.try_into().unwrap();
let priv_key = ed448_rust::PrivateKey::from(key_bytes);
let pub_key = ed448_rust::PublicKey::from(&priv_key);
return format!("\"{}\"", BASE64_STANDARD.encode(pub_key.as_byte()));
}
#[wasm_bindgen]
pub fn js_get_pubkey_x448(key: &str) -> String {
let maybe_key = BASE64_STANDARD.decode(key);
if maybe_key.is_err() {
return maybe_key.unwrap_err().to_string();
}
let key = maybe_key.unwrap();
if key.len() != 56 {
return "invalid key length".to_string();
}
let mut priv_key_bytes = [0u8; 56];
priv_key_bytes.copy_from_slice(&key);
let priv_key = Scalar::from_bytes(&priv_key_bytes);
let pub_key = EdwardsPoint::generator() * priv_key;
return format!("\"{}\"", BASE64_STANDARD.encode(pub_key.compress().to_bytes().to_vec()));
}
#[wasm_bindgen]
pub fn js_sign_ed448(key: &str, message: &str) -> String {
let maybe_key = BASE64_STANDARD.decode(key);
if maybe_key.is_err() {
return maybe_key.unwrap_err().to_string();
}
let maybe_message = BASE64_STANDARD.decode(message);
if maybe_message.is_err() {
return maybe_message.unwrap_err().to_string();
}
let key = maybe_key.unwrap();
if key.len() != 57 {
return "invalid key length".to_string();
}
let key_bytes: [u8; 57] = key.try_into().unwrap();
let priv_key = ed448_rust::PrivateKey::from(key_bytes);
let signature = priv_key.sign(&maybe_message.unwrap(), None);
match signature {
Ok(output) => {
return format!("\"{}\"", BASE64_STANDARD.encode(output));
}
Err(Ed448Error::WrongKeyLength) => {
return "invalid key length".to_string();
}
Err(Ed448Error::WrongPublicKeyLength) => {
return "invalid public key length".to_string();
}
Err(Ed448Error::WrongSignatureLength) => {
return "invalid signature length".to_string();
}
Err(Ed448Error::InvalidPoint) => {
return "invalid point".to_string();
}
Err(Ed448Error::InvalidSignature) => {
return "invalid signature".to_string();
}
Err(Ed448Error::ContextTooLong) => {
return "context too long".to_string();
}
}
}
#[wasm_bindgen]
pub fn js_verify_ed448(public_key: &str, message: &str, signature: &str) -> String {
let maybe_key = BASE64_STANDARD.decode(public_key);
if maybe_key.is_err() {
return maybe_key.unwrap_err().to_string();
}
let maybe_message = BASE64_STANDARD.decode(message);
if maybe_message.is_err() {
return maybe_message.unwrap_err().to_string();
}
let maybe_signature = BASE64_STANDARD.decode(signature);
if maybe_signature.is_err() {
return maybe_signature.unwrap_err().to_string();
}
let key = maybe_key.unwrap();
if key.len() != 57 {
return "invalid key length".to_string();
}
let pub_bytes: [u8; 57] = key.try_into().unwrap();
let pub_key = ed448_rust::PublicKey::from(pub_bytes);
let signature = pub_key.verify(&maybe_message.unwrap(), &maybe_signature.unwrap(), None);
match signature {
Ok(()) => {
return "true".to_string();
}
Err(Ed448Error::WrongKeyLength) => {
return "invalid key length".to_string();
}
Err(Ed448Error::WrongPublicKeyLength) => {
return "invalid public key length".to_string();
}
Err(Ed448Error::WrongSignatureLength) => {
return "invalid signature length".to_string();
}
Err(Ed448Error::InvalidPoint) => {
return "invalid point".to_string();
}
Err(Ed448Error::InvalidSignature) => {
return "invalid signature".to_string();
}
Err(Ed448Error::ContextTooLong) => {
return "context too long".to_string();
}
}
}
#[wasm_bindgen]
pub fn js_new_double_ratchet(params: &str) -> String {
let json: Result<NewDoubleRatchetParameters, serde_json::Error> = serde_json::from_str(params);

View File

@ -1,9 +1,9 @@
use base64::prelude::*;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::{collections::HashMap, io::Read};
use ed448_goldilocks_plus::{elliptic_curve::group::GroupEncoding, EdwardsPoint, Scalar};
use protocols::{doubleratchet::{DoubleRatchetParticipant, P2PChannelEnvelope}, tripleratchet::{PeerInfo, TripleRatchetParticipant}};
use ed448_goldilocks_plus::{elliptic_curve::group::GroupEncoding, CompressedEdwardsY, EdwardsPoint, Scalar};
use protocols::{doubleratchet::{DoubleRatchetParticipant, P2PChannelEnvelope}, tripleratchet::{PeerInfo, TripleRatchetParticipant}, x3dh};
pub(crate) mod protocols;
@ -39,6 +39,86 @@ pub struct TripleRatchetStateAndMessage {
pub message: Vec<u8>,
}
pub fn sender_x3dh(sending_identity_private_key: &Vec<u8>, sending_ephemeral_private_key: &Vec<u8>, receiving_identity_key: &Vec<u8>, receiving_signed_pre_key: &Vec<u8>, session_key_length: usize) -> String {
if sending_identity_private_key.len() != 56 {
return "invalid sending identity private key length".to_string();
}
if sending_ephemeral_private_key.len() != 56 {
return "invalid sending ephemeral private key length".to_string();
}
if receiving_identity_key.len() != 57 {
return "invalid receiving identity public key length".to_string();
}
if receiving_signed_pre_key.len() != 57 {
return "invalid receiving signed public key length".to_string();
}
let sidk = Scalar::from_bytes(sending_identity_private_key.as_slice().try_into().unwrap());
let sepk = Scalar::from_bytes(sending_ephemeral_private_key.as_slice().try_into().unwrap());
let ridk = CompressedEdwardsY(receiving_identity_key.as_slice().try_into().unwrap()).decompress();
let rspk = CompressedEdwardsY(receiving_signed_pre_key.as_slice().try_into().unwrap()).decompress();
if ridk.is_none().into() {
return "invalid receiving identity public key".to_string();
}
if rspk.is_none().into() {
return "invalid receiving signed public key".to_string();
}
match x3dh::sender_x3dh(&sidk, &sepk, &ridk.unwrap(), &rspk.unwrap(), session_key_length) {
Some(result) => {
return format!("\"{}\"", BASE64_STANDARD.encode(result));
}
None => {
return "could not perform key agreement".to_string();
}
}
}
pub fn receiver_x3dh(sending_identity_private_key: &Vec<u8>, sending_signed_private_key: &Vec<u8>, receiving_identity_key: &Vec<u8>, receiving_ephemeral_key: &Vec<u8>, session_key_length: usize) -> String {
if sending_identity_private_key.len() != 56 {
return "invalid sending identity private key length".to_string();
}
if sending_signed_private_key.len() != 56 {
return "invalid sending signed private key length".to_string();
}
if receiving_identity_key.len() != 57 {
return "invalid receiving identity public key length".to_string();
}
if receiving_ephemeral_key.len() != 57 {
return "invalid receiving ephemeral public key length".to_string();
}
let sidk = Scalar::from_bytes(sending_identity_private_key.as_slice().try_into().unwrap());
let sspk = Scalar::from_bytes(sending_signed_private_key.as_slice().try_into().unwrap());
let ridk = CompressedEdwardsY(receiving_identity_key.as_slice().try_into().unwrap()).decompress();
let repk = CompressedEdwardsY(receiving_ephemeral_key.as_slice().try_into().unwrap()).decompress();
if ridk.is_none().into() {
return "invalid receiving identity public key".to_string();
}
if repk.is_none().into() {
return "invalid receiving signed public key".to_string();
}
match x3dh::receiver_x3dh(&sidk, &sspk, &ridk.unwrap(), &repk.unwrap(), session_key_length) {
Some(result) => {
return format!("\"{}\"", BASE64_STANDARD.encode(result));
}
None => {
return "could not perform key agreement".to_string();
}
}
}
pub fn new_double_ratchet(session_key: &Vec<u8>, sending_header_key: &Vec<u8>, next_receiving_header_key: &Vec<u8>, is_sender: bool, sending_ephemeral_private_key: &Vec<u8>, receiving_ephemeral_key: &Vec<u8>) -> String {
if sending_ephemeral_private_key.len() != 56 {
return "invalid private key length".to_string();
@ -64,7 +144,7 @@ pub fn new_double_ratchet(session_key: &Vec<u8>, sending_header_key: &Vec<u8>, n
&session_key,
&sending_header_key,
&next_receiving_header_key,
true,
is_sender,
sending_key,
receiving_key.unwrap(),
);