From 59174c5ac24435aca6967795577061a3f549161e Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Thu, 18 Mar 2021 12:11:38 -0700 Subject: [PATCH 001/116] start refactoring key support; move in pack implementation Signed-off-by: Andrew Whitehead --- Cargo.toml | 18 +- src/didcomm/mod.rs | 1 + src/didcomm/pack/alg.rs | 365 +++++++++++++++++++++++ src/didcomm/pack/mod.rs | 9 + src/didcomm/pack/nacl_box.rs | 139 +++++++++ src/didcomm/pack/types.rs | 79 +++++ src/ffi/store.rs | 15 +- src/keys/alg/edwards.rs | 394 +++++++++++++++++++++++++ src/keys/alg/mod.rs | 1 + src/keys/any.rs | 84 ++++++ src/keys/caps.rs | 193 ++++++++++++ src/keys/encrypt.rs | 13 +- src/keys/kdf/argon2.rs | 2 +- src/keys/mod.rs | 38 ++- src/keys/store.rs | 128 ++++++-- src/keys/types.rs | 286 +++++------------- src/lib.rs | 4 + src/random.rs | 41 +++ src/serde_utils.rs | 4 +- src/store.rs | 113 +++---- tests/utils/mod.rs | 18 +- wrappers/python/aries_askar/version.py | 2 +- 22 files changed, 1591 insertions(+), 356 deletions(-) create mode 100644 src/didcomm/mod.rs create mode 100644 src/didcomm/pack/alg.rs create mode 100644 src/didcomm/pack/mod.rs create mode 100644 src/didcomm/pack/nacl_box.rs create mode 100644 src/didcomm/pack/types.rs create mode 100644 src/keys/alg/edwards.rs create mode 100644 src/keys/alg/mod.rs create mode 100644 src/keys/any.rs create mode 100644 src/keys/caps.rs create mode 100644 src/random.rs diff --git a/Cargo.toml b/Cargo.toml index 60d549de..0ecdf588 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "aries-askar" -version = "0.1.3" +version = "0.2.0" authors = ["Hyperledger Aries Contributors "] edition = "2018" description = "Hyperledger Aries Askar secure storage" -license = "MIT/Apache-2.0" +license = "MIT OR Apache-2.0" readme = "README.md" repository = "https://github.com/hyperledger/aries-askar/" categories = ["cryptography", "database"] @@ -38,9 +38,16 @@ async-global-executor = { version = "1.4", features = ["async-io"] } async-mutex = "1.4" async-stream = "0.3" async-std = "1.7" # temporary addition to encourage common dependencies with sqlx +base64 = "0.13" +blake2 = "0.9" blocking = "1.0" +bs58 = "0.4" +chacha20 = "0.6" # should match version from chacha20poly1305 chacha20poly1305 = { version = "0.7", default-features = false, features = ["alloc", "chacha20"] } chrono = "0.4" +crypto_box = "0.5" +curve25519-dalek = "3.0" +ed25519-dalek = { version = "1.0", features = ["std"] } env_logger = { version = "0.7", optional = true } ffi-support = { version = "0.4", optional = true } futures-lite = "1.7" @@ -61,12 +68,13 @@ serde_json = "1.0" sha2 = "0.9" url = { version = "2.1", default-features = false } uuid = { version = "0.8", features = ["v4"] } +x25519-dalek = "1.1" zeroize = { version = "1.1.0", features = ["zeroize_derive"] } [dependencies.indy-utils] version = "=0.3.9" default-features = false -features = ["ed25519", "pack", "serde_support", "wql"] +features = ["serde_support", "wql"] [dependencies.sqlx] version = "0.5.1" @@ -74,10 +82,6 @@ default-features = false features = ["chrono", "runtime-async-std-rustls"] optional = true -# [target.'cfg(target_os = "macos")'.dependencies] -# keychain-services = { path = "../keychain-services.rs" } -# keychain-services = { git = "https://github.com/iqlusioninc/keychain-services.rs", rev = "7410fb8baf4ecdf04cdcd7d06d02658f4f158d77" } - [profile.release] lto = true codegen-units = 1 diff --git a/src/didcomm/mod.rs b/src/didcomm/mod.rs new file mode 100644 index 00000000..3aae65f7 --- /dev/null +++ b/src/didcomm/mod.rs @@ -0,0 +1 @@ +pub mod pack; diff --git a/src/didcomm/pack/alg.rs b/src/didcomm/pack/alg.rs new file mode 100644 index 00000000..29cd6125 --- /dev/null +++ b/src/didcomm/pack/alg.rs @@ -0,0 +1,365 @@ +use chacha20poly1305::{ + aead::{generic_array::typenum::Unsigned, Aead, NewAead, Payload}, + ChaCha20Poly1305, Key as ChaChaKey, +}; + +use std::string::ToString; + +use super::nacl_box::*; +use super::types::*; +use crate::error::Error; +use crate::random::{random_array, random_vec}; +use crate::types::SecretBytes; + +pub const PROTECTED_HEADER_ENC: &'static str = "xchacha20poly1305_ietf"; +pub const PROTECTED_HEADER_TYP: &'static str = "JWM/1.0"; +pub const PROTECTED_HEADER_ALG_AUTH: &'static str = "Authcrypt"; +pub const PROTECTED_HEADER_ALG_ANON: &'static str = "Anoncrypt"; + +type KeySize = ::KeySize; + +const NONCE_SIZE: usize = ::NonceSize::USIZE; +const TAG_SIZE: usize = ::TagSize::USIZE; + +pub fn pack_message>( + message: M, + receiver_list: Vec, + sender_key: Option, +) -> Result, Error> { + // break early and error out if no receivers keys are provided + if receiver_list.is_empty() { + return Err(err_msg!("No message recipients")); + } + + // generate content encryption key that will encrypt `message` + let cek = SecretBytes::from(random_vec(KeySize::to_usize())); + + let base64_protected = if let Some(sender_key) = sender_key { + // returns authcrypted pack_message format. See Wire message format HIPE for details + prepare_protected_authcrypt(&cek, receiver_list, &sender_key)? + } else { + // returns anoncrypted pack_message format. See Wire message format HIPE for details + prepare_protected_anoncrypt(&cek, receiver_list)? + }; + + // Use AEAD to encrypt `message` with "protected" data as "associated data" + let chacha = ChaCha20Poly1305::new(ChaChaKey::from_slice(&cek)); + let nonce = random_array(); + let payload = Payload { + aad: base64_protected.as_bytes(), + msg: message.as_ref(), + }; + let ciphertext = chacha + .encrypt(&nonce, payload) + .map_err(|_| err_msg!(Encryption, "Error encrypting payload"))?; + let iv = b64_encode(nonce); + let clen = ciphertext.len() - TAG_SIZE; + let tag = b64_encode(&ciphertext[clen..]); + let ciphertext = b64_encode(&ciphertext[..clen]); + + format_pack_message(&base64_protected, &ciphertext, &iv, &tag) +} + +fn prepare_protected_anoncrypt(cek: &[u8], receiver_list: Vec) -> Result { + let mut encrypted_recipients_struct: Vec = Vec::with_capacity(receiver_list.len()); + + for their_vk in receiver_list { + // encrypt cek for recipient + let their_vk_x = their_vk.to_x25519(); + let enc_cek = crypto_box_seal(&their_vk_x.to_bytes(), cek.as_ref())?; + + // create recipient struct and push to encrypted list + encrypted_recipients_struct.push(Recipient { + encrypted_key: b64_encode(enc_cek.as_slice()), + header: Header { + kid: their_vk.to_base58(), + sender: None, + iv: None, + }, + }); + } + + b64_encode_protected(encrypted_recipients_struct, false) +} + +fn prepare_protected_authcrypt( + cek: &[u8], + receiver_list: Vec, + sender_key: &KeyPair, +) -> Result { + let mut encrypted_recipients_struct: Vec = vec![]; + + let sender_key_x = sender_key.to_x25519(); + let sender_sk_x = sender_key_x.private_key(); + let sender_pk = sender_key.public_key(); + + for their_vk in receiver_list { + let their_vk_x = their_vk.to_x25519(); + + // encrypt cek for recipient + let (enc_cek, iv) = crypto_box(&their_vk_x.to_bytes(), sender_sk_x.as_ref(), cek, None)?; + + // encrypt sender key for recipient + let enc_sender = crypto_box_seal(&their_vk_x.to_bytes(), sender_pk.to_base58().as_ref())?; + + // create recipient struct and push to encrypted list + encrypted_recipients_struct.push(Recipient { + encrypted_key: b64_encode(enc_cek.as_slice()), + header: Header { + kid: their_vk.to_base58(), + sender: Some(b64_encode(enc_sender.as_slice())), + iv: Some(b64_encode(iv.as_slice())), + }, + }); + } + + b64_encode_protected(encrypted_recipients_struct, true) +} + +#[inline(always)] +fn b64_decode(input: impl AsRef<[u8]>) -> Result, Error> { + base64::decode_config(input, base64::URL_SAFE).map_err(|_| err_msg!("Error decoding as base64")) +} + +#[inline(always)] +fn b64_encode(input: impl AsRef<[u8]>) -> String { + base64::encode_config(input, base64::URL_SAFE) +} + +fn b64_encode_protected( + encrypted_recipients_struct: Vec, + alg_is_authcrypt: bool, +) -> Result { + let alg_val = if alg_is_authcrypt { + String::from(PROTECTED_HEADER_ALG_AUTH) + } else { + String::from(PROTECTED_HEADER_ALG_ANON) + }; + + // structure protected and base64URL encode it + let protected_struct = Protected { + enc: PROTECTED_HEADER_ENC.to_string(), + typ: PROTECTED_HEADER_TYP.to_string(), + alg: alg_val, + recipients: encrypted_recipients_struct, + }; + let protected_encoded = serde_json::to_string(&protected_struct) + .map_err(|err| err_msg!(Encryption, "Failed to serialize protected field {}", err))?; + + Ok(b64_encode(protected_encoded.as_bytes())) +} + +fn format_pack_message( + base64_protected: &str, + ciphertext: &str, + iv: &str, + tag: &str, +) -> Result, Error> { + // serialize pack message and return as vector of bytes + let jwe_struct = JWE { + protected: base64_protected.to_string(), + iv: iv.to_string(), + ciphertext: ciphertext.to_string(), + tag: tag.to_string(), + }; + + serde_json::to_vec(&jwe_struct).map_err(|_| err_msg!("Error serializing JWE")) +} + +pub async fn unpack_message<'f>( + message: impl AsRef<[u8]>, + lookup: impl KeyLookup<'f>, +) -> Result<(Vec, PublicKey, Option), Error> { + let jwe = + serde_json::from_slice(message.as_ref()).map_err(|_| err_msg!("Invalid format for JWE"))?; + unpack_jwe(&jwe, lookup).await +} + +pub async fn unpack_jwe<'f>( + jwe_struct: &JWE, + lookup: impl KeyLookup<'f>, +) -> Result<(Vec, PublicKey, Option), Error> { + // decode protected data + let protected_decoded = b64_decode(&jwe_struct.protected)?; + let protected: Protected = serde_json::from_slice(&protected_decoded) + .map_err(|_| err_msg!(Encryption, "Invalid format for protected data"))?; + + // extract recipient that matches a key in the wallet + let (recipient, recip_pk, recip_sk) = + if let Some(recip) = find_unpack_recipient(protected, lookup).await? { + recip + } else { + return Err(err_msg!(Encryption, "No matching recipient found")); + }; + let is_auth_recipient = recipient.header.sender.is_some() && recipient.header.iv.is_some(); + + // get cek and sender data + let (sender_verkey_option, cek) = if is_auth_recipient { + let (send, cek) = unpack_cek_authcrypt(&recipient, &recip_sk)?; + (Some(send), cek) + } else { + let cek = unpack_cek_anoncrypt(&recipient, &recip_sk)?; + (None, cek) + }; + + // decrypt message + let chacha = ChaCha20Poly1305::new_varkey(&cek) + .map_err(|_| err_msg!(Encryption, "Error creating unpack decryptor for cek"))?; + let nonce = b64_decode(&jwe_struct.iv)?; + if nonce.len() != NONCE_SIZE { + return Err(err_msg!(Encryption, "Invalid size for message nonce")); + } + let mut ciphertext = b64_decode(&jwe_struct.ciphertext)?; + ciphertext.append(b64_decode(&jwe_struct.tag)?.as_mut()); + let payload = Payload { + aad: jwe_struct.protected.as_bytes(), + msg: ciphertext.as_slice(), + }; + let message = chacha + .decrypt(nonce.as_slice().into(), payload) + .map_err(|_| err_msg!(Encryption, "Error decrypting message payload"))?; + + Ok((message, recip_pk, sender_verkey_option)) +} + +fn unpack_cek_authcrypt( + recipient: &Recipient, + recip_sk: &KeyPair, +) -> Result<(PublicKey, Vec), Error> { + let encrypted_key_vec = b64_decode(&recipient.encrypted_key)?; + let iv = b64_decode(&recipient.header.iv.as_ref().unwrap())?; + let enc_sender_vk = b64_decode(&recipient.header.sender.as_ref().unwrap())?; + + // decrypt sender_vk + let recip_x = recip_sk.to_x25519(); + let recip_sk_x = recip_x.private_key(); + let sender_vk_vec = crypto_box_seal_open( + &recip_x.public_key().to_bytes(), + recip_sk_x.as_ref(), + &enc_sender_vk, + )?; + let sender_vk = PublicKey::from_str( + std::str::from_utf8(&sender_vk_vec) + .map_err(|_| err_msg!(Encryption, "Invalid sender verkey"))?, + )?; + + // decrypt cek + let cek = crypto_box_open( + recip_sk_x.as_ref(), + &sender_vk.to_x25519().to_bytes(), + encrypted_key_vec.as_slice(), + iv.as_slice(), + )?; + + Ok((sender_vk, cek)) +} + +fn unpack_cek_anoncrypt(recipient: &Recipient, recip_sk: &KeyPair) -> Result, Error> { + let encrypted_key = b64_decode(&recipient.encrypted_key)?; + + // decrypt cek + let recip_x = recip_sk.to_x25519(); + let cek = crypto_box_seal_open( + &recip_x.public_key().to_bytes(), + recip_x.private_key().as_ref(), + &encrypted_key, + )?; + + Ok(cek) +} + +async fn find_unpack_recipient<'f>( + protected: Protected, + lookup: impl KeyLookup<'f>, +) -> Result, Error> { + let mut recip_vks = Vec::::with_capacity(protected.recipients.len()); + for recipient in &protected.recipients { + let vk = PublicKey::from_str(&recipient.header.kid)?; + recip_vks.push(vk); + } + if let Some((idx, sk)) = lookup.find(&recip_vks).await { + let recip = protected.recipients.into_iter().nth(idx).unwrap(); + let vk = recip_vks.into_iter().nth(idx).unwrap(); + Ok(Some((recip, vk, sk))) + } else { + Ok(None) + } +} + +#[cfg(test)] +mod tests { + use async_global_executor::block_on; + + use super::*; + + #[test] + fn anon_pack_basic() { + let pk = KeyPair::from_seed(b"000000000000000000000000000Test2") + .unwrap() + .public_key(); + let packed = pack_message(b"hello there", vec![pk], None); + assert!(packed.is_ok()); + } + + #[test] + fn auth_pack_basic() { + let sk = KeyPair::from_seed(b"000000000000000000000000000Test1").unwrap(); + let pk = KeyPair::from_seed(b"000000000000000000000000000Test2") + .unwrap() + .public_key(); + let packed = pack_message(b"hello there", vec![pk], Some(sk)); + assert!(packed.is_ok()); + } + + #[test] + fn anon_pack_round_trip() { + let sk1 = KeyPair::from_seed(b"000000000000000000000000000Test3").unwrap(); + let pk1 = sk1.public_key(); + + let input_msg = b"hello there"; + let packed = pack_message(&input_msg, vec![pk1.clone()], None).unwrap(); + + let lookup = |find_pks: &Vec| { + for (idx, pk) in find_pks.into_iter().enumerate() { + if pk == &pk1 { + return Some((idx, sk1.clone())); + } + } + None + }; + + let lookup_fn = key_lookup_fn(lookup); + let result = unpack_message(&packed, &lookup_fn); + let (msg, p_recip, p_send) = block_on(result).unwrap(); + assert_eq!(msg, input_msg); + assert_eq!(p_recip, pk1); + assert_eq!(p_send, None); + } + + #[test] + fn auth_pack_round_trip() { + let sk1 = KeyPair::from_seed(b"000000000000000000000000000Test3").unwrap(); + let pk1 = sk1.public_key(); + let sk2 = KeyPair::from_seed(b"000000000000000000000000000Test4").unwrap(); + let pk2 = sk2.public_key(); + + let input_msg = b"hello there"; + let packed = pack_message(&input_msg, vec![pk2.clone()], Some(sk1.clone())).unwrap(); + + let lookup = |find_pks: &Vec| { + for (idx, pk) in find_pks.into_iter().enumerate() { + if pk == &pk2 { + return Some((idx, sk2.clone())); + } + } + None + }; + + let lookup_fn = key_lookup_fn(lookup); + let result = unpack_message(&packed, &lookup_fn); + let (msg, p_recip, p_send) = block_on(result).unwrap(); + assert_eq!(msg, input_msg); + assert_eq!(p_recip, pk2); + assert_eq!(p_send, Some(pk1)); + } +} diff --git a/src/didcomm/pack/mod.rs b/src/didcomm/pack/mod.rs new file mode 100644 index 00000000..ca69237e --- /dev/null +++ b/src/didcomm/pack/mod.rs @@ -0,0 +1,9 @@ +mod alg; +mod nacl_box; +mod types; + +pub use alg::{pack_message, unpack_message}; +pub use types::KeyLookup; + +#[cfg(test)] +pub use types::key_lookup_fn; diff --git a/src/didcomm/pack/nacl_box.rs b/src/didcomm/pack/nacl_box.rs new file mode 100644 index 00000000..79460e87 --- /dev/null +++ b/src/didcomm/pack/nacl_box.rs @@ -0,0 +1,139 @@ +use std::convert::TryInto; + +use blake2::{digest::Update, digest::VariableOutput, VarBlake2b}; +use crypto_box::{ + self as cbox, + aead::{generic_array::typenum::Unsigned, Aead}, +}; + +use crate::error::Error; +use crate::keys::alg::edwards::X25519KeyPair; +use crate::random::random_vec; + +const CBOX_NONCE_SIZE: usize = ::NonceSize::USIZE; + +fn crypto_box_key(key: F) -> Result +where + F: AsRef<[u8]>, + T: From<[u8; cbox::KEY_SIZE]>, +{ + let key = key.as_ref(); + if key.len() != cbox::KEY_SIZE { + Err(err_msg!(Encryption, "Invalid crypto box key length")) + } else { + Ok(T::from(key.try_into().unwrap())) + } +} + +pub fn crypto_box_nonce( + ephemeral_pk: &[u8], + recip_pk: &[u8], +) -> Result<[u8; CBOX_NONCE_SIZE], Error> { + let mut key_hash = VarBlake2b::new(CBOX_NONCE_SIZE).unwrap(); + key_hash.update(ephemeral_pk); + key_hash.update(recip_pk); + let mut nonce = [0u8; CBOX_NONCE_SIZE]; + key_hash.finalize_variable(|hash| nonce.copy_from_slice(hash)); + Ok(nonce) +} + +pub fn crypto_box( + recip_pk: &[u8], + sender_sk: &[u8], + message: &[u8], + nonce: Option>, +) -> Result<(Vec, Vec), Error> { + let recip_pk: cbox::PublicKey = crypto_box_key(recip_pk)?; + let sender_sk: cbox::SecretKey = crypto_box_key(sender_sk)?; + let box_inst = cbox::SalsaBox::new(&recip_pk, &sender_sk); + + let nonce = if let Some(nonce) = nonce { + nonce.as_slice().into() + } else { + random_vec(CBOX_NONCE_SIZE) + }; + + let ciphertext = box_inst + .encrypt(nonce.as_slice().into(), message) + .map_err(|_| err_msg!(Encryption, "Error encrypting box"))?; + Ok((ciphertext, nonce)) +} + +pub fn crypto_box_open( + recip_sk: &[u8], + sender_pk: &[u8], + ciphertext: &[u8], + nonce: &[u8], +) -> Result, Error> { + let recip_sk: cbox::SecretKey = crypto_box_key(recip_sk)?; + let sender_pk: cbox::PublicKey = crypto_box_key(sender_pk)?; + let box_inst = cbox::SalsaBox::new(&sender_pk, &recip_sk); + + let plaintext = box_inst + .decrypt(nonce.into(), ciphertext) + .map_err(|_| err_msg!(Encryption, "Error decrypting box"))?; + Ok(plaintext) +} + +pub fn crypto_box_seal(recip_pk: &[u8], message: &[u8]) -> Result, Error> { + let ephem_kp = X25519KeyPair::generate()?; + let ephem_sk = ephem_kp.private_key(); + let ephem_sk_x: cbox::SecretKey = crypto_box_key(&ephem_sk)?; + debug_assert_eq!(ephem_sk_x.to_bytes(), ephem_sk.as_ref()); + let ephem_pk_x = ephem_sk_x.public_key(); + + let nonce = crypto_box_nonce(ephem_pk_x.as_bytes(), &recip_pk)?.to_vec(); + let (mut boxed, _) = crypto_box(recip_pk, ephem_sk.as_ref(), message, Some(nonce))?; + + let mut result = Vec::::with_capacity(cbox::KEY_SIZE); // FIXME + result.extend_from_slice(ephem_pk_x.as_bytes()); + result.append(&mut boxed); + Ok(result) +} + +pub fn crypto_box_seal_open( + recip_pk: &[u8], + recip_sk: &[u8], + ciphertext: &[u8], +) -> Result, Error> { + let ephem_pk = &ciphertext[..32]; + let boxed = &ciphertext[32..]; + + let nonce = crypto_box_nonce(&ephem_pk, &recip_pk)?; + let decode = crypto_box_open(recip_sk, ephem_pk, boxed, &nonce)?; + Ok(decode) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::keys::alg::edwards::Ed25519KeyPair; + + #[test] + fn crypto_box_open_expected() { + let sk = hex::decode("07d0b594683bdb6af5f4eacb1a392687d580a58db196a752dca316dedb7d251d") + .unwrap(); + let pk = hex::decode("07d0b594683bdb6af5f4eacb1a392687d580a58db196a752dca316dedb7d251c") + .unwrap(); + let message = b"hello there"; + // let nonce = b"012345678912012345678912".to_vec(); + let (boxed, nonce) = crypto_box(&pk, &sk, message, None).unwrap(); + + let open = crypto_box_open(&sk, &pk, &boxed, &nonce).unwrap(); + assert_eq!(open, message); + } + + #[test] + fn crypto_box_seal_expected() { + let kp = Ed25519KeyPair::from_seed(b"000000000000000000000000000Test0").unwrap(); + let kp_x = kp.to_x25519(); + let sk_x = kp_x.private_key(); + let pk_x = kp_x.public_key(); + + let message = b"hello there"; + let sealed = crypto_box_seal(&pk_x.to_bytes(), message).unwrap(); + + let open = crypto_box_seal_open(&pk_x.to_bytes(), sk_x.as_ref(), &sealed).unwrap(); + assert_eq!(open, message); + } +} diff --git a/src/didcomm/pack/types.rs b/src/didcomm/pack/types.rs new file mode 100644 index 00000000..cc6f00b6 --- /dev/null +++ b/src/didcomm/pack/types.rs @@ -0,0 +1,79 @@ +use std::future::Future; + +pub use crate::keys::alg::edwards::{Ed25519KeyPair as KeyPair, Ed25519PublicKey as PublicKey}; + +#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)] +pub struct JWE { + pub protected: String, + pub iv: String, + pub ciphertext: String, + pub tag: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)] +pub struct Recipient { + pub encrypted_key: String, + pub header: Header, +} + +#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)] +pub struct Header { + pub kid: String, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + pub iv: Option, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + pub sender: Option, +} + +#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)] +pub struct Protected { + pub enc: String, + pub typ: String, + pub alg: String, + pub recipients: Vec, +} + +/// A trait for custom key lookup implementations used by unpack +pub trait KeyLookup<'f> { + fn find<'a>( + self, + key: &'a Vec, + ) -> std::pin::Pin> + Send + 'a>> + where + 'f: 'a; +} + +type KeyLookupCb<'a> = Box) -> Option<(usize, KeyPair)> + Send + Sync + 'a>; + +pub struct KeyLookupFn<'a> { + cb: KeyLookupCb<'a>, +} + +#[cfg(test)] +/// Create a `KeyLookup` from a callback function +pub fn key_lookup_fn<'a, F>(cb: F) -> KeyLookupFn<'a> +where + F: Fn(&Vec) -> Option<(usize, KeyPair)> + Send + Sync + 'a, +{ + KeyLookupFn { + cb: Box::new(cb) as KeyLookupCb<'a>, + } +} + +impl<'a, 'l, 'r> KeyLookup<'l> for &'r KeyLookupFn<'a> +where + 'a: 'l, + 'r: 'a, +{ + fn find<'f>( + self, + keys: &'f Vec, + ) -> std::pin::Pin> + Send + 'f>> + where + 'l: 'f, + { + Box::pin(async move { (&self.cb)(keys) }) + } +} diff --git a/src/ffi/store.rs b/src/ffi/store.rs index a93bf721..9e117465 100644 --- a/src/ffi/store.rs +++ b/src/ffi/store.rs @@ -966,7 +966,7 @@ pub extern "C" fn askar_session_fetch_keypair( let result = async { let mut session = handle.load().await?; let key_entry = session.fetch_key( - KeyCategory::KeyPair, + KeyCategory::PrivateKey, &ident, for_update != 0 ).await?; @@ -1015,7 +1015,7 @@ pub extern "C" fn askar_session_update_keypair( let result = async { let mut session = handle.load().await?; session.update_key( - KeyCategory::KeyPair, + KeyCategory::PrivateKey, &ident, metadata.as_ref().map(String::as_str), tags.as_ref().map(Vec::as_slice) @@ -1152,7 +1152,7 @@ pub extern "C" fn askar_session_unpack_message( let (unpacked, recipient, sender) = session.unpack_message( &message ).await?; - Ok((unpacked, recipient.to_string(), sender.map(|s| s.to_string()))) + Ok((unpacked, recipient.to_base58(), sender.map(|s| s.to_base58()))) }.await; cb.resolve(result); }); @@ -1208,9 +1208,14 @@ pub extern "C" fn askar_session_close( } fn export_key_entry(key_entry: KeyEntry) -> KvResult { - let (category, name, params, tags) = key_entry.into_parts(); + let KeyEntry { + category, + ident, + params, + tags, + } = key_entry; let value = serde_json::to_string(¶ms) .map_err(err_map!("Error converting key entry to JSON"))? .into_bytes(); - Ok(Entry::new(category.to_string(), name, value, tags)) + Ok(Entry::new(category.to_string(), ident, value, tags)) } diff --git a/src/keys/alg/edwards.rs b/src/keys/alg/edwards.rs new file mode 100644 index 00000000..1237c17f --- /dev/null +++ b/src/keys/alg/edwards.rs @@ -0,0 +1,394 @@ +use std::convert::{TryFrom, TryInto}; + +use curve25519_dalek::edwards::CompressedEdwardsY; +use ed25519_dalek::{Keypair, PublicKey, SecretKey, Signer, KEYPAIR_LENGTH}; +use rand::rngs::OsRng; +use sha2::{self, Digest}; +use x25519_dalek::{PublicKey as XPublicKey, StaticSecret as XSecretKey}; + +use crate::{ + error::Error, + keys::any::{AnyPrivateKey, AnyPublicKey}, + keys::caps::{KeyAlg, KeyCapSign, KeyCapVerify, SignatureFormat, SignatureType}, + types::SecretBytes, +}; + +// FIXME - check for low-order points when loading public keys? +// https://github.com/tendermint/tmkms/pull/279 + +#[derive(Debug)] +pub struct Ed25519KeyPair(Keypair); + +impl Ed25519KeyPair { + pub fn generate() -> Result { + let mut rng = OsRng; + Ok(Self(Keypair::generate(&mut rng))) + } + + pub fn from_seed(ikm: &[u8]) -> Result { + let secret = SecretKey::from_bytes(ikm).map_err(|_| err_msg!("Invalid key material"))?; + let public = PublicKey::from(&secret); + Ok(Self(Keypair { secret, public })) + } + + pub fn from_bytes(kp: &[u8]) -> Result { + let kp = Keypair::from_bytes(kp).map_err(|_| err_msg!("Invalid keypair bytes"))?; + Ok(Self(kp)) + } + + pub fn to_bytes(&self) -> SecretBytes { + SecretBytes::from(self.0.to_bytes().to_vec()) + } + + pub fn to_x25519(&self) -> X25519KeyPair { + let hash = sha2::Sha512::digest(&self.0.secret.to_bytes()[..]); + let output: [u8; 32] = (&hash[..32]).try_into().unwrap(); + // clamp result + let secret = XSecretKey::from(output); + let public = XPublicKey::from(&secret); + X25519KeyPair(secret, public) + } + + pub fn private_key(&self) -> SecretBytes { + SecretBytes::from(self.0.secret.to_bytes().to_vec()) + } + + pub fn public_key(&self) -> Ed25519PublicKey { + Ed25519PublicKey(self.0.public.clone()) + } + + pub fn sign(&self, message: &[u8]) -> [u8; 64] { + self.0.sign(&message).to_bytes() + } + + pub fn verify(&self, message: &[u8], signature: [u8; 64]) -> bool { + self.0.verify_strict(message, &signature.into()).is_ok() + } +} + +impl Clone for Ed25519KeyPair { + fn clone(&self) -> Self { + Self(Keypair { + secret: SecretKey::from_bytes(&self.0.secret.as_bytes()[..]).unwrap(), + public: self.0.public.clone(), + }) + } +} + +impl KeyCapSign for Ed25519KeyPair { + fn key_sign( + &self, + data: &[u8], + sig_type: Option, + sig_format: Option, + ) -> Result, Error> { + match sig_type { + None | Some(SignatureType::Ed25519) => match sig_format { + None | Some(SignatureFormat::Base58) => { + Ok(bs58::encode(self.sign(data)).into_vec()) + } + #[allow(unreachable_patterns)] + _ => Err(err_msg!(Unsupported, "Unsupported signature format")), + }, + #[allow(unreachable_patterns)] + _ => Err(err_msg!(Unsupported, "Unsupported signature type")), + } + } +} + +impl KeyCapVerify for Ed25519KeyPair { + fn key_verify( + &self, + data: &[u8], + signature: &[u8], + sig_type: Option, + sig_format: Option, + ) -> Result { + match sig_type { + None | Some(SignatureType::Ed25519) => match sig_format { + None | Some(SignatureFormat::Base58) => { + let mut sig = [0u8; 64]; + bs58::decode(signature) + .into(&mut sig) + .map_err(|_| err_msg!("Invalid base58 signature"))?; + Ok(self.verify(data, sig)) + } + #[allow(unreachable_patterns)] + _ => Err(err_msg!(Unsupported, "Unsupported signature format")), + }, + #[allow(unreachable_patterns)] + _ => Err(err_msg!(Unsupported, "Unsupported signature type")), + } + } +} + +impl TryFrom<&AnyPrivateKey> for Ed25519KeyPair { + type Error = Error; + + fn try_from(value: &AnyPrivateKey) -> Result { + if value.alg == KeyAlg::ED25519 { + Self::from_bytes(value.data.as_ref()) + } else { + Err(err_msg!(Unsupported, "Expected ed25519 key type")) + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Ed25519PublicKey(PublicKey); + +impl Ed25519PublicKey { + pub fn from_str(key: impl AsRef) -> Result { + let key = key.as_ref(); + let key = key.strip_suffix(":ed25519").unwrap_or(key); + let mut bval = [0u8; 32]; + bs58::decode(key) + .into(&mut bval) + .map_err(|_| err_msg!("Invalid base58 public key"))?; + Self::from_bytes(bval) + } + + pub fn from_bytes(pk: impl AsRef<[u8]>) -> Result { + let pk = + PublicKey::from_bytes(pk.as_ref()).map_err(|_| err_msg!("Invalid public key bytes"))?; + Ok(Self(pk)) + } + + pub fn to_base58(&self) -> String { + bs58::encode(self.to_bytes()).into_string() + } + + pub fn to_string(&self) -> String { + let mut sval = String::with_capacity(64); + bs58::encode(self.to_bytes()).into(&mut sval).unwrap(); + sval.push_str(":ed25519"); + sval + } + + pub fn to_bytes(&self) -> [u8; 32] { + self.0.to_bytes() + } + + pub fn to_jwk(&self) -> Result { + unimplemented!(); + } + + pub fn to_x25519(&self) -> X25519PublicKey { + let public = XPublicKey::from( + CompressedEdwardsY(self.0.to_bytes()) + .decompress() + .unwrap() + .to_montgomery() + .to_bytes(), + ); + X25519PublicKey(public) + } + + pub fn verify(&self, message: &[u8], signature: [u8; 64]) -> bool { + self.0.verify_strict(message, &signature.into()).is_ok() + } +} + +impl KeyCapVerify for Ed25519PublicKey { + fn key_verify( + &self, + data: &[u8], + signature: &[u8], + sig_type: Option, + sig_format: Option, + ) -> Result { + match sig_type { + None | Some(SignatureType::Ed25519) => match sig_format { + None | Some(SignatureFormat::Base58) => { + let mut sig = [0u8; 64]; + bs58::decode(signature) + .into(&mut sig) + .map_err(|_| err_msg!("Invalid base58 signature"))?; + Ok(self.verify(data, sig)) + } + #[allow(unreachable_patterns)] + _ => Err(err_msg!(Unsupported, "Unsupported signature format")), + }, + #[allow(unreachable_patterns)] + _ => Err(err_msg!(Unsupported, "Unsupported signature type")), + } + } +} + +impl TryFrom<&AnyPublicKey> for Ed25519PublicKey { + type Error = Error; + + fn try_from(value: &AnyPublicKey) -> Result { + if value.alg == KeyAlg::ED25519 { + Self::from_bytes(&value.data) + } else { + Err(err_msg!(Unsupported, "Expected ed25519 key type")) + } + } +} + +#[derive(Clone)] +pub struct X25519KeyPair(XSecretKey, XPublicKey); + +impl X25519KeyPair { + pub fn generate() -> Result { + let sk = XSecretKey::new(OsRng); + let pk = XPublicKey::from(&sk); + Ok(Self(sk, pk)) + } + + pub fn private_key(&self) -> SecretBytes { + SecretBytes::from(self.0.to_bytes().to_vec()) + } + + pub fn public_key(&self) -> X25519PublicKey { + X25519PublicKey(self.1.clone()) + } + + pub fn key_exchange_with(&self, pk: &X25519PublicKey) -> SecretBytes { + let xk = self.0.diffie_hellman(&pk.0); + SecretBytes::from(xk.as_bytes().to_vec()) + } + + pub fn from_bytes(pair: &[u8]) -> Result { + if pair.len() != KEYPAIR_LENGTH { + return Err(err_msg!("Invalid keypair bytes")); + } + let sk: [u8; 32] = pair[..32].try_into().unwrap(); + let pk: [u8; 32] = pair[32..].try_into().unwrap(); + Ok(Self(XSecretKey::from(sk), XPublicKey::from(pk))) + } + + pub fn to_bytes(&self) -> SecretBytes { + let mut bytes = Vec::with_capacity(KEYPAIR_LENGTH); + bytes.extend_from_slice(&self.0.to_bytes()); + bytes.extend_from_slice(self.1.as_bytes()); + SecretBytes::from(bytes) + } +} + +impl TryFrom<&AnyPrivateKey> for X25519KeyPair { + type Error = Error; + + fn try_from(value: &AnyPrivateKey) -> Result { + if value.alg == KeyAlg::X25519 { + Self::from_bytes(value.data.as_ref()) + } else { + Err(err_msg!(Unsupported, "Expected x25519 key type")) + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct X25519PublicKey(XPublicKey); + +impl X25519PublicKey { + pub fn from_str(key: impl AsRef) -> Result { + let key = key.as_ref(); + let key = key.strip_suffix(":x25519").unwrap_or(key); + let mut bval = [0u8; 32]; + bs58::decode(key) + .into(&mut bval) + .map_err(|_| err_msg!("Invalid base58 public key"))?; + Self::from_bytes(bval) + } + + pub fn from_bytes(pk: impl AsRef<[u8]>) -> Result { + let pk: [u8; 32] = pk + .as_ref() + .try_into() + .map_err(|_| err_msg!("Invalid public key bytes"))?; + Ok(Self(XPublicKey::from(pk))) + } + + pub fn to_base58(&self) -> String { + bs58::encode(self.to_bytes()).into_string() + } + + pub fn to_string(&self) -> String { + let mut sval = String::with_capacity(64); + bs58::encode(self.to_bytes()).into(&mut sval).unwrap(); + sval.push_str(":x25519"); + sval + } + + pub fn to_bytes(&self) -> [u8; 32] { + self.0.to_bytes() + } +} + +impl TryFrom<&AnyPublicKey> for X25519PublicKey { + type Error = Error; + + fn try_from(value: &AnyPublicKey) -> Result { + if value.alg == KeyAlg::X25519 { + Self::from_bytes(&value.data) + } else { + Err(err_msg!(Unsupported, "Expected x25519 key type")) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn expand_keypair() { + let seed = b"000000000000000000000000Trustee1"; + let test_sk = hex::decode("3030303030303030303030303030303030303030303030305472757374656531e33aaf381fffa6109ad591fdc38717945f8fabf7abf02086ae401c63e9913097").unwrap(); + + let kp = Ed25519KeyPair::from_seed(seed).unwrap(); + assert_eq!(kp.to_bytes(), test_sk); + + // test round trip + let cmp = Ed25519KeyPair::from_bytes(&test_sk).unwrap(); + assert_eq!(cmp.to_bytes(), test_sk); + } + + #[test] + fn ed25519_to_x25519() { + let test_keypair = hex::decode("1c1179a560d092b90458fe6ab8291215a427fcd6b3927cb240701778ef55201927c96646f2d4632d4fc241f84cbc427fbc3ecaa95becba55088d6c7b81fc5bbf").unwrap(); + let x_sk = hex::decode("08e7286c232ec71b37918533ea0229bf0c75d3db4731df1c5c03c45bc909475f") + .unwrap(); + let x_pk = hex::decode("9b4260484c889158c128796103dc8d8b883977f2ef7efb0facb12b6ca9b2ae3d") + .unwrap(); + let x_pair = Ed25519KeyPair::from_bytes(&test_keypair) + .unwrap() + .to_x25519() + .to_bytes(); + assert_eq!(&x_pair[..32], x_sk); + assert_eq!(&x_pair[32..], x_pk); + } + + #[test] + fn test_sign() { + let test_msg = b"This is a dummy message for use with tests"; + let test_sig = hex::decode("451b5b8e8725321541954997781de51f4142e4a56bab68d24f6a6b92615de5eefb74134138315859a32c7cf5fe5a488bc545e2e08e5eedfd1fb10188d532d808").unwrap(); + + let test_keypair = hex::decode("1c1179a560d092b90458fe6ab8291215a427fcd6b3927cb240701778ef55201927c96646f2d4632d4fc241f84cbc427fbc3ecaa95becba55088d6c7b81fc5bbf").unwrap(); + let kp = Ed25519KeyPair::from_bytes(&test_keypair).unwrap(); + let sig = kp.sign(&test_msg[..]); + assert_eq!(sig, test_sig.as_slice()); + assert_eq!(kp.public_key().verify(&test_msg[..], sig), true); + assert_eq!(kp.public_key().verify(b"Not the message", sig), false); + assert_eq!(kp.public_key().verify(&test_msg[..], [0u8; 64]), false); + } + + #[test] + fn key_exchange_random() { + let kp1 = X25519KeyPair::generate().unwrap(); + let kp2 = X25519KeyPair::generate().unwrap(); + assert_ne!(kp1.to_bytes(), kp2.to_bytes()); + + let xch1 = kp1.key_exchange_with(&kp2.public_key()); + let xch2 = kp2.key_exchange_with(&kp1.public_key()); + assert_eq!(xch1, xch2); + + // test round trip + let xch3 = X25519KeyPair::from_bytes(&kp1.to_bytes()) + .unwrap() + .key_exchange_with(&kp2.public_key()); + assert_eq!(xch3, xch1); + } +} diff --git a/src/keys/alg/mod.rs b/src/keys/alg/mod.rs new file mode 100644 index 00000000..39f3af01 --- /dev/null +++ b/src/keys/alg/mod.rs @@ -0,0 +1 @@ +pub mod edwards; diff --git a/src/keys/any.rs b/src/keys/any.rs new file mode 100644 index 00000000..edd951c1 --- /dev/null +++ b/src/keys/any.rs @@ -0,0 +1,84 @@ +use std::convert::TryFrom; + +use super::alg::edwards::Ed25519KeyPair; +use super::caps::{ + KeyAlg, KeyCapGetPublic, KeyCapSign, KeyCategory, SignatureFormat, SignatureType, +}; +use super::store::KeyEntry; +use crate::error::Error; +use crate::types::SecretBytes; + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct AnyPublicKey { + pub alg: KeyAlg, + pub data: Vec, +} + +impl TryFrom for AnyPublicKey { + type Error = Error; + + fn try_from(value: KeyEntry) -> Result { + if value.category == KeyCategory::PublicKey { + if let Some(data) = value.params.data { + Ok(AnyPublicKey { + alg: value.params.alg, + data: data.into_vec(), + }) + } else { + Err(err_msg!(Unsupported, "Missing public key raw data")) + } + } else { + Err(err_msg!(Unsupported, "Not a public key entry")) + } + } +} + +#[derive(Debug)] +pub struct AnyPrivateKey { + pub alg: KeyAlg, + pub data: SecretBytes, +} + +impl KeyCapGetPublic for AnyPrivateKey { + fn key_get_public(&self, alg: Option) -> Result { + unimplemented!(); + } +} + +impl KeyCapSign for AnyPrivateKey { + fn key_sign( + &self, + data: &[u8], + sig_type: Option, + sig_format: Option, + ) -> Result, Error> { + match self.alg { + KeyAlg::ED25519 => { + Ed25519KeyPair::try_from(self).and_then(|k| k.key_sign(data, sig_type, sig_format)) + } + _ => Err(err_msg!( + Unsupported, + "Signing not supported for this key type" + )), + } + } +} + +impl TryFrom for AnyPrivateKey { + type Error = Error; + + fn try_from(value: KeyEntry) -> Result { + if value.category == KeyCategory::PrivateKey { + if let Some(data) = value.params.data { + Ok(AnyPrivateKey { + alg: value.params.alg, + data, + }) + } else { + Err(err_msg!(Unsupported, "Missing private key raw data")) + } + } else { + Err(err_msg!(Unsupported, "Not a private key entry")) + } + } +} diff --git a/src/keys/caps.rs b/src/keys/caps.rs new file mode 100644 index 00000000..f308d1e8 --- /dev/null +++ b/src/keys/caps.rs @@ -0,0 +1,193 @@ +use std::convert::Infallible; +use std::fmt::{self, Display, Formatter}; +use std::str::FromStr; + +use zeroize::Zeroize; + +use super::any::AnyPublicKey; +use crate::error::Error; + +/// Supported key algorithms +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Zeroize)] +pub enum KeyAlg { + /// Curve25519 signing key + ED25519, + /// Curve25519 diffie-hellman key exchange key + X25519, + // /// Elliptic Curve diffie-hellman key exchange key + // Ecdh(EcCurves), + // /// Elliptic Curve signing key + // Ecdsa(EcCurves), + /// BLS12-1381 signing key in group G1 or G2 + // BLS12_1381(BlsGroup), + /// Unrecognized algorithm + Other(String), +} + +serde_as_str_impl!(KeyAlg); + +impl KeyAlg { + /// Get a reference to a string representing the `KeyAlg` + pub fn as_str(&self) -> &str { + match self { + Self::ED25519 => "ed25519", + Self::X25519 => "x25519", + Self::Other(other) => other.as_str(), + } + } +} + +impl AsRef for KeyAlg { + fn as_ref(&self) -> &str { + self.as_str() + } +} + +impl FromStr for KeyAlg { + type Err = Infallible; + + fn from_str(s: &str) -> Result { + Ok(match s { + "ed25519" => Self::ED25519, + "x25519" => Self::X25519, + other => Self::Other(other.to_owned()), + }) + } +} + +impl Display for KeyAlg { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +/// Categories of keys supported by the default KMS +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Zeroize)] +pub enum KeyCategory { + /// A private key or keypair + PrivateKey, + /// A public key + PublicKey, + /// An unrecognized key category + Other(String), +} + +impl KeyCategory { + /// Get a reference to a string representing the `KeyCategory` + pub fn as_str(&self) -> &str { + match self { + Self::PrivateKey => "private", + Self::PublicKey => "public", + Self::Other(other) => other.as_str(), + } + } + + /// Convert the `KeyCategory` into an owned string + pub fn into_string(self) -> String { + match self { + Self::Other(other) => other, + _ => self.as_str().to_owned(), + } + } +} + +serde_as_str_impl!(KeyCategory); + +impl AsRef for KeyCategory { + fn as_ref(&self) -> &str { + self.as_str() + } +} + +impl FromStr for KeyCategory { + type Err = Infallible; + + fn from_str(s: &str) -> Result { + Ok(match s { + "private" => Self::PrivateKey, + "public" => Self::PublicKey, + other => Self::Other(other.to_owned()), + }) + } +} + +impl Display for KeyCategory { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +pub trait KeyCapGetPublic { + fn key_get_public(&self, alg: Option) -> Result; +} + +pub trait KeyCapSign { + fn key_sign( + &self, + data: &[u8], + sig_type: Option, + sig_format: Option, + ) -> Result, Error>; +} + +pub trait KeyCapVerify { + fn key_verify( + &self, + data: &[u8], + signature: &[u8], + sig_type: Option, + sig_format: Option, + ) -> Result; +} + +pub enum SignatureFormat { + /// Base58-encoded binary signature + Base58, +} + +pub enum SignatureType { + // Bls12_1381(BlsGroup), + /// Standard signature output for ed25519 + Ed25519, + // Ecdsa(EcdsaMethod), +} + +impl SignatureType { + pub const fn signature_size(&self) -> usize { + match self { + // Self::Bls12_1381(BlsGroup::G1) => 48, + // Self::Bls12_1381(BlsGroup::G2) => 96, + Self::Ed25519 => 64, + // Self::Ecdsa(_) => , + } + } +} + +// pub enum BlsGroup { +// /// A key or signature represented by an element from the G1 group +// G1, +// /// A key or signature represented by an element from the G2 group +// G2, +// } + +// pub enum EcdsaMethod { +// /// Sign/verify ECC signatures using SHA2-256 +// Sha256, +// /// Sign/verify ECC signatures using SHA2-384 +// Sha384, +// /// Sign/verify ECC signatures using SHA2-512 +// Sha512, +// } + +// /// Possibly supported curves for ECC operations +// #[derive(Clone, Copy, Debug)] +// pub enum EcCurves { +// /// NIST P-256 curve +// Secp256r1, +// /// NIST P-384 curve +// Secp384r1, +// /// NIST P-512 curve +// Secp512r1, +// /// Koblitz 256 curve +// Secp256k1, +// } diff --git a/src/keys/encrypt.rs b/src/keys/encrypt.rs index 612f1a7f..3ba91528 100644 --- a/src/keys/encrypt.rs +++ b/src/keys/encrypt.rs @@ -5,7 +5,9 @@ use serde::{Deserialize, Serialize}; use crate::error::Result; use crate::types::SecretBytes; -pub trait SymEncryptKey: Clone + Debug + Eq + Sized + Serialize + for<'a> Deserialize<'a> { +pub trait SymEncryptKey: + Clone + Debug + Eq + Sized + Serialize + for<'de> Deserialize<'de> +{ const SIZE: usize; fn as_bytes(&self) -> &[u8]; @@ -18,7 +20,7 @@ pub trait SymEncryptKey: Clone + Debug + Eq + Sized + Serialize + for<'a> Deseri } pub trait SymEncryptHashKey: - Clone + Debug + Eq + Sized + Serialize + for<'a> Deserialize<'a> + Clone + Debug + Eq + Sized + Serialize + for<'de> Deserialize<'de> { const SIZE: usize; @@ -68,10 +70,11 @@ pub(crate) mod aead { ChaCha20Poly1305, }; use hmac::{Hmac, Mac, NewMac}; - use indy_utils::{keys::ArrayKey, random::random_deterministic}; use sha2::Sha256; use super::{Result, SecretBytes, SymEncrypt, SymEncryptHashKey, SymEncryptKey}; + use crate::keys::types::ArrayKey; + use crate::random::random_deterministic; pub type ChaChaEncrypt = AeadEncrypt; @@ -93,8 +96,8 @@ pub(crate) mod aead { return Err(err_msg!(Encryption, "Invalid length for seed")); } let input = ArrayKey::from_slice(seed); - let raw_key = ArrayKey::from_slice(&random_deterministic(&input, L::USIZE)); - Ok(ArrayKey::from(raw_key)) + let raw_key = SecretBytes::from(random_deterministic(&input, L::USIZE)); + Ok(ArrayKey::from_slice(&raw_key)) } fn random_key() -> Self { diff --git a/src/keys/kdf/argon2.rs b/src/keys/kdf/argon2.rs index 2bf047e6..3a909635 100644 --- a/src/keys/kdf/argon2.rs +++ b/src/keys/kdf/argon2.rs @@ -1,8 +1,8 @@ -use indy_utils::random::random_vec; use zeroize::Zeroize; use crate::error::Result; use crate::keys::{encrypt::aead::ChaChaEncrypt, store::EncKey}; +use crate::random::random_vec; pub const LEVEL_INTERACTIVE: &'static str = "13:int"; pub const LEVEL_MODERATE: &'static str = "13:mod"; diff --git a/src/keys/mod.rs b/src/keys/mod.rs index 1a10b3fc..ab3e8135 100644 --- a/src/keys/mod.rs +++ b/src/keys/mod.rs @@ -2,7 +2,6 @@ use std::collections::HashMap; use std::sync::Arc; use async_mutex::Mutex; -use indy_utils::keys::{EncodedVerKey, PrivateKey}; use zeroize::Zeroize; use super::error::Result; @@ -12,46 +11,45 @@ use super::types::{EncEntryTag, EntryTag, ProfileId, SecretBytes}; use self::store::StoreKey; use self::wrap::WrapKey; +pub mod any; +pub use self::any::{AnyPrivateKey, AnyPublicKey}; + +pub mod alg; +use self::alg::edwards::{Ed25519KeyPair, Ed25519PublicKey}; + +pub mod caps; +pub use self::caps::{ + KeyAlg, KeyCapGetPublic, KeyCapSign, KeyCapVerify, KeyCategory, SignatureFormat, SignatureType, +}; + pub mod encrypt; pub mod kdf; pub mod store; +pub use self::store::{KeyEntry, KeyParams}; mod types; -pub use self::types::{KeyAlg, KeyCategory, KeyEntry, KeyParams, PassKey}; +pub use self::types::PassKey; pub mod wrap; -// #[cfg(target_os = "macos")] -// mod keychain; - /// Derive the (public) verification key for a keypair pub fn derive_verkey(alg: KeyAlg, seed: &[u8]) -> Result { match alg { KeyAlg::ED25519 => (), _ => return Err(err_msg!(Unsupported, "Unsupported key algorithm")), } - - let sk = - PrivateKey::from_seed(seed).map_err(err_map!(Unexpected, "Error generating keypair"))?; - let pk = sk - .public_key() - .map_err(err_map!(Unexpected, "Error generating public key"))? - .as_base58() - .map_err(err_map!(Unexpected, "Error encoding public key"))? - .long_form(); + let sk = Ed25519KeyPair::from_seed(seed) + .map_err(err_map!(Unexpected, "Error generating keypair"))?; + let pk = sk.public_key().to_string(); Ok(pk) } /// Verify that a message signature is consistent with the signer's key pub fn verify_signature(signer_vk: &str, data: &[u8], signature: &[u8]) -> Result { - let vk = EncodedVerKey::from_str(&signer_vk).map_err(err_map!("Invalid verkey"))?; - Ok(vk - .decode() - .map_err(err_map!("Unsupported verkey"))? - .verify_signature(&data, &signature) - .unwrap_or(false)) + let vk = Ed25519PublicKey::from_str(&signer_vk).map_err(err_map!("Invalid verkey"))?; + vk.key_verify(data, signature, None, None) } #[derive(Debug)] diff --git a/src/keys/store.rs b/src/keys/store.rs index 19ab163a..486f4915 100644 --- a/src/keys/store.rs +++ b/src/keys/store.rs @@ -2,10 +2,11 @@ use std::fmt::Debug; use serde::{Deserialize, Serialize}; +use super::caps::{KeyAlg, KeyCategory}; use super::encrypt::{aead::ChaChaEncrypt, SymEncrypt, SymEncryptHashKey, SymEncryptKey}; -use crate::error::Result; +use crate::error::Error; use crate::keys::EntryEncryptor; -use crate::types::{EncEntryTag, EntryTag, SecretBytes}; +use crate::types::{sorted_tags, EncEntryTag, EntryTag, SecretBytes}; pub type EncKey = ::Key; pub type HashKey = ::HashKey; @@ -29,7 +30,7 @@ pub struct StoreKeyImpl { } impl StoreKeyImpl { - pub fn new() -> Result { + pub fn new() -> Result { Ok(Self { category_key: E::Key::random_key(), name_key: E::Key::random_key(), @@ -41,27 +42,27 @@ impl StoreKeyImpl { }) } - pub fn encrypt_tag_name(&self, name: SecretBytes) -> Result> { + pub fn encrypt_tag_name(&self, name: SecretBytes) -> Result, Error> { encrypt_searchable::(name, &self.tag_name_key, &self.tags_hmac_key) } - pub fn encrypt_tag_value(&self, value: SecretBytes) -> Result> { + pub fn encrypt_tag_value(&self, value: SecretBytes) -> Result, Error> { encrypt_searchable::(value, &self.tag_value_key, &self.tags_hmac_key) } - pub fn decrypt_tag_name(&self, enc_tag_name: Vec) -> Result { + pub fn decrypt_tag_name(&self, enc_tag_name: Vec) -> Result { E::decrypt(enc_tag_name, &self.tag_name_key) } - pub fn decrypt_tag_value(&self, enc_tag_value: Vec) -> Result { + pub fn decrypt_tag_value(&self, enc_tag_value: Vec) -> Result { E::decrypt(enc_tag_value, &self.tag_value_key) } - pub fn to_string(&self) -> Result { + pub fn to_string(&self) -> Result { serde_json::to_string(self).map_err(err_map!(Unexpected, "Error serializing store key")) } - pub fn from_slice(input: &[u8]) -> Result { + pub fn from_slice(input: &[u8]) -> Result { serde_json::from_slice(input).map_err(err_map!(Unsupported, "Invalid store key")) } } @@ -84,7 +85,7 @@ fn encrypt_searchable( input: SecretBytes, enc_key: &E::Key, hmac_key: &E::HashKey, -) -> Result> { +) -> Result, Error> { let nonce = E::hashed_nonce(&input, hmac_key)?; E::encrypt(input, enc_key, Some(nonce)) } @@ -97,15 +98,15 @@ where E::prepare_input(input) } - fn encrypt_entry_category(&self, category: SecretBytes) -> Result> { + fn encrypt_entry_category(&self, category: SecretBytes) -> Result, Error> { encrypt_searchable::(category, &self.category_key, &self.item_hmac_key) } - fn encrypt_entry_name(&self, name: SecretBytes) -> Result> { + fn encrypt_entry_name(&self, name: SecretBytes) -> Result, Error> { encrypt_searchable::(name, &self.name_key, &self.item_hmac_key) } - fn encrypt_entry_value(&self, value: SecretBytes) -> Result> { + fn encrypt_entry_value(&self, value: SecretBytes) -> Result, Error> { let value_key = E::Key::random_key(); let mut value = E::encrypt(value, &value_key, None)?; let key_input = E::prepare_input(value_key.as_bytes()); @@ -114,15 +115,15 @@ where Ok(result) } - fn decrypt_entry_category(&self, enc_category: Vec) -> Result { + fn decrypt_entry_category(&self, enc_category: Vec) -> Result { decode_utf8(E::decrypt(enc_category, &self.category_key)?.into_vec()) } - fn decrypt_entry_name(&self, enc_name: Vec) -> Result { + fn decrypt_entry_name(&self, enc_name: Vec) -> Result { decode_utf8(E::decrypt(enc_name, &self.name_key)?.into_vec()) } - fn decrypt_entry_value(&self, mut enc_value: Vec) -> Result { + fn decrypt_entry_value(&self, mut enc_value: Vec) -> Result { let enc_key_size = E::encrypted_size(E::Key::SIZE); if enc_value.len() < enc_key_size + E::encrypted_size(0) { return Err(err_msg!( @@ -140,7 +141,7 @@ where E::decrypt(value, &value_key) } - fn encrypt_entry_tags(&self, tags: Vec) -> Result> { + fn encrypt_entry_tags(&self, tags: Vec) -> Result, Error> { tags.into_iter() .map(|tag| match tag { EntryTag::Plaintext(name, value) => { @@ -164,7 +165,7 @@ where .collect() } - fn decrypt_entry_tags(&self, enc_tags: Vec) -> Result> { + fn decrypt_entry_tags(&self, enc_tags: Vec) -> Result, Error> { enc_tags.into_iter().try_fold(vec![], |mut acc, tag| { let name = decode_utf8(self.decrypt_tag_name(tag.name)?.into_vec())?; acc.push(if tag.plaintext { @@ -179,16 +180,103 @@ where } } -#[inline] -fn decode_utf8(value: Vec) -> Result { +#[inline(always)] +fn decode_utf8(value: Vec) -> Result { String::from_utf8(value).map_err(err_map!(Encryption)) } +/// Parameters defining a stored key +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub struct KeyParams { + /// The key algorithm + pub alg: KeyAlg, + + /// Associated key metadata + #[serde(default, rename = "meta", skip_serializing_if = "Option::is_none")] + pub metadata: Option, + + /// An optional external reference for the key + #[serde(default, rename = "ref", skip_serializing_if = "Option::is_none")] + pub reference: Option, + + /// The associated key data in binary format + #[serde( + default, + skip_serializing_if = "Option::is_none", + with = "crate::serde_utils::as_base58" + )] + pub data: Option, +} + +impl KeyParams { + pub(crate) fn to_vec(&self) -> Result, Error> { + serde_json::to_vec(self) + .map_err(|e| err_msg!(Unexpected, "Error serializing key params: {}", e)) + } + + pub(crate) fn from_slice(params: &[u8]) -> Result { + let result = serde_json::from_slice(params) + .map_err(|e| err_msg!(Unexpected, "Error deserializing key params: {}", e)); + result + } +} + +/// A stored key entry +#[derive(Clone, Debug, Eq)] +pub struct KeyEntry { + /// The category of the key entry (public or private) + pub category: KeyCategory, + /// The key entry identifier + pub ident: String, + /// The parameters defining the key + pub params: KeyParams, + /// Tags associated with the key entry record + pub tags: Option>, +} + +impl KeyEntry { + /// Determine if a key entry refers to a local or external key + pub fn is_local(&self) -> bool { + self.params.reference.is_none() + } + + /// Fetch the associated key data + pub fn key_data(&self) -> Option<&[u8]> { + self.params.data.as_ref().map(AsRef::as_ref) + } + + pub(crate) fn sorted_tags(&self) -> Option> { + self.tags.as_ref().and_then(sorted_tags) + } +} + +impl PartialEq for KeyEntry { + fn eq(&self, rhs: &Self) -> bool { + self.category == rhs.category + && self.ident == rhs.ident + && self.params == rhs.params + && self.sorted_tags() == rhs.sorted_tags() + } +} + #[cfg(test)] mod tests { use super::*; use crate::types::Entry; + #[test] + fn key_params_roundtrip() { + let params = KeyParams { + alg: KeyAlg::ED25519, + metadata: Some("meta".to_string()), + reference: None, + data: Some(SecretBytes::from(vec![0, 0, 0, 0])), + }; + let enc_params = params.to_vec().unwrap(); + let p2 = KeyParams::from_slice(&enc_params).unwrap(); + assert_eq!(p2, params); + } + #[test] fn store_key_round_trip() { let key = StoreKey::new().unwrap(); diff --git a/src/keys/types.rs b/src/keys/types.rs index 6abd152d..46ad4d60 100644 --- a/src/keys/types.rs +++ b/src/keys/types.rs @@ -1,246 +1,119 @@ use std::borrow::Cow; -use std::convert::Infallible; -use std::fmt::{self, Debug, Display, Formatter}; +use std::cmp::Ordering; +use std::fmt::{self, Debug, Formatter}; use std::mem::ManuallyDrop; use std::ops::Deref; -use std::ptr; -use std::str::FromStr; -use indy_utils::keys::{EncodedVerKey, KeyType as IndyKeyAlg, PrivateKey, VerKey}; -use serde::{Deserialize, Serialize}; +use aead::generic_array::{ArrayLength, GenericArray}; +use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; use zeroize::Zeroize; -use crate::error::Error; -use crate::types::{sorted_tags, EntryTag, SecretBytes}; +use crate::random::random_array; -/// Supported key algorithms -#[derive(Clone, Debug, PartialEq, Eq, Zeroize)] -pub enum KeyAlg { - /// curve25519-based signature scheme - ED25519, - /// Unrecognized algorithm - Other(String), -} +/// A secure key representation for fixed-length keys +#[derive(Clone, Debug, Hash, Zeroize)] +pub struct ArrayKey>(GenericArray); -serde_as_str_impl!(KeyAlg); +impl> ArrayKey { + pub const SIZE: usize = L::USIZE; -impl KeyAlg { - /// Get a reference to a string representing the `KeyAlg` - pub fn as_str(&self) -> &str { - match self { - Self::ED25519 => "ed25519", - Self::Other(other) => other.as_str(), - } + #[inline] + pub fn from_slice>(data: D) -> Self { + Self(GenericArray::clone_from_slice(data.as_ref())) } -} -impl AsRef for KeyAlg { - fn as_ref(&self) -> &str { - self.as_str() + #[inline] + pub fn extract(self) -> GenericArray { + self.0 } -} - -impl FromStr for KeyAlg { - type Err = Infallible; - fn from_str(s: &str) -> Result { - Ok(match s { - "ed25519" => Self::ED25519, - other => Self::Other(other.to_owned()), - }) + #[inline] + pub fn random() -> Self { + Self(random_array()) } } -impl Display for KeyAlg { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.write_str(self.as_str()) +impl> Default for ArrayKey { + #[inline] + fn default() -> Self { + Self(GenericArray::default()) } } -/// Categories of keys supported by the default KMS -#[derive(Clone, Debug, PartialEq, Eq, Zeroize)] -pub enum KeyCategory { - /// A public key - PublicKey, - /// A combination of a public and private key - KeyPair, - /// An unrecognized key category - Other(String), -} - -impl KeyCategory { - /// Get a reference to a string representing the `KeyCategory` - pub fn as_str(&self) -> &str { - match self { - Self::PublicKey => "public", - Self::KeyPair => "keypair", - Self::Other(other) => other.as_str(), - } - } - - /// Convert the `KeyCategory` into an owned string - pub fn into_string(self) -> String { - match self { - Self::Other(other) => other, - _ => self.as_str().to_owned(), - } +impl> From> for ArrayKey { + fn from(key: GenericArray) -> Self { + Self(key) } } -serde_as_str_impl!(KeyCategory); - -impl AsRef for KeyCategory { - fn as_ref(&self) -> &str { - self.as_str() +impl> std::ops::Deref for ArrayKey { + type Target = GenericArray; + fn deref(&self) -> &Self::Target { + &self.0 } } -impl FromStr for KeyCategory { - type Err = Infallible; - - fn from_str(s: &str) -> Result { - Ok(match s { - "public" => Self::PublicKey, - "keypair" => Self::KeyPair, - other => Self::Other(other.to_owned()), - }) +impl> std::ops::DerefMut for ArrayKey { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 } } -impl Display for KeyCategory { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.write_str(self.as_str()) +impl> PartialEq for ArrayKey { + fn eq(&self, other: &Self) -> bool { + **self == **other } } +impl> Eq for ArrayKey {} -/// Parameters defining a stored key -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] -pub struct KeyParams { - /// The key algorithm - pub alg: KeyAlg, - - /// Associated key metadata - #[serde(default, rename = "meta", skip_serializing_if = "Option::is_none")] - pub metadata: Option, - - /// An optional external reference for the key - #[serde(default, rename = "ref", skip_serializing_if = "Option::is_none")] - pub reference: Option, - #[serde( - default, - rename = "pub", - skip_serializing_if = "Option::is_none", - with = "crate::serde_utils::as_base58" - )] - - /// The associated public key in binary format - pub pub_key: Option>, - - /// The associated private key in binary format - #[serde( - default, - rename = "prv", - skip_serializing_if = "Option::is_none", - with = "crate::serde_utils::as_base58" - )] - pub prv_key: Option, -} - -impl KeyParams { - pub(crate) fn to_vec(&self) -> Result, Error> { - serde_json::to_vec(self) - .map_err(|e| err_msg!(Unexpected, "Error serializing key params: {}", e)) +impl> PartialOrd for ArrayKey { + fn partial_cmp(&self, other: &Self) -> Option { + self.0.partial_cmp(&*other) } - - pub(crate) fn from_slice(params: &[u8]) -> Result { - let result = serde_json::from_slice(params) - .map_err(|e| err_msg!(Unexpected, "Error deserializing key params: {}", e)); - result +} +impl> Ord for ArrayKey { + fn cmp(&self, other: &Self) -> Ordering { + self.0.cmp(&*other) } } -impl Drop for KeyParams { - fn drop(&mut self) { - self.zeroize() +impl> Serialize for ArrayKey { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(hex::encode(&self.0.as_slice()).as_str()) } } -impl Zeroize for KeyParams { - fn zeroize(&mut self) { - self.prv_key.zeroize(); +impl<'a, L: ArrayLength> Deserialize<'a> for ArrayKey { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'a>, + { + deserializer.deserialize_str(KeyVisitor { + _pd: std::marker::PhantomData, + }) } } -/// A stored key entry -#[derive(Clone, Debug, Eq)] -pub struct KeyEntry { - /// The category of the key entry (public or public/private pair) - pub category: KeyCategory, - /// The key entry identifier - pub ident: String, - /// The parameters defining the key - pub params: KeyParams, - /// Tags associated with the key entry record - pub tags: Option>, +struct KeyVisitor> { + _pd: std::marker::PhantomData, } -impl KeyEntry { - pub(crate) fn into_parts(self) -> (KeyCategory, String, KeyParams, Option>) { - let slf = ManuallyDrop::new(self); - unsafe { - ( - ptr::read(&slf.category), - ptr::read(&slf.ident), - ptr::read(&slf.params), - ptr::read(&slf.tags), - ) - } - } +impl<'a, L: ArrayLength> Visitor<'a> for KeyVisitor { + type Value = ArrayKey; - /// Determine if a key entry refers to a local or external key - pub fn is_local(&self) -> bool { - self.params.reference.is_none() + fn expecting(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str(stringify!($name)) } - /// Access the associated public key as an [`EncodedVerKey`] - pub fn encoded_verkey(&self) -> Result { - Ok(self - .verkey()? - .as_base58() - .map_err(err_map!(Unexpected, "Error encoding verkey"))?) - } - - /// Access the associated public key as a [`VerKey`] - pub fn verkey(&self) -> Result { - match (&self.params.alg, &self.params.pub_key) { - (KeyAlg::ED25519, Some(pub_key)) => Ok(VerKey::new(pub_key, Some(IndyKeyAlg::ED25519))), - (_, None) => Err(err_msg!(Input, "Undefined public key")), - _ => Err(err_msg!(Unsupported, "Unsupported key algorithm")), - } - } - - /// Access the associated private key as a [`PrivateKey`] - pub fn private_key(&self) -> Result { - match (&self.params.alg, &self.params.prv_key) { - (KeyAlg::ED25519, Some(prv_key)) => { - Ok(PrivateKey::new(prv_key, Some(IndyKeyAlg::ED25519))) - } - (_, None) => Err(err_msg!(Input, "Undefined private key")), - _ => Err(err_msg!(Unsupported, "Unsupported key algorithm")), - } - } - - pub(crate) fn sorted_tags(&self) -> Option> { - self.tags.as_ref().and_then(sorted_tags) - } -} - -impl PartialEq for KeyEntry { - fn eq(&self, rhs: &Self) -> bool { - self.category == rhs.category - && self.ident == rhs.ident - && self.params == rhs.params - && self.sorted_tags() == rhs.sorted_tags() + fn visit_str(self, value: &str) -> Result, E> + where + E: serde::de::Error, + { + let key = hex::decode(value).map_err(E::custom)?; + Ok(ArrayKey(GenericArray::clone_from_slice(key.as_slice()))) } } @@ -337,22 +210,3 @@ impl Zeroize for PassKey<'_> { } } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn key_params_roundtrip() { - let params = KeyParams { - alg: KeyAlg::ED25519, - metadata: Some("meta".to_string()), - reference: None, - pub_key: Some(vec![0, 0, 0, 0]), - prv_key: Some(vec![1, 1, 1, 1].into()), - }; - let enc_params = params.to_vec().unwrap(); - let p2 = KeyParams::from_slice(&enc_params).unwrap(); - assert_eq!(p2, params); - } -} diff --git a/src/lib.rs b/src/lib.rs index b9fd024a..8648abdb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,6 +20,8 @@ extern crate serde; #[cfg(any(feature = "postgres", feature = "sqlite"))] mod db_utils; +mod didcomm; + #[doc(hidden)] pub mod future; @@ -30,6 +32,8 @@ pub mod indy_compat; mod options; +mod random; + #[cfg(feature = "ffi")] #[macro_use] extern crate serde_json; diff --git a/src/random.rs b/src/random.rs new file mode 100644 index 00000000..7eb292b3 --- /dev/null +++ b/src/random.rs @@ -0,0 +1,41 @@ +use aead::generic_array::{ArrayLength, GenericArray}; +use chacha20::{ + cipher::{NewStreamCipher, SyncStreamCipher}, + ChaCha20, +}; +use rand::{rngs::OsRng, RngCore}; + +pub type SeedSize = ::KeySize; + +/// Fill a mutable slice with random data using the +/// system random number generator. +#[inline(always)] +pub fn fill_random(value: &mut [u8]) { + OsRng.fill_bytes(value); +} + +/// Create a new `GenericArray` instance with random data. +#[inline(always)] +pub fn random_array>() -> GenericArray { + let mut buf = GenericArray::default(); + fill_random(buf.as_mut_slice()); + buf +} + +/// Written to be compatible with randombytes_deterministic in libsodium, +/// used to generate a deterministic wallet raw key. +pub fn random_deterministic(seed: &GenericArray, len: usize) -> Vec { + let nonce = GenericArray::from_slice(b"LibsodiumDRG"); + let mut cipher = ChaCha20::new(seed, &nonce); + let mut data = vec![0; len]; + cipher.apply_keystream(data.as_mut_slice()); + data +} + +/// Create a new `Vec` instance with random data. +#[inline(always)] +pub fn random_vec(sz: usize) -> Vec { + let mut buf = vec![0; sz]; + fill_random(buf.as_mut_slice()); + buf +} diff --git a/src/serde_utils.rs b/src/serde_utils.rs index 18f09781..94c1bd04 100644 --- a/src/serde_utils.rs +++ b/src/serde_utils.rs @@ -10,7 +10,7 @@ use super::types::SecretBytes; macro_rules! serde_as_str_impl { ($t:ident) => { - impl Serialize for $t { + impl serde::Serialize for $t { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, @@ -19,7 +19,7 @@ macro_rules! serde_as_str_impl { } } - impl<'de> Deserialize<'de> for $t { + impl<'de> serde::Deserialize<'de> for $t { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, diff --git a/src/store.rs b/src/store.rs index f62e4c0e..80d8fcfd 100644 --- a/src/store.rs +++ b/src/store.rs @@ -1,3 +1,4 @@ +use std::convert::TryInto; use std::fmt::{self, Debug, Formatter}; use std::future::Future; use std::pin::Pin; @@ -5,16 +6,17 @@ use std::str::FromStr; use std::sync::Arc; use futures_lite::stream::{Stream, StreamExt}; -use indy_utils::{ - keys::{EncodedVerKey, KeyType as IndyKeyAlg, PrivateKey}, - pack::{pack_message, unpack_message, KeyLookup}, - Validatable, -}; -use zeroize::Zeroize; +use zeroize::Zeroizing; +use super::didcomm::pack::{pack_message, unpack_message, KeyLookup}; use super::error::Result; use super::future::BoxFuture; -use super::keys::{wrap::WrapKeyMethod, KeyAlg, KeyCategory, KeyEntry, KeyParams, PassKey}; +use super::keys::{ + alg::edwards::{Ed25519KeyPair, Ed25519PublicKey}, + caps::KeyCapSign, + wrap::WrapKeyMethod, + AnyPrivateKey, KeyAlg, KeyCategory, KeyEntry, KeyParams, PassKey, +}; use super::types::{Entry, EntryKind, EntryOperation, EntryTag, TagFilter}; /// Represents a generic backend implementation @@ -386,30 +388,23 @@ impl Session { _ => return Err(err_msg!(Unsupported, "Unsupported key algorithm")), } - let sk = match seed { - None => PrivateKey::generate(Some(IndyKeyAlg::ED25519)), - Some(s) => PrivateKey::from_seed(s), + let keypair = match seed { + None => Ed25519KeyPair::generate(), + Some(s) => Ed25519KeyPair::from_seed(s), } .map_err(err_map!(Unexpected, "Error generating keypair"))?; + let pk = keypair.public_key(); - let pk = sk - .public_key() - .map_err(err_map!(Unexpected, "Error generating public key"))?; - - let category = KeyCategory::KeyPair; - let ident = pk - .as_base58() - .map_err(err_map!(Unexpected, "Error encoding public key"))? - .long_form(); + let category = KeyCategory::PrivateKey; + let ident = pk.to_string(); let params = KeyParams { alg, metadata: metadata.map(str::to_string), reference: None, - pub_key: Some(pk.key_bytes()), - prv_key: Some(sk.key_bytes().into()), + data: Some(keypair.to_bytes()), }; - let mut value = params.to_vec()?; + let value = Zeroizing::new(params.to_vec()?); self.0 .update( @@ -422,7 +417,6 @@ impl Session { None, ) .await?; - value.zeroize(); Ok(KeyEntry { category, @@ -432,10 +426,6 @@ impl Session { }) } - // pub async fn import_key(&self, key: KeyEntry) -> Result<()> { - // Ok(()) - // } - /// Fetch an existing key from the store /// /// Specify `for_update` when in a transaction to create an update lock on the @@ -446,12 +436,6 @@ impl Session { ident: &str, for_update: bool, ) -> Result> { - // normalize ident - let ident = EncodedVerKey::from_str(&ident) - .and_then(|k| k.as_base58()) - .map_err(err_map!("Invalid key"))? - .long_form(); - Ok( if let Some(row) = self .0 @@ -473,12 +457,6 @@ impl Session { /// Remove an existing key from the store pub async fn remove_key(&mut self, category: KeyCategory, ident: &str) -> Result<()> { - // normalize ident - let ident = EncodedVerKey::from_str(&ident) - .and_then(|k| k.as_base58()) - .map_err(err_map!("Invalid key"))? - .long_form(); - self.0 .update( EntryKind::Key, @@ -492,18 +470,6 @@ impl Session { .await } - // pub async fn scan_keys( - // &self, - // profile: Option, - // category: String, - // options: EntryFetchOptions, - // tag_filter: Option, - // offset: Option, - // max_rows: Option, - // ) -> Result> { - // unimplemented!(); - // } - /// Replace the metadata and tags on an existing key in the store pub async fn update_key( &mut self, @@ -512,12 +478,6 @@ impl Session { metadata: Option<&str>, tags: Option<&[EntryTag]>, ) -> Result<()> { - // normalize ident - let ident = EncodedVerKey::from_str(&ident) - .and_then(|k| k.as_base58()) - .map_err(err_map!("Invalid key"))? - .long_form(); - let row = self .0 .fetch(EntryKind::Key, category.as_str(), &ident, true) @@ -526,7 +486,7 @@ impl Session { let mut params = KeyParams::from_slice(&row.value)?; params.metadata = metadata.map(str::to_string); - let mut value = params.to_vec()?; + let value = Zeroizing::new(params.to_vec()?); self.0 .update( @@ -539,26 +499,25 @@ impl Session { None, ) .await?; - value.zeroize(); Ok(()) } - /// Sign a message using an existing keypair in the store identified by `key_ident` + /// Sign a message using an existing private key in the store identified by `key_ident` pub async fn sign_message(&mut self, key_ident: &str, data: &[u8]) -> Result> { if let Some(key) = self - .fetch_key(KeyCategory::KeyPair, key_ident, false) + .fetch_key(KeyCategory::PrivateKey, key_ident, false) .await? { - let sk = key.private_key()?; - sk.sign(&data) + let sk: AnyPrivateKey = key.try_into()?; + sk.key_sign(&data, None, None) .map_err(|e| err_msg!(Unexpected, "Signature error: {}", e)) } else { return Err(err_msg!(NotFound, "Unknown key")); } } - /// Pack a message using an existing keypair in the store identified by `key_ident` + /// Pack a message using an existing private key in the store identified by `key_ident` /// /// This uses the `pack` algorithm defined for DIDComm v1 pub async fn pack_message( @@ -569,22 +528,24 @@ impl Session { ) -> Result> { let sign_key = if let Some(ident) = from_key_ident { let sk = self - .fetch_key(KeyCategory::KeyPair, ident, false) + .fetch_key(KeyCategory::PrivateKey, ident, false) .await? .ok_or_else(|| err_msg!(NotFound, "Unknown sender key"))?; - Some(sk.private_key()?) + let data = sk + .key_data() + .ok_or_else(|| err_msg!(NotFound, "Missing private key data"))?; + Some(Ed25519KeyPair::from_bytes(data)?) } else { None }; let vks = recipient_vks .into_iter() .map(|vk| { - let vk = - EncodedVerKey::from_str(&vk).map_err(err_map!("Invalid recipient verkey"))?; - vk.validate()?; + let vk = Ed25519PublicKey::from_str(&vk) + .map_err(err_map!("Invalid recipient verkey"))?; Ok(vk) }) - .collect::>>()?; + .collect::>>()?; Ok(pack_message(data, vks, sign_key).map_err(err_map!("Error packing message"))?) } @@ -592,7 +553,7 @@ impl Session { pub async fn unpack_message( &mut self, data: &[u8], - ) -> Result<(Vec, EncodedVerKey, Option)> { + ) -> Result<(Vec, Ed25519PublicKey, Option)> { match unpack_message(data, self).await { Ok((message, recip, sender)) => Ok((message, recip, sender)), Err(err) => Err(err_msg!(Unexpected, "Error unpacking message").with_cause(err)), @@ -613,15 +574,17 @@ impl Session { impl<'a, Q: QueryBackend> KeyLookup<'a> for &'a mut Session { fn find<'f>( self, - keys: &'f Vec, - ) -> std::pin::Pin> + Send + 'f>> + keys: &'f Vec, + ) -> std::pin::Pin> + Send + 'f>> where 'a: 'f, { Box::pin(async move { for (idx, key) in keys.into_iter().enumerate() { - if let Ok(Some(key)) = self.fetch_key(KeyCategory::KeyPair, &key.key, false).await { - if let Ok(sk) = key.private_key() { + let ident = key.to_string(); + if let Ok(Some(key)) = self.fetch_key(KeyCategory::PrivateKey, &ident, false).await + { + if let Some(Ok(sk)) = key.key_data().map(Ed25519KeyPair::from_bytes) { return Some((idx, sk)); } } diff --git a/tests/utils/mod.rs b/tests/utils/mod.rs index 8c19ba76..66410b1d 100644 --- a/tests/utils/mod.rs +++ b/tests/utils/mod.rs @@ -506,10 +506,20 @@ pub async fn db_keypair_sign_verify(db: &Store) { ); assert_eq!( - verify_signature(&key_info.ident, &message, b"bad sig").expect(ERR_VERIFY), + verify_signature( + &key_info.ident, + // [0u8; 64] + b"xt19s1sp2UZCGhy9rNyb1FtxdKiDGZZPNFnc1KiM9jYYEuHxuwNeFf1oQKsn8zv6yvYBGhXa83288eF4MqN1oDq", + &sig + ).expect(ERR_VERIFY), false ); + assert_eq!( + verify_signature(&key_info.ident, &message, b"bad sig").is_err(), + true + ); + let err = verify_signature("not a key", &message, &sig).expect_err(ERR_REQ_ERR); assert_eq!(err.kind(), ErrorKind::Input); } @@ -531,7 +541,7 @@ pub async fn db_keypair_pack_unpack_anon(db: &Store) { let (unpacked, p_recip, p_send) = conn.unpack_message(&packed).await.expect(ERR_UNPACK); assert_eq!(unpacked, msg); - assert_eq!(p_recip, recip_key.encoded_verkey().unwrap()); + assert_eq!(p_recip.to_string(), recip_key.ident); assert_eq!(p_send, None); } @@ -560,8 +570,8 @@ pub async fn db_keypair_pack_unpack_auth(db: &Store) { let (unpacked, p_recip, p_send) = conn.unpack_message(&packed).await.expect(ERR_UNPACK); assert_eq!(unpacked, msg); - assert_eq!(p_recip, recip_key.encoded_verkey().unwrap()); - assert_eq!(p_send, Some(sender_key.encoded_verkey().unwrap())); + assert_eq!(p_recip.to_string(), recip_key.ident); + assert_eq!(p_send.map(|k| k.to_string()), Some(sender_key.ident)); } pub async fn db_txn_rollback(db: &Store) { diff --git a/wrappers/python/aries_askar/version.py b/wrappers/python/aries_askar/version.py index 695c0e2b..a310d3d3 100644 --- a/wrappers/python/aries_askar/version.py +++ b/wrappers/python/aries_askar/version.py @@ -1,3 +1,3 @@ """aries_askar library wrapper version.""" -__version__ = "0.1.3" +__version__ = "0.2.0" From 6d296a0255bd8736d28ff6fb3d7bac4b1aae285f Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Fri, 19 Mar 2021 17:55:57 -0700 Subject: [PATCH 002/116] start adding p-256 key type Signed-off-by: Andrew Whitehead --- Cargo.toml | 1 + src/didcomm/pack/alg.rs | 2 +- src/keys/alg/edwards.rs | 108 +++++++---- src/keys/alg/mod.rs | 2 + src/keys/alg/p256.rs | 398 ++++++++++++++++++++++++++++++++++++++++ src/keys/any.rs | 2 +- src/keys/caps.rs | 58 +++--- src/keys/mod.rs | 2 +- src/keys/store.rs | 2 +- src/store.rs | 2 +- tests/utils/mod.rs | 10 +- 11 files changed, 510 insertions(+), 77 deletions(-) create mode 100644 src/keys/alg/p256.rs diff --git a/Cargo.toml b/Cargo.toml index 0ecdf588..c171ffd4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,6 +59,7 @@ log = { version = "0.4", optional = true } num_cpus = { version = "1.0", optional = true } once_cell = "1.5" percent-encoding = "2.0" +p256 = { version = "0.7", features = ["ecdsa", "ecdh"] } rand = "0.7" rmp-serde = "0.14" rust-argon2 = "0.8" diff --git a/src/didcomm/pack/alg.rs b/src/didcomm/pack/alg.rs index 29cd6125..102cfd7f 100644 --- a/src/didcomm/pack/alg.rs +++ b/src/didcomm/pack/alg.rs @@ -123,7 +123,7 @@ fn b64_decode(input: impl AsRef<[u8]>) -> Result, Error> { #[inline(always)] fn b64_encode(input: impl AsRef<[u8]>) -> String { - base64::encode_config(input, base64::URL_SAFE) + base64::encode_config(input, base64::URL_SAFE_NO_PAD) } fn b64_encode_protected( diff --git a/src/keys/alg/edwards.rs b/src/keys/alg/edwards.rs index 1237c17f..9886287c 100644 --- a/src/keys/alg/edwards.rs +++ b/src/keys/alg/edwards.rs @@ -3,6 +3,7 @@ use std::convert::{TryFrom, TryInto}; use curve25519_dalek::edwards::CompressedEdwardsY; use ed25519_dalek::{Keypair, PublicKey, SecretKey, Signer, KEYPAIR_LENGTH}; use rand::rngs::OsRng; +use serde_json::json; use sha2::{self, Digest}; use x25519_dalek::{PublicKey as XPublicKey, StaticSecret as XSecretKey}; @@ -58,7 +59,7 @@ impl Ed25519KeyPair { } pub fn sign(&self, message: &[u8]) -> [u8; 64] { - self.0.sign(&message).to_bytes() + self.0.sign(message).to_bytes() } pub fn verify(&self, message: &[u8], signature: [u8; 64]) -> bool { @@ -83,13 +84,10 @@ impl KeyCapSign for Ed25519KeyPair { sig_format: Option, ) -> Result, Error> { match sig_type { - None | Some(SignatureType::Ed25519) => match sig_format { - None | Some(SignatureFormat::Base58) => { - Ok(bs58::encode(self.sign(data)).into_vec()) - } - #[allow(unreachable_patterns)] - _ => Err(err_msg!(Unsupported, "Unsupported signature format")), - }, + None | Some(SignatureType::EdDSA) => { + let sig = self.sign(data); + encode_signature(&sig, sig_format) + } #[allow(unreachable_patterns)] _ => Err(err_msg!(Unsupported, "Unsupported signature type")), } @@ -105,17 +103,11 @@ impl KeyCapVerify for Ed25519KeyPair { sig_format: Option, ) -> Result { match sig_type { - None | Some(SignatureType::Ed25519) => match sig_format { - None | Some(SignatureFormat::Base58) => { - let mut sig = [0u8; 64]; - bs58::decode(signature) - .into(&mut sig) - .map_err(|_| err_msg!("Invalid base58 signature"))?; - Ok(self.verify(data, sig)) - } - #[allow(unreachable_patterns)] - _ => Err(err_msg!(Unsupported, "Unsupported signature format")), - }, + None | Some(SignatureType::EdDSA) => { + let mut sig = [0u8; 64]; + decode_signature(signature, &mut sig, sig_format)?; + Ok(self.verify(data, sig)) + } #[allow(unreachable_patterns)] _ => Err(err_msg!(Unsupported, "Unsupported signature type")), } @@ -126,7 +118,7 @@ impl TryFrom<&AnyPrivateKey> for Ed25519KeyPair { type Error = Error; fn try_from(value: &AnyPrivateKey) -> Result { - if value.alg == KeyAlg::ED25519 { + if value.alg == KeyAlg::Ed25519 { Self::from_bytes(value.data.as_ref()) } else { Err(err_msg!(Unsupported, "Expected ed25519 key type")) @@ -169,8 +161,14 @@ impl Ed25519PublicKey { self.0.to_bytes() } - pub fn to_jwk(&self) -> Result { - unimplemented!(); + pub fn to_jwk(&self) -> Result { + let x = base64::encode_config(self.to_bytes(), base64::URL_SAFE_NO_PAD); + Ok(json!({ + "kty": "OKP", + "crv": "Ed25519", + "x": x, + "key_ops": ["verify"] + })) } pub fn to_x25519(&self) -> X25519PublicKey { @@ -198,17 +196,11 @@ impl KeyCapVerify for Ed25519PublicKey { sig_format: Option, ) -> Result { match sig_type { - None | Some(SignatureType::Ed25519) => match sig_format { - None | Some(SignatureFormat::Base58) => { - let mut sig = [0u8; 64]; - bs58::decode(signature) - .into(&mut sig) - .map_err(|_| err_msg!("Invalid base58 signature"))?; - Ok(self.verify(data, sig)) - } - #[allow(unreachable_patterns)] - _ => Err(err_msg!(Unsupported, "Unsupported signature format")), - }, + None | Some(SignatureType::EdDSA) => { + let mut sig = [0u8; 64]; + decode_signature(signature, &mut sig, sig_format)?; + Ok(self.verify(data, sig)) + } #[allow(unreachable_patterns)] _ => Err(err_msg!(Unsupported, "Unsupported signature type")), } @@ -219,7 +211,7 @@ impl TryFrom<&AnyPublicKey> for Ed25519PublicKey { type Error = Error; fn try_from(value: &AnyPublicKey) -> Result { - if value.alg == KeyAlg::ED25519 { + if value.alg == KeyAlg::Ed25519 { Self::from_bytes(&value.data) } else { Err(err_msg!(Unsupported, "Expected ed25519 key type")) @@ -305,6 +297,16 @@ impl X25519PublicKey { bs58::encode(self.to_bytes()).into_string() } + pub fn to_jwk(&self) -> Result { + let x = base64::encode_config(self.to_bytes(), base64::URL_SAFE_NO_PAD); + Ok(json!({ + "kty": "OKP", + "crv": "X25519", + "x": x, + "key_ops": ["deriveKey"] + })) + } + pub fn to_string(&self) -> String { let mut sval = String::with_capacity(64); bs58::encode(self.to_bytes()).into(&mut sval).unwrap(); @@ -329,6 +331,42 @@ impl TryFrom<&AnyPublicKey> for X25519PublicKey { } } +pub(super) fn encode_signature( + signature: &[u8], + sig_format: Option, +) -> Result, Error> { + match sig_format { + None | Some(SignatureFormat::Base58) => Ok(bs58::encode(signature).into_vec()), + Some(SignatureFormat::Raw) => Ok(signature.to_vec()), + #[allow(unreachable_patterns)] + _ => Err(err_msg!(Unsupported, "Unsupported signature format")), + } +} + +pub(super) fn decode_signature( + sig_input: &[u8], + sig_output: &mut impl AsMut<[u8]>, + sig_format: Option, +) -> Result<(), Error> { + match sig_format { + None | Some(SignatureFormat::Base58) => { + bs58::decode(sig_input) + .into(sig_output) + .map_err(|_| err_msg!("Invalid base58 signature"))?; + Ok(()) + } + Some(SignatureFormat::Raw) => { + if sig_input.len() != sig_output.as_mut().len() { + return Err(err_msg!("Invalid raw signature")); + } + sig_output.as_mut().copy_from_slice(sig_input); + Ok(()) + } + #[allow(unreachable_patterns)] + _ => Err(err_msg!(Unsupported, "Unsupported signature format")), + } +} + #[cfg(test)] mod tests { use super::*; @@ -362,7 +400,7 @@ mod tests { } #[test] - fn test_sign() { + fn sign_verify_expected() { let test_msg = b"This is a dummy message for use with tests"; let test_sig = hex::decode("451b5b8e8725321541954997781de51f4142e4a56bab68d24f6a6b92615de5eefb74134138315859a32c7cf5fe5a488bc545e2e08e5eedfd1fb10188d532d808").unwrap(); diff --git a/src/keys/alg/mod.rs b/src/keys/alg/mod.rs index 39f3af01..bfaef043 100644 --- a/src/keys/alg/mod.rs +++ b/src/keys/alg/mod.rs @@ -1 +1,3 @@ pub mod edwards; + +pub mod p256; diff --git a/src/keys/alg/p256.rs b/src/keys/alg/p256.rs new file mode 100644 index 00000000..06484dbb --- /dev/null +++ b/src/keys/alg/p256.rs @@ -0,0 +1,398 @@ +use std::convert::TryFrom; + +use p256::{ + ecdsa::{ + signature::{Signer, Verifier}, + Signature, SigningKey, VerifyingKey, + }, + elliptic_curve::{ecdh::diffie_hellman, sec1::FromEncodedPoint}, + EncodedPoint, PublicKey, SecretKey, +}; +use rand::rngs::OsRng; +use serde_json::json; + +use super::edwards::{decode_signature, encode_signature}; +use crate::{ + error::Error, + keys::any::{AnyPrivateKey, AnyPublicKey}, + keys::caps::{EcCurves, KeyAlg, KeyCapSign, KeyCapVerify, SignatureFormat, SignatureType}, + types::SecretBytes, +}; + +#[derive(Clone, Debug)] +pub struct P256SigningKey(SecretKey); + +impl P256SigningKey { + pub fn generate() -> Result { + Ok(Self(SecretKey::random(OsRng))) + } + + pub fn from_bytes(pt: &[u8]) -> Result { + let kp = SecretKey::from_bytes(pt).map_err(|_| err_msg!("Invalid signing key bytes"))?; + Ok(Self(kp)) + } + + pub fn to_bytes(&self) -> SecretBytes { + SecretBytes::from(self.0.to_bytes().to_vec()) + } + + pub fn public_key(&self) -> P256VerifyingKey { + P256VerifyingKey(self.0.public_key()) + } + + pub fn sign(&self, message: &[u8]) -> [u8; 64] { + let skey = SigningKey::from(self.0.clone()); + let sig = skey.sign(message); + let mut sigb = [0u8; 64]; + sigb[0..32].copy_from_slice(&sig.r().to_bytes()); + sigb[32..].copy_from_slice(&sig.s().to_bytes()); + sigb + } + + pub fn verify(&self, message: &[u8], signature: [u8; 64]) -> bool { + let mut r = [0u8; 32]; + let mut s = [0u8; 32]; + r.copy_from_slice(&signature[..32]); + s.copy_from_slice(&signature[32..]); + if let Ok(sig) = Signature::from_scalars(r, s) { + let vk = VerifyingKey::from(self.0.public_key()); + vk.verify(message, &sig).is_ok() + } else { + false + } + } +} + +impl KeyCapSign for P256SigningKey { + fn key_sign( + &self, + data: &[u8], + sig_type: Option, + sig_format: Option, + ) -> Result, Error> { + match sig_type { + None | Some(SignatureType::ES256) => { + let sig = self.sign(data); + encode_signature(&sig, sig_format) + } + #[allow(unreachable_patterns)] + _ => Err(err_msg!(Unsupported, "Unsupported signature type")), + } + } +} + +impl KeyCapVerify for P256SigningKey { + fn key_verify( + &self, + data: &[u8], + signature: &[u8], + sig_type: Option, + sig_format: Option, + ) -> Result { + match sig_type { + None | Some(SignatureType::ES256) => { + let mut sig = [0u8; 64]; + decode_signature(signature, &mut sig, sig_format)?; + Ok(self.verify(data, sig)) + } + #[allow(unreachable_patterns)] + _ => Err(err_msg!(Unsupported, "Unsupported signature type")), + } + } +} + +impl TryFrom<&AnyPrivateKey> for P256SigningKey { + type Error = Error; + + fn try_from(value: &AnyPrivateKey) -> Result { + if value.alg == KeyAlg::Ecdsa(EcCurves::Secp256r1) { + Self::from_bytes(value.data.as_ref()) + } else { + Err(err_msg!(Unsupported, "Expected p-256 key type")) + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct P256VerifyingKey(PublicKey); + +impl P256VerifyingKey { + pub fn from_str(key: impl AsRef) -> Result { + let key = key.as_ref(); + let key = key.strip_suffix(":p256/ecdsa").unwrap_or(key); + let mut bval = [0u8; 32]; + bs58::decode(key) + .into(&mut bval) + .map_err(|_| err_msg!("Invalid base58 public key"))?; + Self::from_bytes(bval) + } + + pub fn from_bytes(pk: impl AsRef<[u8]>) -> Result { + let encp = EncodedPoint::from_bytes(pk.as_ref()) + .map_err(|_| err_msg!("Invalid public key bytes"))?; + if encp.is_identity() { + return Err(err_msg!("Invalid public key: identity point")); + } + Ok(Self( + PublicKey::from_encoded_point(&encp).ok_or_else(|| err_msg!("Invalid public key"))?, + )) + } + + pub fn to_base58(&self) -> String { + bs58::encode(self.to_bytes()).into_string() + } + + pub fn to_bytes(&self) -> [u8; 32] { + let mut k = [0u8; 32]; + let encp = EncodedPoint::from(&self.0); + k.copy_from_slice(encp.as_bytes()); + k + } + + pub fn to_string(&self) -> String { + let mut sval = String::with_capacity(64); + bs58::encode(self.to_bytes()).into(&mut sval).unwrap(); + sval.push_str(":p256/ecdsa"); + sval + } + + pub fn to_jwk(&self) -> Result { + let encp = EncodedPoint::from(&self.0); + if encp.is_identity() { + return Err(err_msg!("Cannot convert identity point to JWK")); + } + let x = base64::encode_config(encp.x().unwrap(), base64::URL_SAFE_NO_PAD); + let y = base64::encode_config(encp.y().unwrap(), base64::URL_SAFE_NO_PAD); + Ok(json!({ + "kty": "EC", + "crv": "P-256", + "x": x, + "y": y, + "key_ops": ["verify"] + })) + } + + pub fn verify(&self, message: &[u8], signature: [u8; 64]) -> bool { + let mut r = [0u8; 32]; + let mut s = [0u8; 32]; + r.copy_from_slice(&signature[..32]); + s.copy_from_slice(&signature[32..]); + if let Ok(sig) = Signature::from_scalars(r, s) { + let vk = VerifyingKey::from(self.0.clone()); + vk.verify(message, &sig).is_ok() + } else { + false + } + } +} + +impl KeyCapVerify for P256VerifyingKey { + fn key_verify( + &self, + data: &[u8], + signature: &[u8], + sig_type: Option, + sig_format: Option, + ) -> Result { + match sig_type { + None | Some(SignatureType::ES256) => { + let mut sig = [0u8; 64]; + decode_signature(signature, &mut sig, sig_format)?; + Ok(self.verify(data, sig)) + } + #[allow(unreachable_patterns)] + _ => Err(err_msg!(Unsupported, "Unsupported signature type")), + } + } +} + +impl TryFrom<&AnyPublicKey> for P256VerifyingKey { + type Error = Error; + + fn try_from(value: &AnyPublicKey) -> Result { + if value.alg == KeyAlg::Ecdsa(EcCurves::Secp256r1) { + Self::from_bytes(&value.data) + } else { + Err(err_msg!(Unsupported, "Expected p-256 key type")) + } + } +} + +#[derive(Clone, Debug)] +pub struct P256ExchPrivateKey(SecretKey); + +impl P256ExchPrivateKey { + pub fn generate() -> Result { + Ok(Self(SecretKey::random(OsRng))) + } + + pub fn from_bytes(pt: &[u8]) -> Result { + let kp = SecretKey::from_bytes(pt).map_err(|_| err_msg!("Invalid signing key bytes"))?; + Ok(Self(kp)) + } + + pub fn to_bytes(&self) -> SecretBytes { + SecretBytes::from(self.0.to_bytes().to_vec()) + } + + pub fn key_exchange_with(&self, pk: &P256ExchPublicKey) -> SecretBytes { + let xk = diffie_hellman(self.0.secret_scalar(), pk.0.as_affine()); + SecretBytes::from(xk.as_bytes().to_vec()) + } + + pub fn public_key(&self) -> P256ExchPublicKey { + P256ExchPublicKey(self.0.public_key()) + } +} + +impl TryFrom<&AnyPrivateKey> for P256ExchPrivateKey { + type Error = Error; + + fn try_from(value: &AnyPrivateKey) -> Result { + if value.alg == KeyAlg::Ecdh(EcCurves::Secp256r1) { + Self::from_bytes(value.data.as_ref()) + } else { + Err(err_msg!(Unsupported, "Expected p-256 key type")) + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct P256ExchPublicKey(PublicKey); + +impl P256ExchPublicKey { + pub fn from_str(key: impl AsRef) -> Result { + let key = key.as_ref(); + let key = key.strip_suffix(":p256/ecdh").unwrap_or(key); + let mut bval = [0u8; 32]; + bs58::decode(key) + .into(&mut bval) + .map_err(|_| err_msg!("Invalid base58 public key"))?; + Self::from_bytes(bval) + } + + pub fn from_bytes(pk: impl AsRef<[u8]>) -> Result { + let encp = EncodedPoint::from_bytes(pk.as_ref()) + .map_err(|_| err_msg!("Invalid public key bytes"))?; + if encp.is_identity() { + return Err(err_msg!("Invalid public key: identity point")); + } + Ok(Self( + PublicKey::from_encoded_point(&encp).ok_or_else(|| err_msg!("Invalid public key"))?, + )) + } + + pub fn to_base58(&self) -> String { + bs58::encode(self.to_bytes()).into_string() + } + + pub fn to_bytes(&self) -> [u8; 32] { + let mut k = [0u8; 32]; + let encp = EncodedPoint::from(&self.0); + k.copy_from_slice(encp.as_bytes()); + k + } + + pub fn to_string(&self) -> String { + let mut sval = String::with_capacity(64); + bs58::encode(self.to_bytes()).into(&mut sval).unwrap(); + sval.push_str(":p256/ecdh"); + sval + } + + pub fn to_jwk(&self) -> Result { + let encp = EncodedPoint::from(&self.0); + if encp.is_identity() { + return Err(err_msg!("Cannot convert identity point to JWK")); + } + let x = base64::encode_config(encp.x().unwrap(), base64::URL_SAFE_NO_PAD); + let y = base64::encode_config(encp.y().unwrap(), base64::URL_SAFE_NO_PAD); + Ok(json!({ + "kty": "EC", + "crv": "P-256", + "x": x, + "y": y, + "key_ops": ["deriveKey"] + })) + } +} + +impl TryFrom<&AnyPublicKey> for P256ExchPublicKey { + type Error = Error; + + fn try_from(value: &AnyPublicKey) -> Result { + if value.alg == KeyAlg::Ecdh(EcCurves::Secp256r1) { + Self::from_bytes(&value.data) + } else { + Err(err_msg!(Unsupported, "Expected p-256 key type")) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn create_keypair_random() { + let sk = P256SigningKey::generate().expect("Error creating signing key"); + let _vk = sk.public_key(); + } + + #[test] + fn create_keypair_expected() { + // from JWS RFC https://tools.ietf.org/html/rfc7515 + // {"kty":"EC", + // "crv":"P-256", + // "x":"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU", + // "y":"x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0", + // "d":"jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI" + // } + let test_pvt = base64::decode_config( + "jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI", + base64::URL_SAFE_NO_PAD, + ) + .unwrap(); + let sk = P256SigningKey::from_bytes(&test_pvt).expect("Error creating signing key"); + let vk = sk.public_key(); + let jwk = vk.to_jwk().expect("Error converting public key to JWK"); + assert_eq!(jwk["kty"], "EC"); + assert_eq!(jwk["crv"], "P-256"); + assert_eq!(jwk["x"], "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU"); + assert_eq!(jwk["y"], "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0"); + } + + #[test] + fn sign_verify_expected() { + let test_msg = b"This is a dummy message for use with tests"; + let test_sig = hex::decode("241f765f19d4e6148452f2249d2fa69882244a6ad6e70aadb8848a6409d207124e85faf9587100247de7bdace13a3073b47ec8a531ca91c1375b2b6134344413").unwrap(); + let test_pvt = base64::decode_config( + "jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI", + base64::URL_SAFE_NO_PAD, + ) + .unwrap(); + let kp = P256SigningKey::from_bytes(&&test_pvt).unwrap(); + let sig = kp.sign(&test_msg[..]); + assert_eq!(sig, test_sig.as_slice()); + assert_eq!(kp.public_key().verify(&test_msg[..], sig), true); + assert_eq!(kp.public_key().verify(b"Not the message", sig), false); + assert_eq!(kp.public_key().verify(&test_msg[..], [0u8; 64]), false); + } + + #[test] + fn key_exchange_random() { + let kp1 = P256ExchPrivateKey::generate().unwrap(); + let kp2 = P256ExchPrivateKey::generate().unwrap(); + assert_ne!(kp1.to_bytes(), kp2.to_bytes()); + + let xch1 = kp1.key_exchange_with(&kp2.public_key()); + let xch2 = kp2.key_exchange_with(&kp1.public_key()); + assert_eq!(xch1, xch2); + + // test round trip + let xch3 = P256ExchPrivateKey::from_bytes(&kp1.to_bytes()) + .unwrap() + .key_exchange_with(&kp2.public_key()); + assert_eq!(xch3, xch1); + } +} diff --git a/src/keys/any.rs b/src/keys/any.rs index edd951c1..5bec5989 100644 --- a/src/keys/any.rs +++ b/src/keys/any.rs @@ -53,7 +53,7 @@ impl KeyCapSign for AnyPrivateKey { sig_format: Option, ) -> Result, Error> { match self.alg { - KeyAlg::ED25519 => { + KeyAlg::Ed25519 => { Ed25519KeyPair::try_from(self).and_then(|k| k.key_sign(data, sig_type, sig_format)) } _ => Err(err_msg!( diff --git a/src/keys/caps.rs b/src/keys/caps.rs index f308d1e8..913f0eb0 100644 --- a/src/keys/caps.rs +++ b/src/keys/caps.rs @@ -11,13 +11,13 @@ use crate::error::Error; #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Zeroize)] pub enum KeyAlg { /// Curve25519 signing key - ED25519, + Ed25519, /// Curve25519 diffie-hellman key exchange key X25519, - // /// Elliptic Curve diffie-hellman key exchange key - // Ecdh(EcCurves), - // /// Elliptic Curve signing key - // Ecdsa(EcCurves), + /// Elliptic Curve diffie-hellman key exchange key + Ecdh(EcCurves), + /// Elliptic Curve signing key + Ecdsa(EcCurves), /// BLS12-1381 signing key in group G1 or G2 // BLS12_1381(BlsGroup), /// Unrecognized algorithm @@ -30,8 +30,10 @@ impl KeyAlg { /// Get a reference to a string representing the `KeyAlg` pub fn as_str(&self) -> &str { match self { - Self::ED25519 => "ed25519", + Self::Ed25519 => "ed25519", Self::X25519 => "x25519", + Self::Ecdh(EcCurves::Secp256r1) => "p256/ecdh", + Self::Ecdsa(EcCurves::Secp256r1) => "p256/ecdsa", Self::Other(other) => other.as_str(), } } @@ -48,7 +50,7 @@ impl FromStr for KeyAlg { fn from_str(s: &str) -> Result { Ok(match s { - "ed25519" => Self::ED25519, + "ed25519" => Self::Ed25519, "x25519" => Self::X25519, other => Self::Other(other.to_owned()), }) @@ -140,16 +142,21 @@ pub trait KeyCapVerify { ) -> Result; } +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum SignatureFormat { /// Base58-encoded binary signature Base58, + /// Raw binary signature (method dependent) + Raw, } +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum SignatureType { // Bls12_1381(BlsGroup), /// Standard signature output for ed25519 - Ed25519, - // Ecdsa(EcdsaMethod), + EdDSA, + // Elliptic curve DSA using P-256 and SHA-256 + ES256, } impl SignatureType { @@ -157,8 +164,8 @@ impl SignatureType { match self { // Self::Bls12_1381(BlsGroup::G1) => 48, // Self::Bls12_1381(BlsGroup::G2) => 96, - Self::Ed25519 => 64, - // Self::Ecdsa(_) => , + Self::EdDSA => 64, + Self::ES256 => 64, } } } @@ -170,24 +177,11 @@ impl SignatureType { // G2, // } -// pub enum EcdsaMethod { -// /// Sign/verify ECC signatures using SHA2-256 -// Sha256, -// /// Sign/verify ECC signatures using SHA2-384 -// Sha384, -// /// Sign/verify ECC signatures using SHA2-512 -// Sha512, -// } - -// /// Possibly supported curves for ECC operations -// #[derive(Clone, Copy, Debug)] -// pub enum EcCurves { -// /// NIST P-256 curve -// Secp256r1, -// /// NIST P-384 curve -// Secp384r1, -// /// NIST P-512 curve -// Secp512r1, -// /// Koblitz 256 curve -// Secp256k1, -// } +/// Supported curves for ECC operations +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Zeroize)] +pub enum EcCurves { + /// NIST P-256 curve + Secp256r1, + // /// Koblitz 256 curve + // Secp256k1, +} diff --git a/src/keys/mod.rs b/src/keys/mod.rs index ab3e8135..5c2b4b6d 100644 --- a/src/keys/mod.rs +++ b/src/keys/mod.rs @@ -37,7 +37,7 @@ pub mod wrap; /// Derive the (public) verification key for a keypair pub fn derive_verkey(alg: KeyAlg, seed: &[u8]) -> Result { match alg { - KeyAlg::ED25519 => (), + KeyAlg::Ed25519 => (), _ => return Err(err_msg!(Unsupported, "Unsupported key algorithm")), } let sk = Ed25519KeyPair::from_seed(seed) diff --git a/src/keys/store.rs b/src/keys/store.rs index 486f4915..d468aa78 100644 --- a/src/keys/store.rs +++ b/src/keys/store.rs @@ -267,7 +267,7 @@ mod tests { #[test] fn key_params_roundtrip() { let params = KeyParams { - alg: KeyAlg::ED25519, + alg: KeyAlg::Ed25519, metadata: Some("meta".to_string()), reference: None, data: Some(SecretBytes::from(vec![0, 0, 0, 0])), diff --git a/src/store.rs b/src/store.rs index 80d8fcfd..e8eb175b 100644 --- a/src/store.rs +++ b/src/store.rs @@ -384,7 +384,7 @@ impl Session { // backend ) -> Result { match alg { - KeyAlg::ED25519 => (), + KeyAlg::Ed25519 => (), _ => return Err(err_msg!(Unsupported, "Unsupported key algorithm")), } diff --git a/tests/utils/mod.rs b/tests/utils/mod.rs index 66410b1d..528b6631 100644 --- a/tests/utils/mod.rs +++ b/tests/utils/mod.rs @@ -469,7 +469,7 @@ pub async fn db_keypair_create_fetch(db: &Store) { let metadata = "meta".to_owned(); let key_info = conn - .create_keypair(KeyAlg::ED25519, Some(&metadata), None, None) + .create_keypair(KeyAlg::Ed25519, Some(&metadata), None, None) .await .expect(ERR_CREATE_KEYPAIR); assert_eq!(key_info.params.metadata, Some(metadata)); @@ -485,7 +485,7 @@ pub async fn db_keypair_sign_verify(db: &Store) { let mut conn = db.session(None).await.expect(ERR_SESSION); let key_info = conn - .create_keypair(KeyAlg::ED25519, None, None, None) + .create_keypair(KeyAlg::Ed25519, None, None, None) .await .expect(ERR_CREATE_KEYPAIR); @@ -528,7 +528,7 @@ pub async fn db_keypair_pack_unpack_anon(db: &Store) { let mut conn = db.session(None).await.expect(ERR_SESSION); let recip_key = conn - .create_keypair(KeyAlg::ED25519, None, None, None) + .create_keypair(KeyAlg::Ed25519, None, None, None) .await .expect(ERR_CREATE_KEYPAIR); @@ -549,11 +549,11 @@ pub async fn db_keypair_pack_unpack_auth(db: &Store) { let mut conn = db.session(None).await.expect(ERR_SESSION); let sender_key = conn - .create_keypair(KeyAlg::ED25519, None, None, None) + .create_keypair(KeyAlg::Ed25519, None, None, None) .await .expect(ERR_CREATE_KEYPAIR); let recip_key = conn - .create_keypair(KeyAlg::ED25519, None, None, None) + .create_keypair(KeyAlg::Ed25519, None, None, None) .await .expect(ERR_CREATE_KEYPAIR); From 2a8fb6a02cc855d1936fbde6a48e41036708cb39 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Fri, 19 Mar 2021 18:07:45 -0700 Subject: [PATCH 003/116] extend AnyPublicKey, AnyPrivateKey Signed-off-by: Andrew Whitehead --- src/keys/any.rs | 51 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/src/keys/any.rs b/src/keys/any.rs index 5bec5989..ff239f6f 100644 --- a/src/keys/any.rs +++ b/src/keys/any.rs @@ -1,8 +1,10 @@ use std::convert::TryFrom; -use super::alg::edwards::Ed25519KeyPair; +use super::alg::edwards::{Ed25519KeyPair, Ed25519PublicKey}; +use super::alg::p256::{P256SigningKey, P256VerifyingKey}; use super::caps::{ - KeyAlg, KeyCapGetPublic, KeyCapSign, KeyCategory, SignatureFormat, SignatureType, + EcCurves, KeyAlg, KeyCapGetPublic, KeyCapSign, KeyCapVerify, KeyCategory, SignatureFormat, + SignatureType, }; use super::store::KeyEntry; use crate::error::Error; @@ -33,6 +35,27 @@ impl TryFrom for AnyPublicKey { } } +impl KeyCapVerify for AnyPublicKey { + fn key_verify( + &self, + data: &[u8], + signature: &[u8], + sig_type: Option, + sig_format: Option, + ) -> Result { + match self.alg { + KeyAlg::Ed25519 => Ed25519PublicKey::try_from(self) + .and_then(|k| k.key_verify(data, signature, sig_type, sig_format)), + KeyAlg::Ecdsa(EcCurves::Secp256r1) => P256VerifyingKey::try_from(self) + .and_then(|k| k.key_verify(data, signature, sig_type, sig_format)), + _ => Err(err_msg!( + Unsupported, + "Signature verification not supported for this key type" + )), + } + } +} + #[derive(Debug)] pub struct AnyPrivateKey { pub alg: KeyAlg, @@ -56,6 +79,9 @@ impl KeyCapSign for AnyPrivateKey { KeyAlg::Ed25519 => { Ed25519KeyPair::try_from(self).and_then(|k| k.key_sign(data, sig_type, sig_format)) } + KeyAlg::Ecdsa(EcCurves::Secp256r1) => { + P256SigningKey::try_from(self).and_then(|k| k.key_sign(data, sig_type, sig_format)) + } _ => Err(err_msg!( Unsupported, "Signing not supported for this key type" @@ -64,6 +90,27 @@ impl KeyCapSign for AnyPrivateKey { } } +impl KeyCapVerify for AnyPrivateKey { + fn key_verify( + &self, + data: &[u8], + signature: &[u8], + sig_type: Option, + sig_format: Option, + ) -> Result { + match self.alg { + KeyAlg::Ed25519 => Ed25519KeyPair::try_from(self) + .and_then(|k| k.key_verify(data, signature, sig_type, sig_format)), + KeyAlg::Ecdsa(EcCurves::Secp256r1) => P256SigningKey::try_from(self) + .and_then(|k| k.key_verify(data, signature, sig_type, sig_format)), + _ => Err(err_msg!( + Unsupported, + "Signature verification not supported for this key type" + )), + } + } +} + impl TryFrom for AnyPrivateKey { type Error = Error; From e1330db290b3b1d9f96daa9ec738d8787f9e5533 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Fri, 19 Mar 2021 18:47:12 -0700 Subject: [PATCH 004/116] test ed25519 jwk generation Signed-off-by: Andrew Whitehead --- src/keys/alg/edwards.rs | 34 +++++++++++++++++++++++++++++++++- src/keys/alg/p256.rs | 4 ++-- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/keys/alg/edwards.rs b/src/keys/alg/edwards.rs index 9886287c..74282813 100644 --- a/src/keys/alg/edwards.rs +++ b/src/keys/alg/edwards.rs @@ -33,7 +33,14 @@ impl Ed25519KeyPair { } pub fn from_bytes(kp: &[u8]) -> Result { - let kp = Keypair::from_bytes(kp).map_err(|_| err_msg!("Invalid keypair bytes"))?; + let kp = if kp.len() == 32 { + let secret = + SecretKey::from_bytes(kp).map_err(|_| err_msg!("Invalid keypair bytes"))?; + let public = PublicKey::from(&secret); + Keypair { secret, public } + } else { + Keypair::from_bytes(kp).map_err(|_| err_msg!("Invalid keypair bytes"))? + }; Ok(Self(kp)) } @@ -399,6 +406,31 @@ mod tests { assert_eq!(&x_pair[32..], x_pk); } + #[test] + fn jwk_expected() { + // from https://www.connect2id.com/blog/nimbus-jose-jwt-6 + // { + // "kty" : "OKP", + // "crv" : "Ed25519", + // "x" : "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo", + // "d" : "nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A" + // "use" : "sig", + // "kid" : "FdFYFzERwC2uCBB46pZQi4GG85LujR8obt-KWRBICVQ" + // } + let test_pvt_b64 = "nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A"; + let test_pvt = base64::decode_config(test_pvt_b64, base64::URL_SAFE).unwrap(); + let sk = Ed25519KeyPair::from_bytes(&test_pvt).expect("Error creating signing key"); + let vk = sk.public_key(); + let jwk = vk.to_jwk().expect("Error converting public key to JWK"); + assert_eq!(jwk["kty"], "OKP"); + assert_eq!(jwk["crv"], "Ed25519"); + assert_eq!(jwk["x"], "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"); + assert_eq!( + base64::encode_config(sk.private_key(), base64::URL_SAFE_NO_PAD), + test_pvt_b64 + ); + } + #[test] fn sign_verify_expected() { let test_msg = b"This is a dummy message for use with tests"; diff --git a/src/keys/alg/p256.rs b/src/keys/alg/p256.rs index 06484dbb..bd6b3dda 100644 --- a/src/keys/alg/p256.rs +++ b/src/keys/alg/p256.rs @@ -340,7 +340,7 @@ mod tests { } #[test] - fn create_keypair_expected() { + fn jwk_expected() { // from JWS RFC https://tools.ietf.org/html/rfc7515 // {"kty":"EC", // "crv":"P-256", @@ -350,7 +350,7 @@ mod tests { // } let test_pvt = base64::decode_config( "jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI", - base64::URL_SAFE_NO_PAD, + base64::URL_SAFE, ) .unwrap(); let sk = P256SigningKey::from_bytes(&test_pvt).expect("Error creating signing key"); From 0656d3cb49a3bd752e96186c1aa3883cca3a5602 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Thu, 8 Apr 2021 19:33:15 -0700 Subject: [PATCH 005/116] start moving key handling, signing, and encryption to askar-keys crate Signed-off-by: Andrew Whitehead --- Cargo.toml | 12 +- askar-keys/Cargo.toml | 60 ++ askar-keys/src/alg/bls.rs | 546 ++++++++++++++++++ askar-keys/src/alg/ed25519.rs | 409 +++++++++++++ askar-keys/src/alg/k256.rs | 394 +++++++++++++ askar-keys/src/alg/mod.rs | 8 + {src/keys => askar-keys/src}/alg/p256.rs | 169 +++--- askar-keys/src/alg/x25519.rs | 233 ++++++++ {src/keys => askar-keys/src}/any.rs | 97 ++-- askar-keys/src/buffer.rs | 451 +++++++++++++++ {src/keys => askar-keys/src}/caps.rs | 132 +++-- {src/keys => askar-keys/src}/encrypt.rs | 45 +- askar-keys/src/error.rs | 150 +++++ askar-keys/src/jwk/encode.rs | 0 askar-keys/src/jwk/mod.rs | 188 ++++++ askar-keys/src/jwk/ops.rs | 203 +++++++ askar-keys/src/jwk/parse.rs | 156 +++++ askar-keys/src/kdf/argon2.rs | 78 +++ askar-keys/src/kdf/mod.rs | 1 + askar-keys/src/lib.rs | 48 ++ {src/didcomm => askar-keys/src}/pack/alg.rs | 16 +- askar-keys/src/pack/mod.rs | 3 + .../src}/pack/nacl_box.rs | 4 +- {src => askar-keys/src}/random.rs | 14 +- {src => askar-keys/src}/serde_utils.rs | 24 +- {src/keys => askar-keys/src}/store.rs | 0 src/didcomm/pack/types.rs | 2 +- src/keys/alg/edwards.rs | 464 --------------- src/keys/alg/mod.rs | 3 - src/keys/{kdf/mod.rs => kdf.rs} | 13 +- src/keys/kdf/argon2.rs | 80 --- src/keys/mod.rs | 27 +- src/keys/types.rs | 212 ------- src/keys/wrap.rs | 10 +- src/store.rs | 2 +- src/types.rs | 144 ----- 36 files changed, 3218 insertions(+), 1180 deletions(-) create mode 100644 askar-keys/Cargo.toml create mode 100644 askar-keys/src/alg/bls.rs create mode 100644 askar-keys/src/alg/ed25519.rs create mode 100644 askar-keys/src/alg/k256.rs create mode 100644 askar-keys/src/alg/mod.rs rename {src/keys => askar-keys/src}/alg/p256.rs (70%) create mode 100644 askar-keys/src/alg/x25519.rs rename {src/keys => askar-keys/src}/any.rs (54%) create mode 100644 askar-keys/src/buffer.rs rename {src/keys => askar-keys/src}/caps.rs (59%) rename {src/keys => askar-keys/src}/encrypt.rs (89%) create mode 100644 askar-keys/src/error.rs create mode 100644 askar-keys/src/jwk/encode.rs create mode 100644 askar-keys/src/jwk/mod.rs create mode 100644 askar-keys/src/jwk/ops.rs create mode 100644 askar-keys/src/jwk/parse.rs create mode 100644 askar-keys/src/kdf/argon2.rs create mode 100644 askar-keys/src/kdf/mod.rs create mode 100644 askar-keys/src/lib.rs rename {src/didcomm => askar-keys/src}/pack/alg.rs (97%) create mode 100644 askar-keys/src/pack/mod.rs rename {src/didcomm => askar-keys/src}/pack/nacl_box.rs (97%) rename {src => askar-keys/src}/random.rs (81%) rename {src => askar-keys/src}/serde_utils.rs (91%) rename {src/keys => askar-keys/src}/store.rs (100%) delete mode 100644 src/keys/alg/edwards.rs delete mode 100644 src/keys/alg/mod.rs rename src/keys/{kdf/mod.rs => kdf.rs} (90%) delete mode 100644 src/keys/kdf/argon2.rs delete mode 100644 src/keys/types.rs diff --git a/Cargo.toml b/Cargo.toml index c171ffd4..5ee7b9bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,6 @@ +[workspace] +members = ["askar-keys"] + [package] name = "aries-askar" version = "0.2.0" @@ -33,21 +36,16 @@ sqlite = ["num_cpus", "sqlx", "sqlx/sqlite"] pg_test = ["postgres"] [dependencies] -aead = "0.3" async-global-executor = { version = "1.4", features = ["async-io"] } async-mutex = "1.4" async-stream = "0.3" async-std = "1.7" # temporary addition to encourage common dependencies with sqlx +askar-keys = { path = "./askar-keys" } base64 = "0.13" blake2 = "0.9" blocking = "1.0" bs58 = "0.4" -chacha20 = "0.6" # should match version from chacha20poly1305 -chacha20poly1305 = { version = "0.7", default-features = false, features = ["alloc", "chacha20"] } chrono = "0.4" -crypto_box = "0.5" -curve25519-dalek = "3.0" -ed25519-dalek = { version = "1.0", features = ["std"] } env_logger = { version = "0.7", optional = true } ffi-support = { version = "0.4", optional = true } futures-lite = "1.7" @@ -59,7 +57,6 @@ log = { version = "0.4", optional = true } num_cpus = { version = "1.0", optional = true } once_cell = "1.5" percent-encoding = "2.0" -p256 = { version = "0.7", features = ["ecdsa", "ecdh"] } rand = "0.7" rmp-serde = "0.14" rust-argon2 = "0.8" @@ -69,7 +66,6 @@ serde_json = "1.0" sha2 = "0.9" url = { version = "2.1", default-features = false } uuid = { version = "0.8", features = ["v4"] } -x25519-dalek = "1.1" zeroize = { version = "1.1.0", features = ["zeroize_derive"] } [dependencies.indy-utils] diff --git a/askar-keys/Cargo.toml b/askar-keys/Cargo.toml new file mode 100644 index 00000000..64b011f2 --- /dev/null +++ b/askar-keys/Cargo.toml @@ -0,0 +1,60 @@ +[package] +name = "askar-keys" +version = "0.1.0" +authors = ["Hyperledger Aries Contributors "] +edition = "2018" +description = "Hyperledger Aries Askar key management" +license = "MIT OR Apache-2.0" +readme = "README.md" +repository = "https://github.com/hyperledger/aries-askar/" +categories = ["cryptography", "database"] +keywords = ["hyperledger", "aries", "ssi", "verifiable", "credentials"] + +[lib] +name = "askar_keys" +path = "src/lib.rs" +crate-type = ["rlib"] + +[package.metadata.docs.rs] +features = ["all"] +no-default-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[features] +default = ["logger"] +any = [] +logger = ["env_logger", "log"] +std = [] + +[dependencies] +aead = "0.3" +base64 = "0.13" +blake2 = "0.9" +bls12_381 = { version = "0.4.0", path = "../../bls12_381" } +bs58 = "0.4" +chacha20 = "0.6" # should match version from chacha20poly1305 +chacha20poly1305 = { version = "0.7", default-features = false, features = ["alloc", "chacha20"] } +crypto_box = "0.5" +curve25519-dalek = "3.0" +ed25519-dalek = { version = "1.0", features = ["std"] } +env_logger = { version = "0.7", optional = true } +digest = "0.9" +group = "0.9" +hex = "0.4" +hkdf = "0.10" +hmac = "0.10" +k256 = { version = "0.7", features = ["ecdsa", "ecdh", "sha256"] } +log = { version = "0.4", optional = true } +p256 = { version = "0.7", features = ["ecdsa", "ecdh"] } +rand = "0.7" +rust-argon2 = "0.8" +serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } +serde_bytes = "0.11" +serde_json = { version = "1.0", default-features = false, features = ["alloc"] } +sha2 = "0.9" +x25519-dalek = "1.1" +zeroize = { version = "1.1.0", features = ["zeroize_derive"] } + +[profile.release] +lto = true +codegen-units = 1 diff --git a/askar-keys/src/alg/bls.rs b/askar-keys/src/alg/bls.rs new file mode 100644 index 00000000..2f8b1af4 --- /dev/null +++ b/askar-keys/src/alg/bls.rs @@ -0,0 +1,546 @@ +use std::convert::TryFrom; + +use digest::{ + generic_array::{typenum::U48, GenericArray}, + BlockInput, Digest, +}; +use group::{ + prime::{PrimeCurveAffine, PrimeGroup}, + GroupEncoding, +}; +use std::{convert::TryInto, iter::FromIterator, marker::PhantomData, ops::Mul}; + +use bls12_381::{ + hash_to_curve::{ExpandMessage, ExpandMsgXmd, HashToCurve, HashToField}, + pairing, G1Affine, G1Projective, G2Affine, G2Projective, Gt, Scalar, +}; +use rand::{rngs::OsRng, RngCore}; +use zeroize::Zeroize; + +use crate::{ + error::Error, + keys::any::{AnyPrivateKey, AnyPublicKey}, + keys::caps::{KeyAlg, KeyCapSign, KeyCapVerify, SignatureFormat, SignatureType}, + types::SecretBytes, +}; + +const SIG_SUITE_G1_SSWU_NUL: &'static [u8] = b"BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_"; +const SIG_SUITE_G2_SSWU_NUL: &'static [u8] = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_"; +const SIG_SUITE_G1_SSWU_AUG: &'static [u8] = b"BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_AUG_"; +const SIG_SUITE_G2_SSWU_AUG: &'static [u8] = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_AUG_"; +const SIG_SUITE_G1_SSWU_POP: &'static [u8] = b"BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_POP_"; +const SIG_SUITE_G2_SSWU_POP: &'static [u8] = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_"; + +#[derive(Clone, Debug)] +pub struct Bls12PrivateKey(Scalar); + +impl Bls12PrivateKey { + pub fn generate() -> Result { + let mut secret = [0u8; 64]; + OsRng.fill_bytes(&mut secret); + let inner = Scalar::from_bytes_wide(&secret); + secret.zeroize(); + Ok(Self(inner)) + } + + // FIXME - this is the draft 7 version, + // draft 10 hashes the salt and loops until a non-zero SK is found + pub fn from_seed(ikm: &[u8]) -> Result { + const SALT: &[u8] = b"BLS-SIG-KEYGEN-SALT-"; + let mut okm = GenericArray::::default(); + let mut extract = hkdf::HkdfExtract::::new(Some(SALT)); + extract.input_ikm(ikm); + extract.input_ikm(&[0u8]); + let (_, hkdf) = extract.finalize(); + hkdf.expand(&(48 as u16).to_be_bytes(), &mut okm) + .expect("HDKF extract failure"); + let inner = Scalar::from_okm(&okm); + Ok(Self(inner)) + } + + pub fn from_bytes(sk: &[u8]) -> Result { + if sk.len() != 32 { + return Err(err_msg!("Invalid key length")); + } + let mut skb = [0u8; 32]; + skb.copy_from_slice(sk); + skb.reverse(); + let result: Option = Scalar::from_bytes(&skb).into(); + // turn into little-endian format + skb.zeroize(); + Ok(Self( + result.ok_or_else(|| err_msg!("Invalid keypair bytes"))?, + )) + } + + pub fn to_bytes(&self) -> SecretBytes { + let mut skb = self.0.to_bytes(); + // turn into big-endian format + skb.reverse(); + let v = skb.to_vec(); + skb.zeroize(); + SecretBytes::from(v) + } + + pub fn g1_public_key(&self) -> Bls12G1PublicKey { + Bls12G1PublicKey(G1Affine::from(G1Projective::generator() * self.0)) + } + + pub fn g2_public_key(&self) -> Bls12G2PublicKey { + Bls12G2PublicKey(G2Affine::from(G2Projective::generator() * self.0)) + } + + pub fn g1_sign_basic( + &self, + message: &[u8], + domain: Option<&[u8]>, + ) -> Vec { + >>::sign( + &self.0, + message, + domain.unwrap_or(SIG_SUITE_G1_SSWU_NUL), + ) + } + + pub fn g2_sign_basic( + &self, + message: &[u8], + domain: Option<&[u8]>, + ) -> Vec { + >>::sign( + &self.0, + message, + domain.unwrap_or(SIG_SUITE_G2_SSWU_NUL), + ) + } + + pub fn g1_sign_aug( + &self, + message: &[u8], + domain: Option<&[u8]>, + ) -> Vec { + >>::sign( + &self.0, + message, + domain.unwrap_or(SIG_SUITE_G1_SSWU_AUG), + ) + } + + pub fn g2_sign_aug( + &self, + message: &[u8], + domain: Option<&[u8]>, + ) -> Vec { + >>::sign( + &self.0, + message, + domain.unwrap_or(SIG_SUITE_G2_SSWU_AUG), + ) + } + + // pub fn verify(&self, message: &[u8], signature: [u8; 64]) -> bool { + // self.0.verify_strict(message, &signature.into()).is_ok() + // } +} + +trait HasSignature { + type SigPt; + type Repr: AsRef<[u8]>; + + fn public_key(sk: &Scalar) -> Self; + + fn hash_to_point(message: &[u8], domain: &[u8]) -> Self::SigPt; + + fn create_signature(sk: &Scalar, pt: &Self::SigPt) -> Self::Repr; + + fn verify_signature(&self, sig: &[u8], pt: &Self::SigPt) -> bool; +} + +impl HasSignature for G1Affine +where + G2Projective: HashToCurve, +{ + type SigPt = G2Projective; + type Repr = ::Repr; + + fn public_key(sk: &Scalar) -> Self { + G1Affine::from(G1Projective::generator() * sk) + } + + fn hash_to_point(message: &[u8], domain: &[u8]) -> Self::SigPt { + G2Projective::hash_to_curve(message, domain) + } + + fn create_signature(sk: &Scalar, pt: &Self::SigPt) -> Self::Repr { + G2Affine::from(pt * sk).to_bytes() + } + + fn verify_signature(&self, sig: &[u8], pt: &G2Projective) -> bool { + if let Some(sig) = <&[u8; 96]>::try_from(sig) + .ok() + .and_then(|arr| Option::::from(G2Affine::from_compressed(arr))) + { + pairing(self, &G2Affine::from(pt)) == pairing(&G1Affine::generator(), &sig) + } else { + false + } + } +} + +impl HasSignature for G2Affine +where + G1Projective: HashToCurve, +{ + type SigPt = G1Projective; + type Repr = ::Repr; + + fn public_key(sk: &Scalar) -> Self { + G2Affine::from(G2Projective::generator() * sk) + } + + fn hash_to_point(message: &[u8], domain: &[u8]) -> Self::SigPt { + G1Projective::hash_to_curve(message, domain) + } + + fn create_signature(sk: &Scalar, pt: &Self::SigPt) -> Self::Repr { + G1Affine::from(pt * sk).to_bytes() + } + + fn verify_signature(&self, sig: &[u8], pt: &G1Projective) -> bool { + if let Some(sig) = <&[u8; 48]>::try_from(sig) + .ok() + .and_then(|arr| Option::::from(G1Affine::from_compressed(arr))) + { + pairing(&G1Affine::from(pt), self) == pairing(&sig, &G2Affine::generator()) + } else { + false + } + } +} + +trait BlsSignBasic { + type Pk: HasSignature + GroupEncoding; + + fn sign(sk: &Scalar, message: &[u8], domain: &[u8]) -> Vec { + let pt = Self::Pk::hash_to_point(message, domain); + >::create_signature(sk, &pt) + .as_ref() + .to_vec() + } + + fn verify(pk: &Self::Pk, sig: &[u8], message: &[u8], domain: &[u8]) -> bool { + let q = Self::Pk::hash_to_point(&message, domain); + pk.verify_signature(&sig, &q) + } +} + +impl BlsSignBasic for T +where + T: HasSignature + GroupEncoding, +{ + type Pk = Self; +} + +fn augment_message(pk: &impl GroupEncoding, message: &[u8]) -> Vec { + let pk = pk.to_bytes(); + let mut ext_msg = Vec::with_capacity(pk.as_ref().len() + message.len()); + ext_msg.extend_from_slice(&pk.as_ref()); + ext_msg.extend_from_slice(&message); + ext_msg +} + +trait BlsSignAug: BlsSignBasic { + fn sign(sk: &Scalar, message: &[u8], domain: &[u8]) -> Vec { + let pk = Self::Pk::public_key(sk); + let ext_msg = augment_message(&pk, message); + >::sign(sk, &ext_msg, domain) + } + + fn verify(pk: &Self::Pk, sig: &[u8], message: &[u8], domain: &[u8]) -> bool { + let ext_msg = augment_message(pk, message); + >::verify(pk, sig, &ext_msg, domain) + } +} + +impl BlsSignAug for T where + T: BlsSignBasic + PrimeCurveAffine +{ +} + +// impl KeyCapSign for Bls12PrivateKey { +// fn key_sign( +// &self, +// data: &[u8], +// sig_type: Option, +// sig_format: Option, +// ) -> Result, Error> { +// match sig_type { +// None | Some(SignatureType::Bls12_1381(G1)) => { +// let sig = self.sign(data); +// encode_signature(&sig, sig_format) +// } +// #[allow(unreachable_patterns)] +// _ => Err(err_msg!(Unsupported, "Unsupported signature type")), +// } +// } +// } + +// impl KeyCapVerify for Ed25519KeyPair { +// fn key_verify( +// &self, +// data: &[u8], +// signature: &[u8], +// sig_type: Option, +// sig_format: Option, +// ) -> Result { +// match sig_type { +// None | Some(SignatureType::EdDSA) => { +// let mut sig = [0u8; 64]; +// decode_signature(signature, &mut sig, sig_format)?; +// Ok(self.verify(data, sig)) +// } +// #[allow(unreachable_patterns)] +// _ => Err(err_msg!(Unsupported, "Unsupported signature type")), +// } +// } +// } + +// impl TryFrom<&AnyPrivateKey> for Ed25519KeyPair { +// type Error = Error; + +// fn try_from(value: &AnyPrivateKey) -> Result { +// if value.alg == KeyAlg::Ed25519 { +// Self::from_bytes(value.data.as_ref()) +// } else { +// Err(err_msg!(Unsupported, "Expected ed25519 key type")) +// } +// } +// } + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Bls12G1PublicKey(G1Affine); + +impl Bls12G1PublicKey { + // pub fn from_str(key: impl AsRef) -> Result { + // let key = key.as_ref(); + // let key = key.strip_suffix(":ed25519").unwrap_or(key); + // let mut bval = [0u8; 32]; + // bs58::decode(key) + // .into(&mut bval) + // .map_err(|_| err_msg!("Invalid base58 public key"))?; + // Self::from_bytes(bval) + // } + + // pub fn from_bytes(pk: impl AsRef<[u8]>) -> Result { + // let pk = + // PublicKey::from_bytes(pk.as_ref()).map_err(|_| err_msg!("Invalid public key bytes"))?; + // Ok(Self(pk)) + // } + + // pub fn to_base58(&self) -> String { + // bs58::encode(self.to_bytes()).into_string() + // } + + // pub fn to_string(&self) -> String { + // let mut sval = String::with_capacity(64); + // bs58::encode(self.to_bytes()).into(&mut sval).unwrap(); + // sval.push_str(":ed25519"); + // sval + // } + + // pub fn to_bytes(&self) -> [u8; 32] { + // self.0.to_bytes() + // } + + // pub fn to_jwk(&self) -> Result { + // let x = base64::encode_config(self.to_bytes(), base64::URL_SAFE_NO_PAD); + // Ok(json!({ + // "kty": "OKP", + // "crv": "Ed25519", + // "x": x, + // "key_ops": ["verify"] + // })) + // } + + pub fn verify_basic( + &self, + signature: &[u8], + message: &[u8], + domain: Option<&[u8]>, + ) -> bool { + >>::verify( + &self.0, + signature, + message, + domain.unwrap_or(SIG_SUITE_G2_SSWU_NUL), + ) + } + + pub fn verify_aug( + &self, + signature: &[u8], + message: &[u8], + domain: Option<&[u8]>, + ) -> bool { + >>::verify( + &self.0, + signature, + message, + domain.unwrap_or(SIG_SUITE_G2_SSWU_AUG), + ) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Bls12G2PublicKey(G2Affine); + +impl Bls12G2PublicKey { + pub fn verify_basic( + &self, + signature: &[u8], + message: &[u8], + domain: Option<&[u8]>, + ) -> bool { + >>::verify( + &self.0, + signature, + message, + domain.unwrap_or(SIG_SUITE_G1_SSWU_NUL), + ) + } + + pub fn verify_aug( + &self, + signature: &[u8], + message: &[u8], + domain: Option<&[u8]>, + ) -> bool { + >>::verify( + &self.0, + signature, + message, + domain.unwrap_or(SIG_SUITE_G1_SSWU_AUG), + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn fingerprint() { + let mut secret = [0u8; 64]; + OsRng.fill_bytes(&mut secret); + + let sk = Scalar::from_bytes_wide(&secret); + let pk_g1 = G1Affine::from(G1Affine::generator() * sk); + let pk_g2 = G2Affine::from(G2Affine::generator() * sk); + println!( + "sk {}\ng1 {}\ng2: {}", + hex::encode(sk.to_bytes()), + hex::encode(pk_g1.to_compressed()), + hex::encode(pk_g2.to_compressed()) + ); + + let cross_0 = pairing(&G1Affine::generator(), &pk_g2); + let cross_1 = pairing(&pk_g1, &G2Affine::generator()); + println!("gt_0 {:?}", cross_0); + println!("gt_1 {:?}", cross_1); + println!("eq {:?}", cross_0 == cross_1); + + assert!(false); + } + + #[test] + fn sig_basic_g1() { + // test vectors from https://github.com/algorand/bls_sigs_ref/pull/7/files + // First lines of test-vectors/sig_g1_basic/fips_186_3_B283 + let tests: &[(&str,&str,&str)] = &[ + ("067f27bbcecbad85277fa3629da11a24b2f19ba1e65a69d827fad430346c9d102e1b4452d04147c8133acc1e268490cd342a54065a1bd6470aabbad42fbddc54a9a76c68aceba397cb350327c5e6f5a6df0b5b5560f04700d536b384dd4b412e74fd1b8f782611e9426bf8ca77b2448d9a9f415bcfee30dda1ccb49737994f2d","0299ff06e019b5f78a1aec39706b22213abb601bd62b9979bf9bc89fb702e724e3ada994","89a67d9ba659d37eec881d172e04ae25b667dc5b98ab7967f889294e116be9089ded2b3b074ed80450735d3b2a3f237f"), + ("44adcb7e2462247b44c59608cbe228ada574ecb9f6f38baf30e42b589fb9b157bb0560e5a2aa5523b71cc0d7f583b502bec45d9b8352f29ee1842f42a17a5b16136feaa2efa4a0ae306402940ecd6b71e57d1467c98e7960de2a97f88b43487e4f4016af1292381d70c18c7e6eed99a14cdeb5b3caf73688658e4c5b54c81e08","009c2804f8cab768248fb3fff8a055b3f4585c00de5c1615a19f9425b9432ea09afba8f2","978d59bb5a235726c7fef7823130729b1d638d1d8094d45b76b8f25437d09a79c07fd41b249f046ef55bb96ef6436ddb"), + ("cffee6252c7eb6d91d8fe100a1e62f0ad9f862d78ca2b747a6c17b8c9ea8980dc239b3b673310e6e7483582399163e39d889abc1a613fe77849ebc09b4f7f4fe0688b8a9869ae918a88294c7ee199be50ee9460db14725ae70b449d0cb48f30e7d817ec02c0cd586119341dba0b74f0279330807cfccc99c8c340b72c1764a45","02e625a6bc6d0ce7c06231de827068bdb0abc8ffb57c82b35ee3a0f873b9473905974d34","8ee9e81799098380f1bb22ff1b9d625df88257033161463faa5a02fb42362d44deba1b76082f4be7e0bbefc68ff1f0e0"), + ("d058ab5dc07228253707ef224897ea0fcd09c3d5cc91fdce9e03c1c59c53fb4596be2ed929c7455e67ac7f4891aed3eb06ad88f2c4aaaabff045b959f900d1019d706b60526375851bb891494e99995928e4cd51c9616aa651ec77bd7e398916bb9ed3156391bf7fb1e29181e2b011dae2edaf803607def2ac6b194929a57f45","0376ac24e1b86f8a55c052d92a0bdc6472fa03acdcdbccbf7c321ec0ccd97aa0a66b4181","8af01de91658881604f3f759f8fe6770c315beaa84ee5b3d05242dacdad44a2b6e1008455d44f91e5ca8222aea5b10e3"), + ("c86f2cc7ab5df5cf1a236fd83792769474cef464032800ffe98a44cf29dbfb6f24088160eb31a11a382ff2a49f3e05e983462f5304272f96c0a002b69af3d233aebe867ee63fa46666760a6889d022c18645b491f8d71b6a3b6b4ef058e280cf625198715b64b025bf0449445d3dd7e1f27153926e617bd2c96638345431d1ed","02b50a6395fc02b9ac1841323de4520292f913519bc0d6a471aa28021322fc4dbcd7b802","a065875d636cb557fae8adb2b4e149f9670a9a76406d21912e73362a6d2b8e1c37078cda48555597a5348e4b952e6d3d"), + ]; + for (message, sk_hex, expect_hex) in tests { + let message = hex::decode(message).unwrap(); + let seed = hex::decode(sk_hex).unwrap(); + let sk = Bls12PrivateKey::from_seed(&seed).unwrap(); + let sig = sk.g1_sign_basic::(&message, None); + assert!(sk + .g2_public_key() + .verify_basic::(&sig, &message, None)); + assert_eq!(&hex::encode(sig), expect_hex); + } + } + + #[test] + fn sig_g2_basic() { + // test vectors from https://github.com/algorand/bls_sigs_ref/pull/7/files + // First lines of test-vectors/sig_g2_basic/fips_186_3_B283 + let tests: &[(&str,&str,&str)] = &[ + ("067f27bbcecbad85277fa3629da11a24b2f19ba1e65a69d827fad430346c9d102e1b4452d04147c8133acc1e268490cd342a54065a1bd6470aabbad42fbddc54a9a76c68aceba397cb350327c5e6f5a6df0b5b5560f04700d536b384dd4b412e74fd1b8f782611e9426bf8ca77b2448d9a9f415bcfee30dda1ccb49737994f2d","0299ff06e019b5f78a1aec39706b22213abb601bd62b9979bf9bc89fb702e724e3ada994","a9285367fc83373703663146c2a533c2ebcfdb71dda9f031bb20ca3b168908ff12fe5ae086e4e1e0f74e85cacf4f3ef20ed98849c4c8d45d0536d1759cd6208a5ba3d1966422a908ef344af4d4742b8b09fa88711b7d2c957bc073c0072ebdf7"), + ("44adcb7e2462247b44c59608cbe228ada574ecb9f6f38baf30e42b589fb9b157bb0560e5a2aa5523b71cc0d7f583b502bec45d9b8352f29ee1842f42a17a5b16136feaa2efa4a0ae306402940ecd6b71e57d1467c98e7960de2a97f88b43487e4f4016af1292381d70c18c7e6eed99a14cdeb5b3caf73688658e4c5b54c81e08","009c2804f8cab768248fb3fff8a055b3f4585c00de5c1615a19f9425b9432ea09afba8f2","a287aa25075030d5741ad7707f68ea61fe0b965bebc7bf6bff49be60c104a7abdf2de75cd53b9beb64f90acf667851a007d0547a0e25068afd3c52a45e5bebe7a4ab1a4de9dbed8a25bee03468265169512d6755bf6cc8e53b90d2bafd10934d"), + ("cffee6252c7eb6d91d8fe100a1e62f0ad9f862d78ca2b747a6c17b8c9ea8980dc239b3b673310e6e7483582399163e39d889abc1a613fe77849ebc09b4f7f4fe0688b8a9869ae918a88294c7ee199be50ee9460db14725ae70b449d0cb48f30e7d817ec02c0cd586119341dba0b74f0279330807cfccc99c8c340b72c1764a45","02e625a6bc6d0ce7c06231de827068bdb0abc8ffb57c82b35ee3a0f873b9473905974d34","b90b74be15b24343c7b0d8647ea4625bdba477fdee5b28e847ab451f19bba7b93abfb14a6fa3394dadc055e59bcb1fba056f721c4af27b6870371dde1750646e1a29194956b41a711c363a0fdc66609cdf812ec1e2a8723fe87acd1bae751704"), + ("d058ab5dc07228253707ef224897ea0fcd09c3d5cc91fdce9e03c1c59c53fb4596be2ed929c7455e67ac7f4891aed3eb06ad88f2c4aaaabff045b959f900d1019d706b60526375851bb891494e99995928e4cd51c9616aa651ec77bd7e398916bb9ed3156391bf7fb1e29181e2b011dae2edaf803607def2ac6b194929a57f45","0376ac24e1b86f8a55c052d92a0bdc6472fa03acdcdbccbf7c321ec0ccd97aa0a66b4181","803e8f4be33388a272ff3a0f80986f7edd9ef35505d6754c2a63ca6a9296e0576f0b069b6761e9ae95006262f847e33b0cc660a0f0a05d19032e1a15d61c524da28869aa7aee963b4c35eaa8b2d4d3e4eeaa361619e8be911f97c5ef8c69df27"), + ("c86f2cc7ab5df5cf1a236fd83792769474cef464032800ffe98a44cf29dbfb6f24088160eb31a11a382ff2a49f3e05e983462f5304272f96c0a002b69af3d233aebe867ee63fa46666760a6889d022c18645b491f8d71b6a3b6b4ef058e280cf625198715b64b025bf0449445d3dd7e1f27153926e617bd2c96638345431d1ed","02b50a6395fc02b9ac1841323de4520292f913519bc0d6a471aa28021322fc4dbcd7b802","8f8b7a649bb0eb1341eb9de65e94d9b3eade5173604a6b538771308d28d55d05615eb9dfd34a9e70dcf7f92c672854fa062d66edd7cb8b5391b799f2d4cf5cbd9555b61f26f4ab62b3589a0c4d9ee050597798cf7d6403a63403729de1b82e58"), + ]; + for (message, sk_hex, expect_hex) in tests { + let message = hex::decode(message).unwrap(); + let seed = hex::decode(sk_hex).unwrap(); + let sk = Bls12PrivateKey::from_seed(&seed).unwrap(); + let sig = sk.g2_sign_basic::(&message, None); + assert!(sk + .g1_public_key() + .verify_basic::(&sig, &message, None)); + assert_eq!(&hex::encode(sig), expect_hex); + } + } + + #[test] + fn sig_g1_aug() { + // test vectors from https://github.com/algorand/bls_sigs_ref/pull/7/files + // First lines of test-vectors/sig_g1_aug/fips_186_3_B283 + let tests: &[(&str,&str,&str)] = &[ + ("067f27bbcecbad85277fa3629da11a24b2f19ba1e65a69d827fad430346c9d102e1b4452d04147c8133acc1e268490cd342a54065a1bd6470aabbad42fbddc54a9a76c68aceba397cb350327c5e6f5a6df0b5b5560f04700d536b384dd4b412e74fd1b8f782611e9426bf8ca77b2448d9a9f415bcfee30dda1ccb49737994f2d","0299ff06e019b5f78a1aec39706b22213abb601bd62b9979bf9bc89fb702e724e3ada994","aaa1ba14a100902b89aa04239faadd48be036d118acbbc12fd7847bfc492534b77ded3b05fbf0a9fc863f77a2fed548d"), + ("44adcb7e2462247b44c59608cbe228ada574ecb9f6f38baf30e42b589fb9b157bb0560e5a2aa5523b71cc0d7f583b502bec45d9b8352f29ee1842f42a17a5b16136feaa2efa4a0ae306402940ecd6b71e57d1467c98e7960de2a97f88b43487e4f4016af1292381d70c18c7e6eed99a14cdeb5b3caf73688658e4c5b54c81e08","009c2804f8cab768248fb3fff8a055b3f4585c00de5c1615a19f9425b9432ea09afba8f2","a648266d72c8c47293dda9ecefb9f7ec4eff5b1a7676b87b03e9cf3ab41cc985f787ce25c55c87a2d782f8827f46a3d4"), + ("cffee6252c7eb6d91d8fe100a1e62f0ad9f862d78ca2b747a6c17b8c9ea8980dc239b3b673310e6e7483582399163e39d889abc1a613fe77849ebc09b4f7f4fe0688b8a9869ae918a88294c7ee199be50ee9460db14725ae70b449d0cb48f30e7d817ec02c0cd586119341dba0b74f0279330807cfccc99c8c340b72c1764a45","02e625a6bc6d0ce7c06231de827068bdb0abc8ffb57c82b35ee3a0f873b9473905974d34","a75a5dbdf03daba8467d50ad2af6b400becc3bcdcd89a72d8da94c5d8f44d3497758bcd58995e10570425cda4f58ee56"), + ("d058ab5dc07228253707ef224897ea0fcd09c3d5cc91fdce9e03c1c59c53fb4596be2ed929c7455e67ac7f4891aed3eb06ad88f2c4aaaabff045b959f900d1019d706b60526375851bb891494e99995928e4cd51c9616aa651ec77bd7e398916bb9ed3156391bf7fb1e29181e2b011dae2edaf803607def2ac6b194929a57f45","0376ac24e1b86f8a55c052d92a0bdc6472fa03acdcdbccbf7c321ec0ccd97aa0a66b4181","ad256fbe6c2634f0ff3e374e8f28ea6784c57b458942b939972c4fa7cb2ea930d237b65a5b5be4ef81855a8e1fca4a06"), + ("c86f2cc7ab5df5cf1a236fd83792769474cef464032800ffe98a44cf29dbfb6f24088160eb31a11a382ff2a49f3e05e983462f5304272f96c0a002b69af3d233aebe867ee63fa46666760a6889d022c18645b491f8d71b6a3b6b4ef058e280cf625198715b64b025bf0449445d3dd7e1f27153926e617bd2c96638345431d1ed","02b50a6395fc02b9ac1841323de4520292f913519bc0d6a471aa28021322fc4dbcd7b802","a676ae116b734121d9a0263a37da6bdc63b9cd60a97e98e45570ae2433a75ac323b396ec5a188fda20e86992301d28ed"), + ]; + for (message, sk_hex, expect_hex) in tests { + let message = hex::decode(message).unwrap(); + let seed = hex::decode(sk_hex).unwrap(); + let sk = Bls12PrivateKey::from_seed(&seed).unwrap(); + let sig = sk.g1_sign_aug::(&message, None); + assert!(sk + .g2_public_key() + .verify_aug::(&sig, &message, None)); + assert_eq!(&hex::encode(sig), expect_hex); + } + } + + #[test] + fn sig_g2_aug() { + // test vectors from https://github.com/algorand/bls_sigs_ref/pull/7/files + // First lines of test-vectors/sig_g2_aug/fips_186_3_B283 + let tests: &[(&str,&str,&str)] = &[ + ("067f27bbcecbad85277fa3629da11a24b2f19ba1e65a69d827fad430346c9d102e1b4452d04147c8133acc1e268490cd342a54065a1bd6470aabbad42fbddc54a9a76c68aceba397cb350327c5e6f5a6df0b5b5560f04700d536b384dd4b412e74fd1b8f782611e9426bf8ca77b2448d9a9f415bcfee30dda1ccb49737994f2d","0299ff06e019b5f78a1aec39706b22213abb601bd62b9979bf9bc89fb702e724e3ada994","905c02745f0f549b3c6e2c4f246a8f65a4062f8e3d82dc32a9c97607eac2ad749c01083e3e13cd573e8229e227d798861400b9ad2cfc91126059c1a097ae9762791e4940c5f11a686250f83565e78a111f2977bfad30ac15b5dc4828b34eb9d0"), + ("44adcb7e2462247b44c59608cbe228ada574ecb9f6f38baf30e42b589fb9b157bb0560e5a2aa5523b71cc0d7f583b502bec45d9b8352f29ee1842f42a17a5b16136feaa2efa4a0ae306402940ecd6b71e57d1467c98e7960de2a97f88b43487e4f4016af1292381d70c18c7e6eed99a14cdeb5b3caf73688658e4c5b54c81e08","009c2804f8cab768248fb3fff8a055b3f4585c00de5c1615a19f9425b9432ea09afba8f2","aa16169aaaf4ea98f5a20d132dce6388f533c7f01ff7aaf015c9b78f6e1072a5642b3192119d7f169caca757e923b44b0381bf45909591455b750356fcdaba451f4293090322e5ddc9a3b17b91ed2550ec983314b7cc9f4f762dd57a07a8d4af"), + ("cffee6252c7eb6d91d8fe100a1e62f0ad9f862d78ca2b747a6c17b8c9ea8980dc239b3b673310e6e7483582399163e39d889abc1a613fe77849ebc09b4f7f4fe0688b8a9869ae918a88294c7ee199be50ee9460db14725ae70b449d0cb48f30e7d817ec02c0cd586119341dba0b74f0279330807cfccc99c8c340b72c1764a45","02e625a6bc6d0ce7c06231de827068bdb0abc8ffb57c82b35ee3a0f873b9473905974d34","a1abbf78718265c73643ac29aa823c8a11b02c34562c2b06a4a449fd964e19c57341d90e44f2db2849bea7d06a32b5bf114c80312057dd9eb3f46b8466233c52abbd6131314f1d5ec8aa277b58bbd09cedcf15d98dac3f723c534d56c9fef01d"), + ("d058ab5dc07228253707ef224897ea0fcd09c3d5cc91fdce9e03c1c59c53fb4596be2ed929c7455e67ac7f4891aed3eb06ad88f2c4aaaabff045b959f900d1019d706b60526375851bb891494e99995928e4cd51c9616aa651ec77bd7e398916bb9ed3156391bf7fb1e29181e2b011dae2edaf803607def2ac6b194929a57f45","0376ac24e1b86f8a55c052d92a0bdc6472fa03acdcdbccbf7c321ec0ccd97aa0a66b4181","a00990857bc78b952f7e1b8279de59ecad9b24367b6b03fa5d1d3bbf70d06e8895802faaefa41b514f3dae706d305a86048cd87a3d92da71979f220f4f6649fb6696804f581867b46961b1b10d368a4656386d6f0b52340902bd292157358445"), + ("c86f2cc7ab5df5cf1a236fd83792769474cef464032800ffe98a44cf29dbfb6f24088160eb31a11a382ff2a49f3e05e983462f5304272f96c0a002b69af3d233aebe867ee63fa46666760a6889d022c18645b491f8d71b6a3b6b4ef058e280cf625198715b64b025bf0449445d3dd7e1f27153926e617bd2c96638345431d1ed","02b50a6395fc02b9ac1841323de4520292f913519bc0d6a471aa28021322fc4dbcd7b802","91f183d0c29ccf5cd8116ace89605a7ae0f4b6e980060dce7e87aa364b04506f59679e0c9d3305a8869ff067e39265780442756b48bcfc07eb5dd289b0fd4062e54d30cf0b4f9a5dd9dae41e7b2cb916266cd88e7c3380c638e9704a58ad025f"), + ]; + for (message, sk_hex, expect_hex) in tests { + let message = hex::decode(message).unwrap(); + let seed = hex::decode(sk_hex).unwrap(); + let sk = Bls12PrivateKey::from_seed(&seed).unwrap(); + let sig = sk.g2_sign_aug::(&message, None); + assert!(sk + .g1_public_key() + .verify_aug::(&sig, &message, None)); + assert_eq!(&hex::encode(sig), expect_hex); + } + } +} diff --git a/askar-keys/src/alg/ed25519.rs b/askar-keys/src/alg/ed25519.rs new file mode 100644 index 00000000..5ecd90b1 --- /dev/null +++ b/askar-keys/src/alg/ed25519.rs @@ -0,0 +1,409 @@ +use alloc::boxed::Box; +use core::convert::{TryFrom, TryInto}; + +use curve25519_dalek::edwards::CompressedEdwardsY; +use ed25519_dalek::{ExpandedSecretKey, PublicKey, SecretKey, Signature, Signer}; +use rand::rngs::OsRng; +use serde_json::json; +use sha2::{self, Digest}; +use x25519_dalek::{PublicKey as XPublicKey, StaticSecret as XSecretKey}; +use zeroize::Zeroize; + +use super::x25519::X25519KeyPair; +use crate::{ + // any::{AnyPrivateKey, AnyPublicKey}, + buffer::{SecretBytes, WriteBuffer}, + caps::{KeyAlg, KeyCapSign, KeyCapVerify, SignatureType}, + error::Error, +}; + +// FIXME - check for low-order points when loading public keys? +// https://github.com/tendermint/tmkms/pull/279 + +pub const EDDSA_SIGNATURE_LENGTH: usize = 64; + +pub const PUBLIC_KEY_LENGTH: usize = 32; +pub const SECRET_KEY_LENGTH: usize = 32; +pub const KEYPAIR_LENGTH: usize = 64; + +// FIXME implement debug +pub struct Ed25519KeyPair(Box); + +impl Ed25519KeyPair { + pub fn generate() -> Result { + Ok(Self::from_secret_key(SecretKey::generate(&mut OsRng))) + } + + pub fn from_seed(ikm: &[u8]) -> Result { + Ok(Self::from_secret_key( + SecretKey::from_bytes(ikm).map_err(|_| err_msg!("Invalid key material"))?, + )) + } + + pub fn from_keypair_bytes(kp: &[u8]) -> Result { + let kp = if kp.len() == 32 { + Self::from_secret_key( + SecretKey::from_bytes(kp).map_err(|_| err_msg!("Invalid keypair bytes"))?, + ) + } else if kp.len() == KEYPAIR_LENGTH { + let sk = SecretKey::from_bytes(kp).map_err(|_| err_msg!("Invalid keypair bytes"))?; + let pk = PublicKey::from_bytes(kp).map_err(|_| err_msg!("Invalid keypair bytes"))?; + Self(Box::new(Keypair { + secret: Some(sk), + public: pk, + })) + } else { + return Err(err_msg!("Invalid keypair bytes")); + }; + Ok(kp) + } + + pub(crate) fn from_secret_key(sk: SecretKey) -> Self { + let public = PublicKey::from(&sk); + Self(Box::new(Keypair { + secret: Some(sk), + public, + })) + } + + pub fn keypair_bytes(&self) -> Option { + SecretBytes::from(self.0.to_bytes().to_vec()) + } + + pub fn to_x25519(&self) -> X25519KeyPair { + let hash = sha2::Sha512::digest(&self.0.secret.to_bytes()[..]); + let output: [u8; 32] = (&hash[..32]).try_into().unwrap(); + // clamp result + let secret = XSecretKey::from(output); + let public = XPublicKey::from(&secret); + X25519KeyPair::new(Some(secret), public) + + // pub fn to_x25519(&self) -> X25519PublicKey { + // let public = XPublicKey::from( + // CompressedEdwardsY(self.0.to_bytes()) + // .decompress() + // .unwrap() + // .to_montgomery() + // .to_bytes(), + // ); + // X25519PublicKey(public) + // } + } + + pub fn private_key_bytes(&self) -> Option { + SecretBytes::from(self.0.secret.to_bytes().to_vec()) + } + + pub fn signing_key(&self) -> Option> { + self.0 + .secret + .as_ref() + .map(|sk| Ed25519SigningKey(ExpandedSecretKey::from(sk), &self.0.public)) + } + + pub fn verify_signature(&self, message: &[u8], signature: &[u8]) -> bool { + if let Ok(sig) = Signature::try_from(signature) { + self.0.public.verify_strict(message, &sig).is_ok() + } else { + false + } + } +} + +impl Clone for Ed25519KeyPair { + fn clone(&self) -> Self { + Self(Box::new(Keypair { + secret: self + .0 + .secret + .as_ref() + .map(|sk| SecretKey::from_bytes(&sk.as_bytes()[..]).unwrap()), + public: self.0.public.clone(), + })) + } +} + +impl KeyCapSign for Ed25519KeyPair { + fn key_sign( + &self, + data: &[u8], + sig_type: Option, + out: &mut B, + ) -> Result { + match sig_type { + None | Some(SignatureType::EdDSA) => { + if let Some(signer) = self.signing_key() { + let sig = signer.sign(data); + out.extend_from_slice(&sig[..]); + Ok(EDDSA_SIGNATURE_LENGTH) + } else { + Err(err_msg!(Unsupported, "Undefined private key")) + } + } + #[allow(unreachable_patterns)] + _ => Err(err_msg!(Unsupported, "Unsupported signature type")), + } + } +} + +impl KeyCapVerify for Ed25519KeyPair { + fn key_verify( + &self, + message: &[u8], + signature: &[u8], + sig_type: Option, + ) -> Result { + match sig_type { + None | Some(SignatureType::EdDSA) => Ok(self.verify_signature(message, signature)), + #[allow(unreachable_patterns)] + _ => Err(err_msg!(Unsupported, "Unsupported signature type")), + } + } +} + +struct Keypair { + // SECURITY: SecretKey zeroizes on drop + secret: Option, + public: PublicKey, +} + +/// FIXME implement debug +// SECURITY: ExpandedSecretKey zeroizes on drop +pub struct Ed25519SigningKey<'p>(ExpandedSecretKey, &'p PublicKey); + +impl Ed25519SigningKey<'_> { + pub fn sign(&self, message: &[u8]) -> [u8; EDDSA_SIGNATURE_LENGTH] { + self.0.sign(message, &self.1).to_bytes() + } +} + +// impl TryFrom<&AnyPrivateKey> for Ed25519KeyPair { +// type Error = Error; + +// fn try_from(value: &AnyPrivateKey) -> Result { +// if value.alg == KeyAlg::Ed25519 { +// Self::from_bytes(value.data.as_ref()) +// } else { +// Err(err_msg!(Unsupported, "Expected ed25519 key type")) +// } +// } +// } + +// #[derive(Clone, Debug, PartialEq, Eq)] +// pub struct Ed25519PublicKey(PublicKey); + +// impl Ed25519PublicKey { +// pub fn from_str(key: impl AsRef) -> Result { +// let key = key.as_ref(); +// let key = key.strip_suffix(":ed25519").unwrap_or(key); +// let mut bval = [0u8; 32]; +// bs58::decode(key) +// .into(&mut bval) +// .map_err(|_| err_msg!("Invalid base58 public key"))?; +// Self::from_bytes(bval) +// } + +// pub fn from_bytes(pk: impl AsRef<[u8]>) -> Result { +// let pk = +// PublicKey::from_bytes(pk.as_ref()).map_err(|_| err_msg!("Invalid public key bytes"))?; +// Ok(Self(pk)) +// } + +// pub fn to_base58(&self) -> String { +// bs58::encode(self.to_bytes()).into_string() +// } + +// pub fn to_string(&self) -> String { +// let mut sval = String::with_capacity(64); +// bs58::encode(self.to_bytes()).into(&mut sval).unwrap(); +// sval.push_str(":ed25519"); +// sval +// } + +// pub fn to_bytes(&self) -> [u8; 32] { +// self.0.to_bytes() +// } + +// pub fn to_jwk(&self) -> Result { +// let x = base64::encode_config(self.to_bytes(), base64::URL_SAFE_NO_PAD); +// Ok(json!({ +// "kty": "OKP", +// "crv": "Ed25519", +// "x": x, +// "key_ops": ["verify"] +// })) +// } + +// pub fn to_x25519(&self) -> X25519PublicKey { +// let public = XPublicKey::from( +// CompressedEdwardsY(self.0.to_bytes()) +// .decompress() +// .unwrap() +// .to_montgomery() +// .to_bytes(), +// ); +// X25519PublicKey(public) +// } + +// pub fn verify(&self, message: &[u8], signature: &[u8; EDDSA_SIGNATURE_LENGTH]) -> bool { +// self.0.verify_strict(message, &signature.into()).is_ok() +// } +// } + +// impl KeyCapVerify for Ed25519PublicKey { +// fn key_verify( +// &self, +// data: &[u8], +// signature: &[u8], +// sig_type: Option, +// ) -> Result { +// match sig_type { +// None | Some(SignatureType::EdDSA) => { +// if let Ok(sig) = TryInto::<&[u8; EDDSA_SIGNATURE_LENGTH]>::try_into(signature) { +// Ok(self.verify(data, sig)) +// } else { +// Ok(false) +// } +// } +// #[allow(unreachable_patterns)] +// _ => Err(err_msg!(Unsupported, "Unsupported signature type")), +// } +// } +// } + +// impl TryFrom<&AnyPublicKey> for Ed25519PublicKey { +// type Error = Error; + +// fn try_from(value: &AnyPublicKey) -> Result { +// if value.alg == KeyAlg::Ed25519 { +// Self::from_bytes(&value.data) +// } else { +// Err(err_msg!(Unsupported, "Expected ed25519 key type")) +// } +// } +// } + +// pub(super) fn encode_signature( +// signature: &[u8], +// sig_format: Option, +// ) -> Result, Error> { +// match sig_format { +// None | Some(SignatureFormat::Base58) => Ok(bs58::encode(signature).into_vec()), +// Some(SignatureFormat::Raw) => Ok(signature.to_vec()), +// #[allow(unreachable_patterns)] +// _ => Err(err_msg!(Unsupported, "Unsupported signature format")), +// } +// } + +// pub(super) fn decode_signature( +// sig_input: &[u8], +// sig_output: &mut impl AsMut<[u8]>, +// sig_format: Option, +// ) -> Result<(), Error> { +// match sig_format { +// None | Some(SignatureFormat::Base58) => { +// bs58::decode(sig_input) +// .into(sig_output) +// .map_err(|_| err_msg!("Invalid base58 signature"))?; +// Ok(()) +// } +// Some(SignatureFormat::Raw) => { +// if sig_input.len() != sig_output.as_mut().len() { +// return Err(err_msg!("Invalid raw signature")); +// } +// sig_output.as_mut().copy_from_slice(sig_input); +// Ok(()) +// } +// #[allow(unreachable_patterns)] +// _ => Err(err_msg!(Unsupported, "Unsupported signature format")), +// } +// } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn expand_keypair() { + let seed = b"000000000000000000000000Trustee1"; + let test_sk = hex::decode("3030303030303030303030303030303030303030303030305472757374656531e33aaf381fffa6109ad591fdc38717945f8fabf7abf02086ae401c63e9913097").unwrap(); + + let kp = Ed25519KeyPair::from_seed(seed).unwrap(); + assert_eq!(kp.to_bytes(), test_sk); + + // test round trip + let cmp = Ed25519KeyPair::from_bytes(&test_sk).unwrap(); + assert_eq!(cmp.to_bytes(), test_sk); + } + + #[test] + fn ed25519_to_x25519() { + let test_keypair = hex::decode("1c1179a560d092b90458fe6ab8291215a427fcd6b3927cb240701778ef55201927c96646f2d4632d4fc241f84cbc427fbc3ecaa95becba55088d6c7b81fc5bbf").unwrap(); + let x_sk = hex::decode("08e7286c232ec71b37918533ea0229bf0c75d3db4731df1c5c03c45bc909475f") + .unwrap(); + let x_pk = hex::decode("9b4260484c889158c128796103dc8d8b883977f2ef7efb0facb12b6ca9b2ae3d") + .unwrap(); + let x_pair = Ed25519KeyPair::from_bytes(&test_keypair) + .unwrap() + .to_x25519() + .to_bytes(); + assert_eq!(&x_pair[..32], x_sk); + assert_eq!(&x_pair[32..], x_pk); + } + + #[test] + fn jwk_expected() { + // from https://www.connect2id.com/blog/nimbus-jose-jwt-6 + // { + // "kty" : "OKP", + // "crv" : "Ed25519", + // "x" : "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo", + // "d" : "nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A" + // "use" : "sig", + // "kid" : "FdFYFzERwC2uCBB46pZQi4GG85LujR8obt-KWRBICVQ" + // } + let test_pvt_b64 = "nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A"; + let test_pvt = base64::decode_config(test_pvt_b64, base64::URL_SAFE).unwrap(); + let sk = Ed25519KeyPair::from_bytes(&test_pvt).expect("Error creating signing key"); + let vk = sk.public_key(); + let jwk = vk.to_jwk().expect("Error converting public key to JWK"); + assert_eq!(jwk["kty"], "OKP"); + assert_eq!(jwk["crv"], "Ed25519"); + assert_eq!(jwk["x"], "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"); + assert_eq!( + base64::encode_config(sk.private_key(), base64::URL_SAFE_NO_PAD), + test_pvt_b64 + ); + } + + #[test] + fn sign_verify_expected() { + let test_msg = b"This is a dummy message for use with tests"; + let test_sig = hex::decode("451b5b8e8725321541954997781de51f4142e4a56bab68d24f6a6b92615de5eefb74134138315859a32c7cf5fe5a488bc545e2e08e5eedfd1fb10188d532d808").unwrap(); + + let test_keypair = hex::decode("1c1179a560d092b90458fe6ab8291215a427fcd6b3927cb240701778ef55201927c96646f2d4632d4fc241f84cbc427fbc3ecaa95becba55088d6c7b81fc5bbf").unwrap(); + let kp = Ed25519KeyPair::from_bytes(&test_keypair).unwrap(); + let sig = kp.sign(&test_msg[..]); + assert_eq!(sig, test_sig.as_slice()); + assert_eq!(kp.public_key().verify(&test_msg[..], sig), true); + assert_eq!(kp.public_key().verify(b"Not the message", sig), false); + assert_eq!(kp.public_key().verify(&test_msg[..], [0u8; 64]), false); + } + + #[test] + fn key_exchange_random() { + let kp1 = X25519KeyPair::generate().unwrap(); + let kp2 = X25519KeyPair::generate().unwrap(); + assert_ne!(kp1.to_bytes(), kp2.to_bytes()); + + let xch1 = kp1.key_exchange_with(&kp2.public_key()); + let xch2 = kp2.key_exchange_with(&kp1.public_key()); + assert_eq!(xch1, xch2); + + // test round trip + let xch3 = X25519KeyPair::from_bytes(&kp1.to_bytes()) + .unwrap() + .key_exchange_with(&kp2.public_key()); + assert_eq!(xch3, xch1); + } +} diff --git a/askar-keys/src/alg/k256.rs b/askar-keys/src/alg/k256.rs new file mode 100644 index 00000000..b0469cb7 --- /dev/null +++ b/askar-keys/src/alg/k256.rs @@ -0,0 +1,394 @@ +use core::convert::TryInto; + +use k256::{ + ecdsa::{ + signature::{Signer, Verifier}, + Signature, SigningKey, VerifyingKey, + }, + elliptic_curve::{ + ecdh::diffie_hellman, + sec1::{Coordinates, FromEncodedPoint}, + }, + EncodedPoint, PublicKey, SecretKey, +}; +use rand::rngs::OsRng; +use serde_json::json; + +use crate::{ + // any::{AnyPrivateKey, AnyPublicKey}, + buffer::{SecretBytes, WriteBuffer}, + caps::{EcCurves, KeyAlg, KeyCapSign, KeyCapVerify, SignatureFormat, SignatureType}, + error::Error, +}; + +pub const ES256K_SIGNATURE_LENGTH: usize = 64; + +#[derive(Clone, Debug)] +pub struct K256SigningKey(SecretKey); + +impl K256SigningKey { + pub fn generate() -> Result { + Ok(Self(SecretKey::random(OsRng))) + } + + pub fn from_bytes(pt: &[u8]) -> Result { + let kp = SecretKey::from_bytes(pt).map_err(|_| err_msg!("Invalid signing key bytes"))?; + Ok(Self(kp)) + } + + pub fn to_bytes(&self) -> SecretBytes { + SecretBytes::from(self.0.to_bytes().to_vec()) + } + + pub fn public_key(&self) -> K256VerifyingKey { + K256VerifyingKey(SigningKey::from(&self.0).verify_key()) + } + + pub fn sign(&self, message: &[u8]) -> [u8; ES256K_SIGNATURE_LENGTH] { + let skey = SigningKey::from(self.0.clone()); + let sig: Signature = skey.sign(message); + let mut sigb = [0u8; 64]; + sigb[0..32].copy_from_slice(&sig.r().to_bytes()); + sigb[32..].copy_from_slice(&sig.s().to_bytes()); + sigb + } + + pub fn verify(&self, message: &[u8], signature: &[u8; ES256K_SIGNATURE_LENGTH]) -> bool { + let mut r = [0u8; 32]; + let mut s = [0u8; 32]; + r.copy_from_slice(&signature[..32]); + s.copy_from_slice(&signature[32..]); + if let Ok(sig) = Signature::from_scalars(r, s) { + let vk = VerifyingKey::from(SigningKey::from(&self.0)); + vk.verify(message, &sig).is_ok() + } else { + false + } + } +} + +impl KeyCapSign for K256SigningKey { + fn key_sign( + &self, + data: &[u8], + sig_type: Option, + out: &mut B, + ) -> Result { + match sig_type { + None | Some(SignatureType::ES256K) => { + let sig = self.sign(data); + out.extend_from_slice(&sig[..]); + Ok(ES256K_SIGNATURE_LENGTH) + } + #[allow(unreachable_patterns)] + _ => Err(err_msg!(Unsupported, "Unsupported signature type")), + } + } +} + +impl KeyCapVerify for K256SigningKey { + fn key_verify( + &self, + message: &[u8], + signature: &[u8], + sig_type: Option, + ) -> Result { + match sig_type { + None | Some(SignatureType::ES256K) => { + if let Ok(sig) = TryInto::<&[u8; ES256K_SIGNATURE_LENGTH]>::try_into(signature) { + Ok(self.verify(message, sig)) + } else { + Ok(false) + } + } + #[allow(unreachable_patterns)] + _ => Err(err_msg!(Unsupported, "Unsupported signature type")), + } + } +} + +// impl TryFrom<&AnyPrivateKey> for K256SigningKey { +// type Error = Error; + +// fn try_from(value: &AnyPrivateKey) -> Result { +// if value.alg == KeyAlg::Ecdsa(EcCurves::Secp256k1) { +// Self::from_bytes(value.data.as_ref()) +// } else { +// Err(err_msg!(Unsupported, "Expected k-256 key type")) +// } +// } +// } + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct K256VerifyingKey(VerifyingKey); + +impl K256VerifyingKey { + pub fn from_str(key: impl AsRef) -> Result { + let key = key.as_ref(); + let key = key.strip_suffix(":k256/ecdsa").unwrap_or(key); + let mut bval = [0u8; 32]; + bs58::decode(key) + .into(&mut bval) + .map_err(|_| err_msg!("Invalid base58 public key"))?; + Self::from_bytes(bval) + } + + pub fn from_bytes(pk: impl AsRef<[u8]>) -> Result { + let encp = EncodedPoint::from_bytes(pk.as_ref()) + .map_err(|_| err_msg!("Invalid public key bytes"))?; + if encp.is_identity() { + return Err(err_msg!("Invalid public key: identity point")); + } + Ok(Self( + VerifyingKey::from_encoded_point(&encp).map_err(|_| err_msg!("Invalid public key"))?, + )) + } + + pub fn to_bytes(&self) -> [u8; 32] { + let mut k = [0u8; 32]; + let encp = EncodedPoint::from(&self.0); + k.copy_from_slice(encp.as_bytes()); + k + } + + // pub fn to_string(&self) -> String { + // let mut sval = String::with_capacity(64); + // bs58::encode(self.to_bytes()).into(&mut sval).unwrap(); + // sval.push_str(":k256/ecdsa"); + // sval + // } + + pub fn to_jwk(&self) -> Result { + let encp = EncodedPoint::encode(self.0.clone(), false); + let (x, y) = match encp.coordinates() { + Coordinates::Identity => return Err(err_msg!("Cannot convert identity point to JWK")), + Coordinates::Uncompressed { x, y } => ( + base64::encode_config(x, base64::URL_SAFE_NO_PAD), + base64::encode_config(y, base64::URL_SAFE_NO_PAD), + ), + Coordinates::Compressed { .. } => unreachable!(), + }; + Ok(json!({ + "kty": "EC", + "crv": "secp256k1", + "x": x, + "y": y, + "key_ops": ["verify"] + })) + } + + pub fn verify(&self, message: &[u8], signature: [u8; 64]) -> bool { + let mut r = [0u8; 32]; + let mut s = [0u8; 32]; + r.copy_from_slice(&signature[..32]); + s.copy_from_slice(&signature[32..]); + if let Ok(sig) = Signature::from_scalars(r, s) { + self.0.verify(message, &sig).is_ok() + } else { + false + } + } +} + +impl KeyCapVerify for K256VerifyingKey { + fn key_verify( + &self, + data: &[u8], + signature: &[u8], + sig_type: Option, + ) -> Result { + match sig_type { + None | Some(SignatureType::ES256K) => { + let mut sig = [0u8; ES256K_SIGNATURE_LENGTH]; + if data.len() != ES256K_SIGNATURE_LENGTH { + return Err(err_msg!("Invalid ES256K signature")); + } + Ok(self.verify(data, sig)) + } + #[allow(unreachable_patterns)] + _ => Err(err_msg!(Unsupported, "Unsupported signature type")), + } + } +} + +// impl TryFrom<&AnyPublicKey> for K256VerifyingKey { +// type Error = Error; + +// fn try_from(value: &AnyPublicKey) -> Result { +// if value.alg == KeyAlg::Ecdsa(EcCurves::Secp256k1) { +// Self::from_bytes(&value.data) +// } else { +// Err(err_msg!(Unsupported, "Expected k-256 key type")) +// } +// } +// } + +#[derive(Clone, Debug)] +pub struct K256ExchPrivateKey(SecretKey); + +impl K256ExchPrivateKey { + pub fn generate() -> Result { + Ok(Self(SecretKey::random(OsRng))) + } + + pub fn from_bytes(pt: &[u8]) -> Result { + let kp = SecretKey::from_bytes(pt).map_err(|_| err_msg!("Invalid signing key bytes"))?; + Ok(Self(kp)) + } + + pub fn to_bytes(&self) -> SecretBytes { + SecretBytes::from(self.0.to_bytes().to_vec()) + } + + pub fn key_exchange_with(&self, pk: &K256ExchPublicKey) -> SecretBytes { + let xk = diffie_hellman(self.0.secret_scalar(), pk.0.as_affine()); + SecretBytes::from(xk.as_bytes().to_vec()) + } + + pub fn public_key(&self) -> K256ExchPublicKey { + K256ExchPublicKey(self.0.public_key()) + } +} + +// impl TryFrom<&AnyPrivateKey> for K256ExchPrivateKey { +// type Error = Error; + +// fn try_from(value: &AnyPrivateKey) -> Result { +// if value.alg == KeyAlg::Ecdh(EcCurves::Secp256k1) { +// Self::from_bytes(value.data.as_ref()) +// } else { +// Err(err_msg!(Unsupported, "Expected k-256 key type")) +// } +// } +// } + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct K256ExchPublicKey(PublicKey); + +impl K256ExchPublicKey { + pub fn from_str(key: impl AsRef) -> Result { + let key = key.as_ref(); + let key = key.strip_suffix(":k256/ecdh").unwrap_or(key); + let mut bval = [0u8; 32]; + bs58::decode(key) + .into(&mut bval) + .map_err(|_| err_msg!("Invalid base58 public key"))?; + Self::from_bytes(bval) + } + + pub fn from_bytes(pk: impl AsRef<[u8]>) -> Result { + let encp = EncodedPoint::from_bytes(pk.as_ref()) + .map_err(|_| err_msg!("Invalid public key bytes"))?; + if encp.is_identity() { + return Err(err_msg!("Invalid public key: identity point")); + } + Ok(Self( + PublicKey::from_encoded_point(&encp).ok_or_else(|| err_msg!("Invalid public key"))?, + )) + } + + pub fn to_bytes(&self) -> [u8; 32] { + let mut k = [0u8; 32]; + let encp = EncodedPoint::from(&self.0); + k.copy_from_slice(encp.as_bytes()); + k + } + + pub fn to_jwk(&self) -> Result { + let encp = EncodedPoint::encode(self.0, false); + let (x, y) = match encp.coordinates() { + Coordinates::Identity => return Err(err_msg!("Cannot convert identity point to JWK")), + Coordinates::Uncompressed { x, y } => ( + base64::encode_config(x, base64::URL_SAFE_NO_PAD), + base64::encode_config(y, base64::URL_SAFE_NO_PAD), + ), + Coordinates::Compressed { .. } => unreachable!(), + }; + Ok(json!({ + "kty": "EC", + "crv": "K-256", + "x": x, + "y": y, + "key_ops": ["deriveKey"] + })) + } +} + +// impl TryFrom<&AnyPublicKey> for K256ExchPublicKey { +// type Error = Error; + +// fn try_from(value: &AnyPublicKey) -> Result { +// if value.alg == KeyAlg::Ecdh(EcCurves::Secp256k1) { +// Self::from_bytes(&value.data) +// } else { +// Err(err_msg!(Unsupported, "Expected k-256 key type")) +// } +// } +// } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn create_keypair_random() { + let sk = K256SigningKey::generate().expect("Error creating signing key"); + let _vk = sk.public_key(); + } + + #[test] + fn jwk_expected() { + // {"kty":"EC", + // "crv":"secp256k1", + // "x":"Hr99bGFN0HWT3ZgNNAXvmz-6Wk1HIxwsyFqUZH8PBFc", + // "y":"bonc3DRZe51NzuWetY336VmTYdUFvPK7DivSPHeu_CA", + // "d":"jv_VrhPomm6_WOzb74xF4eMI0hu9p0W1Zlxi0nz8AFs" + // } + let test_pvt = base64::decode_config( + "jv_VrhPomm6_WOzb74xF4eMI0hu9p0W1Zlxi0nz8AFs", + base64::URL_SAFE, + ) + .unwrap(); + let sk = K256SigningKey::from_bytes(&test_pvt).expect("Error creating signing key"); + let vk = sk.public_key(); + let jwk = vk.to_jwk().expect("Error converting public key to JWK"); + assert_eq!(jwk["kty"], "EC"); + assert_eq!(jwk["crv"], "secp256k1"); + assert_eq!(jwk["x"], "Hr99bGFN0HWT3ZgNNAXvmz-6Wk1HIxwsyFqUZH8PBFc"); + assert_eq!(jwk["y"], "bonc3DRZe51NzuWetY336VmTYdUFvPK7DivSPHeu_CA"); + } + + #[test] + fn sign_verify_expected() { + let test_msg = b"This is a dummy message for use with tests"; + let test_sig = hex::decode("a2a3affbe18cda8c5a7b6375f05b304c2303ab8beb21428709a43a519f8f946f6ffa7966afdb337e9b1f70bb575282e71d4fe5bbe6bfa97b229d6bd7e97df1e5").unwrap(); + let test_pvt = base64::decode_config( + "jv_VrhPomm6_WOzb74xF4eMI0hu9p0W1Zlxi0nz8AFs", + base64::URL_SAFE_NO_PAD, + ) + .unwrap(); + let kp = K256SigningKey::from_bytes(&&test_pvt).unwrap(); + let sig = kp.sign(&test_msg[..]); + assert_eq!(sig, test_sig.as_slice()); + assert_eq!(kp.public_key().verify(&test_msg[..], sig), true); + assert_eq!(kp.public_key().verify(b"Not the message", sig), false); + assert_eq!(kp.public_key().verify(&test_msg[..], [0u8; 64]), false); + } + + #[test] + fn key_exchange_random() { + let kp1 = K256ExchPrivateKey::generate().unwrap(); + let kp2 = K256ExchPrivateKey::generate().unwrap(); + assert_ne!(kp1.to_bytes(), kp2.to_bytes()); + + let xch1 = kp1.key_exchange_with(&kp2.public_key()); + let xch2 = kp2.key_exchange_with(&kp1.public_key()); + assert_eq!(xch1, xch2); + + // test round trip + let xch3 = K256ExchPrivateKey::from_bytes(&kp1.to_bytes()) + .unwrap() + .key_exchange_with(&kp2.public_key()); + assert_eq!(xch3, xch1); + } +} diff --git a/askar-keys/src/alg/mod.rs b/askar-keys/src/alg/mod.rs new file mode 100644 index 00000000..867b2210 --- /dev/null +++ b/askar-keys/src/alg/mod.rs @@ -0,0 +1,8 @@ +// pub mod bls; + +// pub mod ed25519; +// pub mod x25519; + +// pub mod k256; + +// pub mod p256; diff --git a/src/keys/alg/p256.rs b/askar-keys/src/alg/p256.rs similarity index 70% rename from src/keys/alg/p256.rs rename to askar-keys/src/alg/p256.rs index bd6b3dda..5bc6da87 100644 --- a/src/keys/alg/p256.rs +++ b/askar-keys/src/alg/p256.rs @@ -1,24 +1,28 @@ -use std::convert::TryFrom; +use std::convert::TryInto; use p256::{ ecdsa::{ signature::{Signer, Verifier}, Signature, SigningKey, VerifyingKey, }, - elliptic_curve::{ecdh::diffie_hellman, sec1::FromEncodedPoint}, + elliptic_curve::{ + ecdh::diffie_hellman, + sec1::{Coordinates, FromEncodedPoint}, + }, EncodedPoint, PublicKey, SecretKey, }; use rand::rngs::OsRng; use serde_json::json; -use super::edwards::{decode_signature, encode_signature}; use crate::{ + buffer::{SecretBytes, WriteBuffer}, + // any::{AnyPrivateKey, AnyPublicKey}, + caps::{EcCurves, KeyAlg, KeyCapSign, KeyCapVerify, SignatureType}, error::Error, - keys::any::{AnyPrivateKey, AnyPublicKey}, - keys::caps::{EcCurves, KeyAlg, KeyCapSign, KeyCapVerify, SignatureFormat, SignatureType}, - types::SecretBytes, }; +pub const ES256_SIGNATURE_LENGTH: usize = 64; + #[derive(Clone, Debug)] pub struct P256SigningKey(SecretKey); @@ -40,16 +44,16 @@ impl P256SigningKey { P256VerifyingKey(self.0.public_key()) } - pub fn sign(&self, message: &[u8]) -> [u8; 64] { + pub fn sign(&self, message: &[u8]) -> [u8; ES256_SIGNATURE_LENGTH] { let skey = SigningKey::from(self.0.clone()); let sig = skey.sign(message); - let mut sigb = [0u8; 64]; + let mut sigb = [0u8; ES256_SIGNATURE_LENGTH]; sigb[0..32].copy_from_slice(&sig.r().to_bytes()); sigb[32..].copy_from_slice(&sig.s().to_bytes()); sigb } - pub fn verify(&self, message: &[u8], signature: [u8; 64]) -> bool { + pub fn verify(&self, message: &[u8], signature: &[u8; ES256_SIGNATURE_LENGTH]) -> bool { let mut r = [0u8; 32]; let mut s = [0u8; 32]; r.copy_from_slice(&signature[..32]); @@ -64,16 +68,17 @@ impl P256SigningKey { } impl KeyCapSign for P256SigningKey { - fn key_sign( + fn key_sign( &self, data: &[u8], sig_type: Option, - sig_format: Option, - ) -> Result, Error> { + out: &mut B, + ) -> Result { match sig_type { None | Some(SignatureType::ES256) => { let sig = self.sign(data); - encode_signature(&sig, sig_format) + out.extend_from_slice(&sig[..]); + Ok(ES256_SIGNATURE_LENGTH) } #[allow(unreachable_patterns)] _ => Err(err_msg!(Unsupported, "Unsupported signature type")), @@ -84,16 +89,17 @@ impl KeyCapSign for P256SigningKey { impl KeyCapVerify for P256SigningKey { fn key_verify( &self, - data: &[u8], + message: &[u8], signature: &[u8], sig_type: Option, - sig_format: Option, ) -> Result { match sig_type { None | Some(SignatureType::ES256) => { - let mut sig = [0u8; 64]; - decode_signature(signature, &mut sig, sig_format)?; - Ok(self.verify(data, sig)) + if let Ok(sig) = TryInto::<&[u8; ES256_SIGNATURE_LENGTH]>::try_into(signature) { + Ok(self.verify(message, sig)) + } else { + Ok(false) + } } #[allow(unreachable_patterns)] _ => Err(err_msg!(Unsupported, "Unsupported signature type")), @@ -101,17 +107,17 @@ impl KeyCapVerify for P256SigningKey { } } -impl TryFrom<&AnyPrivateKey> for P256SigningKey { - type Error = Error; +// impl TryFrom<&AnyPrivateKey> for P256SigningKey { +// type Error = Error; - fn try_from(value: &AnyPrivateKey) -> Result { - if value.alg == KeyAlg::Ecdsa(EcCurves::Secp256r1) { - Self::from_bytes(value.data.as_ref()) - } else { - Err(err_msg!(Unsupported, "Expected p-256 key type")) - } - } -} +// fn try_from(value: &AnyPrivateKey) -> Result { +// if value.alg == KeyAlg::Ecdsa(EcCurves::Secp256r1) { +// Self::from_bytes(value.data.as_ref()) +// } else { +// Err(err_msg!(Unsupported, "Expected p-256 key type")) +// } +// } +// } #[derive(Clone, Debug, PartialEq, Eq)] pub struct P256VerifyingKey(PublicKey); @@ -157,12 +163,15 @@ impl P256VerifyingKey { } pub fn to_jwk(&self) -> Result { - let encp = EncodedPoint::from(&self.0); - if encp.is_identity() { - return Err(err_msg!("Cannot convert identity point to JWK")); - } - let x = base64::encode_config(encp.x().unwrap(), base64::URL_SAFE_NO_PAD); - let y = base64::encode_config(encp.y().unwrap(), base64::URL_SAFE_NO_PAD); + let encp = EncodedPoint::encode(self.0, false); + let (x, y) = match encp.coordinates() { + Coordinates::Identity => return Err(err_msg!("Cannot convert identity point to JWK")), + Coordinates::Uncompressed { x, y } => ( + base64::encode_config(x, base64::URL_SAFE_NO_PAD), + base64::encode_config(y, base64::URL_SAFE_NO_PAD), + ), + Coordinates::Compressed { .. } => unreachable!(), + }; Ok(json!({ "kty": "EC", "crv": "P-256", @@ -172,7 +181,7 @@ impl P256VerifyingKey { })) } - pub fn verify(&self, message: &[u8], signature: [u8; 64]) -> bool { + pub fn verify(&self, message: &[u8], signature: &[u8; 64]) -> bool { let mut r = [0u8; 32]; let mut s = [0u8; 32]; r.copy_from_slice(&signature[..32]); @@ -192,13 +201,14 @@ impl KeyCapVerify for P256VerifyingKey { data: &[u8], signature: &[u8], sig_type: Option, - sig_format: Option, ) -> Result { match sig_type { None | Some(SignatureType::ES256) => { - let mut sig = [0u8; 64]; - decode_signature(signature, &mut sig, sig_format)?; - Ok(self.verify(data, sig)) + if let Ok(sig) = TryInto::<&[u8; ES256_SIGNATURE_LENGTH]>::try_into(signature) { + Ok(self.verify(data, sig)) + } else { + Ok(false) + } } #[allow(unreachable_patterns)] _ => Err(err_msg!(Unsupported, "Unsupported signature type")), @@ -206,17 +216,17 @@ impl KeyCapVerify for P256VerifyingKey { } } -impl TryFrom<&AnyPublicKey> for P256VerifyingKey { - type Error = Error; +// impl TryFrom<&AnyPublicKey> for P256VerifyingKey { +// type Error = Error; - fn try_from(value: &AnyPublicKey) -> Result { - if value.alg == KeyAlg::Ecdsa(EcCurves::Secp256r1) { - Self::from_bytes(&value.data) - } else { - Err(err_msg!(Unsupported, "Expected p-256 key type")) - } - } -} +// fn try_from(value: &AnyPublicKey) -> Result { +// if value.alg == KeyAlg::Ecdsa(EcCurves::Secp256r1) { +// Self::from_bytes(&value.data) +// } else { +// Err(err_msg!(Unsupported, "Expected p-256 key type")) +// } +// } +// } #[derive(Clone, Debug)] pub struct P256ExchPrivateKey(SecretKey); @@ -245,17 +255,17 @@ impl P256ExchPrivateKey { } } -impl TryFrom<&AnyPrivateKey> for P256ExchPrivateKey { - type Error = Error; +// impl TryFrom<&AnyPrivateKey> for P256ExchPrivateKey { +// type Error = Error; - fn try_from(value: &AnyPrivateKey) -> Result { - if value.alg == KeyAlg::Ecdh(EcCurves::Secp256r1) { - Self::from_bytes(value.data.as_ref()) - } else { - Err(err_msg!(Unsupported, "Expected p-256 key type")) - } - } -} +// fn try_from(value: &AnyPrivateKey) -> Result { +// if value.alg == KeyAlg::Ecdh(EcCurves::Secp256r1) { +// Self::from_bytes(value.data.as_ref()) +// } else { +// Err(err_msg!(Unsupported, "Expected p-256 key type")) +// } +// } +// } #[derive(Clone, Debug, PartialEq, Eq)] pub struct P256ExchPublicKey(PublicKey); @@ -301,12 +311,15 @@ impl P256ExchPublicKey { } pub fn to_jwk(&self) -> Result { - let encp = EncodedPoint::from(&self.0); - if encp.is_identity() { - return Err(err_msg!("Cannot convert identity point to JWK")); - } - let x = base64::encode_config(encp.x().unwrap(), base64::URL_SAFE_NO_PAD); - let y = base64::encode_config(encp.y().unwrap(), base64::URL_SAFE_NO_PAD); + let encp = EncodedPoint::encode(self.0, false); + let (x, y) = match encp.coordinates() { + Coordinates::Identity => return Err(err_msg!("Cannot convert identity point to JWK")), + Coordinates::Uncompressed { x, y } => ( + base64::encode_config(x, base64::URL_SAFE_NO_PAD), + base64::encode_config(y, base64::URL_SAFE_NO_PAD), + ), + Coordinates::Compressed { .. } => unreachable!(), + }; Ok(json!({ "kty": "EC", "crv": "P-256", @@ -317,17 +330,17 @@ impl P256ExchPublicKey { } } -impl TryFrom<&AnyPublicKey> for P256ExchPublicKey { - type Error = Error; +// impl TryFrom<&AnyPublicKey> for P256ExchPublicKey { +// type Error = Error; - fn try_from(value: &AnyPublicKey) -> Result { - if value.alg == KeyAlg::Ecdh(EcCurves::Secp256r1) { - Self::from_bytes(&value.data) - } else { - Err(err_msg!(Unsupported, "Expected p-256 key type")) - } - } -} +// fn try_from(value: &AnyPublicKey) -> Result { +// if value.alg == KeyAlg::Ecdh(EcCurves::Secp256r1) { +// Self::from_bytes(&value.data) +// } else { +// Err(err_msg!(Unsupported, "Expected p-256 key type")) +// } +// } +// } #[cfg(test)] mod tests { @@ -374,9 +387,9 @@ mod tests { let kp = P256SigningKey::from_bytes(&&test_pvt).unwrap(); let sig = kp.sign(&test_msg[..]); assert_eq!(sig, test_sig.as_slice()); - assert_eq!(kp.public_key().verify(&test_msg[..], sig), true); - assert_eq!(kp.public_key().verify(b"Not the message", sig), false); - assert_eq!(kp.public_key().verify(&test_msg[..], [0u8; 64]), false); + assert_eq!(kp.public_key().verify(&test_msg[..], &sig), true); + assert_eq!(kp.public_key().verify(b"Not the message", &sig), false); + assert_eq!(kp.public_key().verify(&test_msg[..], &[0u8; 64]), false); } #[test] diff --git a/askar-keys/src/alg/x25519.rs b/askar-keys/src/alg/x25519.rs new file mode 100644 index 00000000..1296f277 --- /dev/null +++ b/askar-keys/src/alg/x25519.rs @@ -0,0 +1,233 @@ +use alloc::boxed::Box; +use core::convert::TryInto; + +use rand::rngs::OsRng; +use x25519_dalek::{PublicKey, StaticSecret as SecretKey}; +use zeroize::Zeroize; + +use crate::{ + buffer::{SecretBytes, WriteBuffer}, + error::Error, + jwk::{JwkEncoder, KeyToJwk, KeyToJwkPrivate}, +}; + +pub const PUBLIC_KEY_LENGTH: usize = 32; +pub const SECRET_KEY_LENGTH: usize = 32; + +#[derive(Clone)] +// FIXME implement zeroize +pub struct X25519KeyPair(Box); + +impl X25519KeyPair { + #[inline(always)] + pub(crate) fn new(sk: Option, pk: PublicKey) -> Self { + Self(Box::new(Keypair { sk, pk })) + } + + pub fn generate() -> Result { + let sk = SecretKey::new(OsRng); + let pk = PublicKey::from(&sk); + Ok(Self::new(Some(sk), pk)) + } + + pub fn key_exchange_with(&self, other: &Self) -> Option { + match &self.0.sk { + Some(sk) => { + let xk = sk.diffie_hellman(&other.0.pk); + Some(SecretBytes::from(xk.as_bytes().to_vec())) + } + None => None, + } + } + + pub fn from_public_key_bytes(key: &[u8]) -> Result { + if key.len() != PUBLIC_KEY_LENGTH { + return Err(err_msg!("Invalid x25519 key length")); + } + Ok(Self::new( + None, + PublicKey::from(TryInto::<[u8; PUBLIC_KEY_LENGTH]>::try_into(key).unwrap()), + )) + } + + pub fn from_secret_key_bytes(key: &[u8]) -> Result { + if key.len() != SECRET_KEY_LENGTH { + return Err(err_msg!("Invalid x25519 key length")); + } + let sk = SecretKey::from(TryInto::<[u8; SECRET_KEY_LENGTH]>::try_into(key).unwrap()); + let pk = PublicKey::from(&sk); + Ok(Self::new(Some(sk), pk)) + } + + pub fn public_key_bytes(&self) -> [u8; PUBLIC_KEY_LENGTH] { + self.0.pk.to_bytes() + } + + pub fn secret_key_bytes(&self) -> Option { + self.0 + .sk + .as_ref() + .map(|sk| SecretBytes::from_slice(&sk.to_bytes()[..])) + } +} + +impl KeyToJwk for X25519KeyPair { + const KTY: &'static str = "OKP"; + + fn to_jwk_buffer(&self, buffer: &mut JwkEncoder) -> Result<(), Error> { + buffer.add_str("crv", "X25519")?; + buffer.add_as_base58("x", &self.public_key_bytes()[..])?; + buffer.add_str("use", "enc"); + Ok(()) + } +} + +impl KeyToJwkPrivate for X25519KeyPair { + fn to_jwk_buffer_private( + &self, + buffer: &mut JwkEncoder, + ) -> Result<(), Error> { + if let Some(sk) = self.0.sk.as_ref() { + let mut sk = sk.to_bytes(); + buffer.add_str("crv", "X25519")?; + buffer.add_as_base58("x", &self.public_key_bytes()[..])?; + buffer.add_as_base58("d", &sk[..])?; + sk.zeroize(); + Ok(()) + } else { + self.to_jwk_buffer(buffer) + } + } +} + +#[derive(Clone)] +struct Keypair { + sk: Option, + pk: PublicKey, +} + +// impl TryFrom<&AnyPrivateKey> for X25519KeyPair { +// type Error = Error; + +// fn try_from(value: &AnyPrivateKey) -> Result { +// if value.alg == KeyAlg::X25519 { +// Self::from_bytes(value.data.as_ref()) +// } else { +// Err(err_msg!(Unsupported, "Expected x25519 key type")) +// } +// } +// } + +// #[derive(Clone, Debug, PartialEq, Eq)] +// pub struct X25519PublicKey(PublicKey); + +// impl X25519PublicKey { +// pub fn from_str(key: impl AsRef) -> Result { +// let key = key.as_ref(); +// let key = key.strip_suffix(":x25519").unwrap_or(key); +// let mut bval = [0u8; 32]; +// bs58::decode(key) +// .into(&mut bval) +// .map_err(|_| err_msg!("Invalid base58 public key"))?; +// Self::from_bytes(bval) +// } + +// pub fn from_bytes(pk: impl AsRef<[u8]>) -> Result { +// let pk: [u8; 32] = pk +// .as_ref() +// .try_into() +// .map_err(|_| err_msg!("Invalid public key bytes"))?; +// Ok(Self(XPublicKey::from(pk))) +// } + +// pub fn to_base58(&self) -> String { +// bs58::encode(self.to_bytes()).into_string() +// } + +// pub fn to_jwk(&self) -> Result { +// let x = base64::encode_config(self.to_bytes(), base64::URL_SAFE_NO_PAD); +// Ok(json!({ +// "kty": "OKP", +// "crv": "X25519", +// "x": x, +// "key_ops": ["deriveKey"] +// })) +// } + +// pub fn to_string(&self) -> String { +// let mut sval = String::with_capacity(64); +// bs58::encode(self.to_bytes()).into(&mut sval).unwrap(); +// sval.push_str(":x25519"); +// sval +// } + +// pub fn to_bytes(&self) -> [u8; 32] { +// self.0.to_bytes() +// } +// } + +// impl TryFrom<&AnyPublicKey> for X25519PublicKey { +// type Error = Error; + +// fn try_from(value: &AnyPublicKey) -> Result { +// if value.alg == KeyAlg::X25519 { +// Self::from_bytes(&value.data) +// } else { +// Err(err_msg!(Unsupported, "Expected x25519 key type")) +// } +// } +// } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn jwk_expected() { + // from https://www.connect2id.com/blog/nimbus-jose-jwt-6 + // { + // "kty" : "OKP", + // "crv" : "Ed25519", + // "x" : "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo", + // "d" : "nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A" + // "use" : "sig", + // "kid" : "FdFYFzERwC2uCBB46pZQi4GG85LujR8obt-KWRBICVQ" + // } + let test_pvt_b64 = "nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A"; + let test_pvt = base64::decode_config(test_pvt_b64, base64::URL_SAFE).unwrap(); + let kp = + X25519KeyPair::from_secret_key_bytes(&test_pvt).expect("Error creating signing key"); + let jwk = kp.to_jwk().expect("Error converting public key to JWK"); + let jwk = serde_json::to_value(&jwk).expect("Error parsing JWK output"); + assert_eq!(jwk["kty"], "OKP"); + assert_eq!(jwk["crv"], "X25519"); + assert_eq!(jwk["x"], "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"); + assert_eq!(jwk["d"].is_null(), true); + + let jwk = kp + .to_jwk_private() + .expect("Error converting private key to JWK"); + let jwk = serde_json::to_value(&jwk).expect("Error parsing JWK output"); + assert_eq!(jwk["kty"], "OKP"); + assert_eq!(jwk["crv"], "X25519"); + assert_eq!(jwk["x"], "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"); + assert_eq!(jwk["d"], test_pvt_b64); + } + + #[test] + fn key_exchange_random() { + let kp1 = X25519KeyPair::generate().unwrap(); + let kp2 = X25519KeyPair::generate().unwrap(); + assert_ne!(kp1.to_jwk(), kp2.to_jwk()); + + let xch1 = kp1.key_exchange_with(&kp2); + let xch2 = kp2.key_exchange_with(&kp1); + assert_eq!(xch1, xch2); + + // // test round trip + // let xch3 = X25519KeyPair::from_bytes(&kp1.to_bytes()) + // .unwrap() + // .key_exchange_with(&kp2.public_key()); + // assert_eq!(xch3, xch1); + } +} diff --git a/src/keys/any.rs b/askar-keys/src/any.rs similarity index 54% rename from src/keys/any.rs rename to askar-keys/src/any.rs index ff239f6f..20540a68 100644 --- a/src/keys/any.rs +++ b/askar-keys/src/any.rs @@ -1,40 +1,51 @@ use std::convert::TryFrom; -use super::alg::edwards::{Ed25519KeyPair, Ed25519PublicKey}; +use super::alg::ed25519::{Ed25519KeyPair, Ed25519PublicKey}; +use super::alg::k256::{K256SigningKey, K256VerifyingKey}; use super::alg::p256::{P256SigningKey, P256VerifyingKey}; use super::caps::{ EcCurves, KeyAlg, KeyCapGetPublic, KeyCapSign, KeyCapVerify, KeyCategory, SignatureFormat, SignatureType, }; -use super::store::KeyEntry; +// use super::store::KeyEntry; use crate::error::Error; use crate::types::SecretBytes; #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct AnyPublicKey { - pub alg: KeyAlg, - pub data: Vec, + pub(crate) alg: KeyAlg, + pub(crate) data: Vec, } -impl TryFrom for AnyPublicKey { - type Error = Error; +impl AnyPublicKey { + pub fn key_alg(&self) -> KeyAlg { + self.alg + } - fn try_from(value: KeyEntry) -> Result { - if value.category == KeyCategory::PublicKey { - if let Some(data) = value.params.data { - Ok(AnyPublicKey { - alg: value.params.alg, - data: data.into_vec(), - }) - } else { - Err(err_msg!(Unsupported, "Missing public key raw data")) - } - } else { - Err(err_msg!(Unsupported, "Not a public key entry")) - } + pub fn key_data(&self) -> &[u8] { + self.data.as_ref() } } +// impl TryFrom for AnyPublicKey { +// type Error = Error; + +// fn try_from(value: KeyEntry) -> Result { +// if value.category == KeyCategory::PublicKey { +// if let Some(data) = value.params.data { +// Ok(AnyPublicKey { +// alg: value.params.alg, +// data: data.into_vec(), +// }) +// } else { +// Err(err_msg!(Unsupported, "Missing public key raw data")) +// } +// } else { +// Err(err_msg!(Unsupported, "Not a public key entry")) +// } +// } +// } + impl KeyCapVerify for AnyPublicKey { fn key_verify( &self, @@ -46,6 +57,8 @@ impl KeyCapVerify for AnyPublicKey { match self.alg { KeyAlg::Ed25519 => Ed25519PublicKey::try_from(self) .and_then(|k| k.key_verify(data, signature, sig_type, sig_format)), + KeyAlg::Ecdsa(EcCurves::Secp256k1) => K256VerifyingKey::try_from(self) + .and_then(|k| k.key_verify(data, signature, sig_type, sig_format)), KeyAlg::Ecdsa(EcCurves::Secp256r1) => P256VerifyingKey::try_from(self) .and_then(|k| k.key_verify(data, signature, sig_type, sig_format)), _ => Err(err_msg!( @@ -69,16 +82,14 @@ impl KeyCapGetPublic for AnyPrivateKey { } impl KeyCapSign for AnyPrivateKey { - fn key_sign( - &self, - data: &[u8], - sig_type: Option, - sig_format: Option, - ) -> Result, Error> { + fn key_sign(&self, data: &[u8], sig_type: Option) -> Result, Error> { match self.alg { KeyAlg::Ed25519 => { Ed25519KeyPair::try_from(self).and_then(|k| k.key_sign(data, sig_type, sig_format)) } + KeyAlg::Ecdsa(EcCurves::Secp256k1) => { + K256SigningKey::try_from(self).and_then(|k| k.key_sign(data, sig_type, sig_format)) + } KeyAlg::Ecdsa(EcCurves::Secp256r1) => { P256SigningKey::try_from(self).and_then(|k| k.key_sign(data, sig_type, sig_format)) } @@ -101,6 +112,8 @@ impl KeyCapVerify for AnyPrivateKey { match self.alg { KeyAlg::Ed25519 => Ed25519KeyPair::try_from(self) .and_then(|k| k.key_verify(data, signature, sig_type, sig_format)), + KeyAlg::Ecdsa(EcCurves::Secp256k1) => K256SigningKey::try_from(self) + .and_then(|k| k.key_verify(data, signature, sig_type, sig_format)), KeyAlg::Ecdsa(EcCurves::Secp256r1) => P256SigningKey::try_from(self) .and_then(|k| k.key_verify(data, signature, sig_type, sig_format)), _ => Err(err_msg!( @@ -111,21 +124,21 @@ impl KeyCapVerify for AnyPrivateKey { } } -impl TryFrom for AnyPrivateKey { - type Error = Error; +// impl TryFrom for AnyPrivateKey { +// type Error = Error; - fn try_from(value: KeyEntry) -> Result { - if value.category == KeyCategory::PrivateKey { - if let Some(data) = value.params.data { - Ok(AnyPrivateKey { - alg: value.params.alg, - data, - }) - } else { - Err(err_msg!(Unsupported, "Missing private key raw data")) - } - } else { - Err(err_msg!(Unsupported, "Not a private key entry")) - } - } -} +// fn try_from(value: KeyEntry) -> Result { +// if value.category == KeyCategory::PrivateKey { +// if let Some(data) = value.params.data { +// Ok(AnyPrivateKey { +// alg: value.params.alg, +// data, +// }) +// } else { +// Err(err_msg!(Unsupported, "Missing private key raw data")) +// } +// } else { +// Err(err_msg!(Unsupported, "Not a private key entry")) +// } +// } +// } diff --git a/askar-keys/src/buffer.rs b/askar-keys/src/buffer.rs new file mode 100644 index 00000000..7fe73731 --- /dev/null +++ b/askar-keys/src/buffer.rs @@ -0,0 +1,451 @@ +use alloc::{ + borrow::Cow, + string::{String, ToString}, + vec::Vec, +}; +use core::{ + cmp::Ordering, + fmt::{self, Debug, Formatter}, + marker::PhantomData, + mem::{self, ManuallyDrop}, + ops::{Deref, DerefMut}, +}; + +use aead::{ + generic_array::{ArrayLength, GenericArray}, + Buffer, +}; +use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; +use zeroize::Zeroize; + +use crate::{error::Error, random::random_array}; + +/// A secure key representation for fixed-length keys +#[derive(Clone, Debug, Hash, Zeroize)] +pub struct ArrayKey>(GenericArray); + +impl> ArrayKey { + pub const SIZE: usize = L::USIZE; + + #[inline] + pub fn from_slice>(data: D) -> Self { + Self(GenericArray::clone_from_slice(data.as_ref())) + } + + #[inline] + pub fn extract(self) -> GenericArray { + self.0.clone() + } + + #[inline] + pub fn random() -> Self { + Self(random_array()) + } +} + +impl> Default for ArrayKey { + #[inline] + fn default() -> Self { + Self(GenericArray::default()) + } +} + +impl> From> for ArrayKey { + fn from(key: GenericArray) -> Self { + Self(key) + } +} + +impl> Deref for ArrayKey { + type Target = GenericArray; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl> DerefMut for ArrayKey { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl> PartialEq for ArrayKey { + fn eq(&self, other: &Self) -> bool { + **self == **other + } +} +impl> Eq for ArrayKey {} + +impl> PartialOrd for ArrayKey { + fn partial_cmp(&self, other: &Self) -> Option { + self.0.partial_cmp(&*other) + } +} +impl> Ord for ArrayKey { + fn cmp(&self, other: &Self) -> Ordering { + self.0.cmp(&*other) + } +} + +impl> Serialize for ArrayKey { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(hex::encode(&self.0.as_slice()).as_str()) + } +} + +impl<'a, L: ArrayLength> Deserialize<'a> for ArrayKey { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'a>, + { + deserializer.deserialize_str(KeyVisitor { _pd: PhantomData }) + } +} + +impl> Drop for ArrayKey { + fn drop(&mut self) { + self.zeroize(); + } +} + +struct KeyVisitor> { + _pd: PhantomData, +} + +impl<'a, L: ArrayLength> Visitor<'a> for KeyVisitor { + type Value = ArrayKey; + + fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { + formatter.write_str(stringify!($name)) + } + + fn visit_str(self, value: &str) -> Result, E> + where + E: serde::de::Error, + { + let key = hex::decode(value).map_err(E::custom)?; + Ok(ArrayKey(GenericArray::clone_from_slice(key.as_slice()))) + } +} + +/// A possibly-empty password or key used to derive a store wrap key +#[derive(Clone)] +pub struct PassKey<'a>(Option>); + +impl PassKey<'_> { + /// Create a scoped reference to the passkey + pub fn as_ref(&self) -> PassKey<'_> { + PassKey(Some(Cow::Borrowed(&**self))) + } + + pub(crate) fn is_none(&self) -> bool { + self.0.is_none() + } + + pub(crate) fn into_owned(self) -> PassKey<'static> { + let mut slf = ManuallyDrop::new(self); + let val = slf.0.take(); + PassKey(match val { + None => None, + Some(Cow::Borrowed(s)) => Some(Cow::Owned(s.to_string())), + Some(Cow::Owned(s)) => Some(Cow::Owned(s)), + }) + } +} + +impl Debug for PassKey<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + if cfg!(test) { + f.debug_tuple("PassKey").field(&*self).finish() + } else { + f.debug_tuple("PassKey").field(&"").finish() + } + } +} + +impl Default for PassKey<'_> { + fn default() -> Self { + Self(None) + } +} + +impl Deref for PassKey<'_> { + type Target = str; + + fn deref(&self) -> &str { + match self.0.as_ref() { + None => "", + Some(s) => s.as_ref(), + } + } +} + +impl Drop for PassKey<'_> { + fn drop(&mut self) { + self.zeroize(); + } +} + +impl<'a> From<&'a str> for PassKey<'a> { + fn from(inner: &'a str) -> Self { + Self(Some(Cow::Borrowed(inner))) + } +} + +impl From for PassKey<'_> { + fn from(inner: String) -> Self { + Self(Some(Cow::Owned(inner))) + } +} + +impl<'a> From> for PassKey<'a> { + fn from(inner: Option<&'a str>) -> Self { + Self(inner.map(Cow::Borrowed)) + } +} + +impl<'a, 'b> PartialEq> for PassKey<'a> { + fn eq(&self, other: &PassKey<'b>) -> bool { + &**self == &**other + } +} +impl Eq for PassKey<'_> {} + +impl Zeroize for PassKey<'_> { + fn zeroize(&mut self) { + match self.0.take() { + Some(Cow::Owned(mut s)) => { + s.zeroize(); + } + _ => (), + } + } +} + +/// A heap-allocated, zeroized byte buffer +#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Zeroize)] +pub struct SecretBytes(Vec); + +impl SecretBytes { + pub fn new_with(len: usize, f: impl FnOnce(&mut [u8])) -> Self { + let mut slf = Self::with_capacity(len); + let mut buf = slf.as_buffer(); + buf.resize(len); + f(buf.as_mut()); + slf + } + + pub fn with_capacity(max_len: usize) -> Self { + Self(Vec::with_capacity(max_len)) + } + + pub(crate) fn as_buffer(&mut self) -> SecretBytesMut<'_> { + SecretBytesMut(&mut self.0) + } + + pub fn from_slice(data: &[u8]) -> Self { + let mut v = Vec::with_capacity(data.len()); + v.extend_from_slice(data); + Self(v) + } + + /// Try to convert the buffer value to a string reference + pub fn as_opt_str(&self) -> Option<&str> { + core::str::from_utf8(self.0.as_slice()).ok() + } + + pub(crate) fn into_vec(mut self) -> Vec { + // FIXME zeroize extra capacity? + let mut v = Vec::new(); // note: no heap allocation for empty vec + mem::swap(&mut v, &mut self.0); + mem::forget(self); + v + } +} + +impl Debug for SecretBytes { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + if cfg!(test) { + f.debug_tuple("Secret") + .field(&MaybeStr(self.0.as_slice())) + .finish() + } else { + f.write_str("") + } + } +} + +impl AsRef<[u8]> for SecretBytes { + fn as_ref(&self) -> &[u8] { + self.0.as_slice() + } +} + +impl Deref for SecretBytes { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + self.0.as_slice() + } +} + +impl Drop for SecretBytes { + fn drop(&mut self) { + self.zeroize(); + } +} + +impl From<&[u8]> for SecretBytes { + fn from(inner: &[u8]) -> Self { + Self(inner.to_vec()) + } +} + +impl From<&str> for SecretBytes { + fn from(inner: &str) -> Self { + Self(inner.as_bytes().to_vec()) + } +} + +impl From for SecretBytes { + fn from(inner: String) -> Self { + Self(inner.into_bytes()) + } +} + +impl From> for SecretBytes { + fn from(inner: Vec) -> Self { + Self(inner) + } +} + +impl PartialEq<&[u8]> for SecretBytes { + fn eq(&self, other: &&[u8]) -> bool { + self.0.eq(other) + } +} + +impl PartialEq> for SecretBytes { + fn eq(&self, other: &Vec) -> bool { + self.0.eq(other) + } +} + +pub(crate) struct SecretBytesMut<'m>(&'m mut Vec); + +impl SecretBytesMut<'_> { + /// Securely extend the buffer without leaving copies + #[inline] + pub fn extend_from_slice(&mut self, data: &[u8]) { + self.reserve(data.len()); + self.0.extend_from_slice(data); + } + + fn truncate(&mut self, len: usize) { + self.0.truncate(len); + } + + /// Obtain a large-enough SecretBytes without creating unsafe copies of + /// the contained data + pub fn reserve(&mut self, extra: usize) { + let len = self.0.len(); + if extra + len > self.0.capacity() { + // allocate a new buffer and copy the secure data over + let mut buf = Vec::with_capacity(extra + len); + buf.extend_from_slice(&self.0[..]); + mem::swap(&mut buf, &mut self.0); + buf.zeroize() + } + } + + pub fn resize(&mut self, new_len: usize) { + let len = self.len(); + if new_len > len { + self.reserve(new_len - len); + self.0.resize(new_len, 0u8); + } else { + self.0.truncate(new_len); + } + } +} + +impl Buffer for SecretBytesMut<'_> { + fn extend_from_slice(&mut self, other: &[u8]) -> Result<(), aead::Error> { + self.extend_from_slice(other); + Ok(()) + } + + fn truncate(&mut self, len: usize) { + self.truncate(len); + } +} + +impl AsRef<[u8]> for SecretBytesMut<'_> { + fn as_ref(&self) -> &[u8] { + self.0.as_slice() + } +} + +impl AsMut<[u8]> for SecretBytesMut<'_> { + fn as_mut(&mut self) -> &mut [u8] { + self.0.as_mut_slice() + } +} + +/// A utility type for debug printing of byte strings +struct MaybeStr<'a>(&'a [u8]); + +impl Debug for MaybeStr<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + if let Ok(sval) = core::str::from_utf8(self.0) { + write!(f, "{:?}", sval) + } else { + write!(f, "_\"{}\"", hex::encode(self.0)) + } + } +} + +pub trait WriteBuffer { + fn extend_from_slice(&mut self, data: &[u8]) -> Result<(), Error> { + if let Some(ext) = self.extend_buffer(data.len()) { + ext.copy_from_slice(data); + Ok(()) + } else { + Err(err_msg!(Unexpected, "JWK buffer too small")) + } + } + + fn extend_buffer(&mut self, max_len: usize) -> Option<&mut [u8]>; + + fn truncate_by(&mut self, len: usize); +} + +impl WriteBuffer for Vec { + fn extend_buffer(&mut self, max_len: usize) -> Option<&mut [u8]> { + let len = self.len(); + self.resize(len + max_len, 0u8); + Some(&mut self[len..(len + max_len)]) + } + + fn truncate_by(&mut self, len: usize) { + self.truncate(self.len().saturating_sub(len)); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_write_vec() { + let mut v = Vec::new(); + let mut b = &mut v as &mut dyn WriteBuffer; + b.extend_from_slice(b"hello").unwrap(); + b.truncate_by(3); + b.extend_from_slice(b"y").unwrap(); + assert_eq!(&v[..], b"hey"); + } +} diff --git a/src/keys/caps.rs b/askar-keys/src/caps.rs similarity index 59% rename from src/keys/caps.rs rename to askar-keys/src/caps.rs index 913f0eb0..aeccdcbc 100644 --- a/src/keys/caps.rs +++ b/askar-keys/src/caps.rs @@ -1,14 +1,40 @@ -use std::convert::Infallible; -use std::fmt::{self, Display, Formatter}; -use std::str::FromStr; +use alloc::string::{String, ToString}; +use core::{ + fmt::{self, Display, Formatter}, + str::FromStr, +}; use zeroize::Zeroize; -use super::any::AnyPublicKey; -use crate::error::Error; +use crate::{buffer::WriteBuffer, error::Error}; + +// #[cfg(feature = "any")] +// use crate::any::AnyKey; + +// pub trait KeyCapGetPublic { +// fn key_get_public(&self, alg: Option) -> Result; +// } + +pub trait KeyCapSign { + fn key_sign( + &self, + data: &[u8], + sig_type: Option, + out: &mut B, + ) -> Result; +} + +pub trait KeyCapVerify { + fn key_verify( + &self, + data: &[u8], + signature: &[u8], + sig_type: Option, + ) -> Result; +} /// Supported key algorithms -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Zeroize)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Zeroize)] pub enum KeyAlg { /// Curve25519 signing key Ed25519, @@ -18,10 +44,8 @@ pub enum KeyAlg { Ecdh(EcCurves), /// Elliptic Curve signing key Ecdsa(EcCurves), - /// BLS12-1381 signing key in group G1 or G2 + // /// BLS12-1381 signing key in group G1 or G2 // BLS12_1381(BlsGroup), - /// Unrecognized algorithm - Other(String), } serde_as_str_impl!(KeyAlg); @@ -30,11 +54,12 @@ impl KeyAlg { /// Get a reference to a string representing the `KeyAlg` pub fn as_str(&self) -> &str { match self { - Self::Ed25519 => "ed25519", - Self::X25519 => "x25519", - Self::Ecdh(EcCurves::Secp256r1) => "p256/ecdh", - Self::Ecdsa(EcCurves::Secp256r1) => "p256/ecdsa", - Self::Other(other) => other.as_str(), + Self::Ed25519 => "Ed25519", + Self::X25519 => "X25519", + Self::Ecdh(EcCurves::Secp256r1) => "P-256/ecdh", + Self::Ecdsa(EcCurves::Secp256r1) => "P-256/ecdsa", + Self::Ecdh(EcCurves::Secp256k1) => "secp256k1/ecdh", + Self::Ecdsa(EcCurves::Secp256k1) => "secp256k1/ecdsa", } } } @@ -46,13 +71,13 @@ impl AsRef for KeyAlg { } impl FromStr for KeyAlg { - type Err = Infallible; + type Err = Error; fn from_str(s: &str) -> Result { Ok(match s { - "ed25519" => Self::Ed25519, - "x25519" => Self::X25519, - other => Self::Other(other.to_owned()), + "Ed25519" => Self::Ed25519, + "X25519" => Self::X25519, + _ => return Err(err_msg!("Unknown key algorithm: {}", s)), }) } } @@ -64,14 +89,12 @@ impl Display for KeyAlg { } /// Categories of keys supported by the default KMS -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Zeroize)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Zeroize)] pub enum KeyCategory { /// A private key or keypair PrivateKey, /// A public key PublicKey, - /// An unrecognized key category - Other(String), } impl KeyCategory { @@ -80,16 +103,12 @@ impl KeyCategory { match self { Self::PrivateKey => "private", Self::PublicKey => "public", - Self::Other(other) => other.as_str(), } } /// Convert the `KeyCategory` into an owned string - pub fn into_string(self) -> String { - match self { - Self::Other(other) => other, - _ => self.as_str().to_owned(), - } + pub fn to_string(&self) -> String { + self.as_str().to_string() } } @@ -102,13 +121,13 @@ impl AsRef for KeyCategory { } impl FromStr for KeyCategory { - type Err = Infallible; + type Err = Error; fn from_str(s: &str) -> Result { Ok(match s { "private" => Self::PrivateKey, "public" => Self::PublicKey, - other => Self::Other(other.to_owned()), + _ => return Err(err_msg!("Unknown key category: {}", s)), }) } } @@ -119,69 +138,56 @@ impl Display for KeyCategory { } } -pub trait KeyCapGetPublic { - fn key_get_public(&self, alg: Option) -> Result; -} - -pub trait KeyCapSign { - fn key_sign( - &self, - data: &[u8], - sig_type: Option, - sig_format: Option, - ) -> Result, Error>; -} - -pub trait KeyCapVerify { - fn key_verify( - &self, - data: &[u8], - signature: &[u8], - sig_type: Option, - sig_format: Option, - ) -> Result; -} - #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum SignatureFormat { /// Base58-encoded binary signature Base58, + /// Base64-encoded binary signature + Base64, + /// Base64-URL-encoded binary signature + Base64_URL, + /// Hex-encoded binary signature + Hex, /// Raw binary signature (method dependent) Raw, } #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum SignatureType { - // Bls12_1381(BlsGroup), + Bls12_1381(BlsGroup), /// Standard signature output for ed25519 EdDSA, // Elliptic curve DSA using P-256 and SHA-256 ES256, + // Elliptic curve DSA using K-256 and SHA-256 + ES256K, } impl SignatureType { pub const fn signature_size(&self) -> usize { match self { - // Self::Bls12_1381(BlsGroup::G1) => 48, - // Self::Bls12_1381(BlsGroup::G2) => 96, + Self::Bls12_1381(BlsGroup::G1) => 48, + Self::Bls12_1381(BlsGroup::G2) => 96, Self::EdDSA => 64, Self::ES256 => 64, + Self::ES256K => 64, } } } -// pub enum BlsGroup { -// /// A key or signature represented by an element from the G1 group -// G1, -// /// A key or signature represented by an element from the G2 group -// G2, -// } +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Zeroize)] +pub enum BlsGroup { + /// A key or signature represented by an element from the BLS12-381 G1 group + G1, + /// A key or signature represented by an element from the BLS12-381 G2 group + G2, +} /// Supported curves for ECC operations #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Zeroize)] pub enum EcCurves { /// NIST P-256 curve Secp256r1, - // /// Koblitz 256 curve - // Secp256k1, + /// Koblitz 256 curve + Secp256k1, } diff --git a/src/keys/encrypt.rs b/askar-keys/src/encrypt.rs similarity index 89% rename from src/keys/encrypt.rs rename to askar-keys/src/encrypt.rs index 3ba91528..fb842157 100644 --- a/src/keys/encrypt.rs +++ b/askar-keys/src/encrypt.rs @@ -1,9 +1,10 @@ -use std::fmt::Debug; +use alloc::vec::Vec; +use core::fmt::Debug; use serde::{Deserialize, Serialize}; -use crate::error::Result; -use crate::types::SecretBytes; +use crate::buffer::SecretBytes; +use crate::error::Error; pub trait SymEncryptKey: Clone + Debug + Eq + Sized + Serialize + for<'de> Deserialize<'de> @@ -14,7 +15,7 @@ pub trait SymEncryptKey: fn from_slice(bytes: &[u8]) -> Self; - fn from_seed(seed: &[u8]) -> Result; + fn from_seed(seed: &[u8]) -> Result; fn random_key() -> Self; } @@ -37,7 +38,7 @@ pub trait SymEncrypt: Debug { fn prepare_input(input: &[u8]) -> SecretBytes; /// Create a predictable nonce for an input, to allow searching - fn hashed_nonce(input: &SecretBytes, key: &Self::HashKey) -> Result; + fn hashed_nonce(input: &SecretBytes, key: &Self::HashKey) -> Result; /// Encrypt a secret value and optional random nonce, producing a Vec containing the /// nonce, ciphertext and tag @@ -45,19 +46,22 @@ pub trait SymEncrypt: Debug { input: SecretBytes, enc_key: &Self::Key, nonce: Option, - ) -> Result>; + ) -> Result, Error>; /// Get the expected size of an input value after encryption fn encrypted_size(input_size: usize) -> usize; /// Decrypt a combined encrypted value - fn decrypt(enc: Vec, enc_key: &Self::Key) -> Result; + fn decrypt(enc: Vec, enc_key: &Self::Key) -> Result; } pub(crate) mod aead { - use std::fmt::{self, Debug, Formatter}; - use std::marker::PhantomData; - use std::ptr; + use alloc::vec::Vec; + use core::{ + fmt::{self, Debug, Formatter}, + marker::PhantomData, + ptr, + }; use chacha20poly1305::{ aead::{ @@ -72,9 +76,12 @@ pub(crate) mod aead { use hmac::{Hmac, Mac, NewMac}; use sha2::Sha256; - use super::{Result, SecretBytes, SymEncrypt, SymEncryptHashKey, SymEncryptKey}; - use crate::keys::types::ArrayKey; - use crate::random::random_deterministic; + use super::{SymEncrypt, SymEncryptHashKey, SymEncryptKey}; + use crate::{ + buffer::{ArrayKey, SecretBytes}, + error::Error, + random::random_deterministic, + }; pub type ChaChaEncrypt = AeadEncrypt; @@ -91,7 +98,7 @@ pub(crate) mod aead { ArrayKey::from_slice(bytes) } - fn from_seed(seed: &[u8]) -> Result { + fn from_seed(seed: &[u8]) -> Result { if seed.len() != SEED_LENGTH { return Err(err_msg!(Encryption, "Invalid length for seed")); } @@ -145,7 +152,7 @@ pub(crate) mod aead { SecretBytes::from(buf) } - fn hashed_nonce(input: &SecretBytes, key: &Self::HashKey) -> Result { + fn hashed_nonce(input: &SecretBytes, key: &Self::HashKey) -> Result { let mut nonce_hmac = Hmac::::new_varkey(&**key).map_err(|e| err_msg!(Encryption, "{}", e))?; nonce_hmac.update(&*input); @@ -159,12 +166,12 @@ pub(crate) mod aead { mut input: SecretBytes, enc_key: &Self::Key, nonce: Option, - ) -> Result> { + ) -> Result, Error> { let nonce = nonce.unwrap_or_else(|| Self::Nonce::random()); let chacha = E::new(&enc_key); let mut buf = input.as_buffer(); - // should be a no-op if prepare_input was used - buf.reserve_extra(Self::NONCE_SIZE + Self::TAG_SIZE); + // should be trivial if prepare_input was used + buf.reserve(Self::NONCE_SIZE + Self::TAG_SIZE); // replace the input data with the ciphertext and tag chacha .encrypt_in_place(&*nonce, &[], &mut buf) @@ -182,7 +189,7 @@ pub(crate) mod aead { Self::NONCE_SIZE + Self::TAG_SIZE + input_size } - fn decrypt(mut enc: Vec, enc_key: &Self::Key) -> Result { + fn decrypt(mut enc: Vec, enc_key: &Self::Key) -> Result { if enc.len() < Self::NONCE_SIZE + Self::TAG_SIZE { return Err(err_msg!( Encryption, diff --git a/askar-keys/src/error.rs b/askar-keys/src/error.rs new file mode 100644 index 00000000..80ca30a4 --- /dev/null +++ b/askar-keys/src/error.rs @@ -0,0 +1,150 @@ +use alloc::string::String; +use core::fmt::{self, Display, Formatter}; + +#[cfg(feature = "std")] +use std::error::Error as StdError; + +/// The possible kinds of error produced by the crate +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ErrorKind { + /// An encryption or decryption operation failed + Encryption, + + /// The input parameters to the method were incorrect + Input, + + /// An unexpected error occurred + Unexpected, + + /// An unsupported operation was requested + Unsupported, +} + +impl ErrorKind { + /// Convert the error kind to a string reference + pub fn as_str(&self) -> &'static str { + match self { + Self::Encryption => "Encryption error", + Self::Input => "Input error", + Self::Unexpected => "Unexpected error", + Self::Unsupported => "Unsupported", + } + } +} + +impl Display for ErrorKind { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +/// The standard crate error type +#[derive(Debug)] +pub struct Error { + pub(crate) kind: ErrorKind, + #[cfg(feature = "std")] + pub(crate) cause: Option>, + pub(crate) message: Option, +} + +impl Error { + pub(crate) fn from_msg>(kind: ErrorKind, msg: T) -> Self { + Self { + kind, + #[cfg(feature = "std")] + cause: None, + message: Some(msg.into()), + } + } + + #[allow(dead_code)] + pub(crate) fn from_opt_msg>(kind: ErrorKind, msg: Option) -> Self { + Self { + kind, + #[cfg(feature = "std")] + cause: None, + message: msg.map(Into::into), + } + } + + /// Accessor for the error kind + pub fn kind(&self) -> ErrorKind { + self.kind + } + + #[cfg(feature = "std")] + pub(crate) fn with_cause>>(mut self, err: T) -> Self { + self.cause = Some(err.into()); + self + } +} + +impl Display for Error { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + if let Some(msg) = self.message.as_ref() { + f.write_str(msg)?; + } else { + f.write_str(self.kind.as_str())?; + } + #[cfg(feature = "std")] + if let Some(cause) = self.cause.as_ref() { + write!(f, "\nCaused by: {}", cause)?; + } + Ok(()) + } +} + +#[cfg(feature = "std")] +impl StdError for Error { + fn source(&self) -> Option<&(dyn StdError + 'static)> { + self.cause + .as_ref() + .map(|err| unsafe { std::mem::transmute(&**err) }) + } +} + +impl PartialEq for Error { + fn eq(&self, other: &Self) -> bool { + self.kind == other.kind && self.message == other.message + } +} + +impl From for Error { + fn from(kind: ErrorKind) -> Self { + Self { + kind, + #[cfg(feature = "std")] + cause: None, + message: None, + } + } +} + +macro_rules! err_msg { + () => { + $crate::error::Error::from($crate::error::ErrorKind::Input) + }; + ($kind:ident) => { + $crate::error::Error::from($crate::error::ErrorKind::$kind) + }; + ($kind:ident, $($args:tt)+) => { + $crate::error::Error::from_msg($crate::error::ErrorKind::$kind, $crate::alloc::format!($($args)+)) + }; + ($($args:tt)+) => { + $crate::error::Error::from_msg($crate::error::ErrorKind::Input, $crate::alloc::format!($($args)+)) + }; +} + +#[cfg(feature = "std")] +macro_rules! err_map { + ($($params:tt)*) => { + |err| err_msg!($($params)*).with_cause(err) + }; +} + +#[cfg(not(feature = "std"))] +macro_rules! err_map { + ($($params:tt)*) => { + |_| err_msg!($($params)*) + }; +} diff --git a/askar-keys/src/jwk/encode.rs b/askar-keys/src/jwk/encode.rs new file mode 100644 index 00000000..e69de29b diff --git a/askar-keys/src/jwk/mod.rs b/askar-keys/src/jwk/mod.rs new file mode 100644 index 00000000..56b624b8 --- /dev/null +++ b/askar-keys/src/jwk/mod.rs @@ -0,0 +1,188 @@ +use alloc::{borrow::Cow, string::String, vec::Vec}; + +use zeroize::Zeroize; + +use crate::{buffer::WriteBuffer, error::Error}; + +mod ops; +pub use self::ops::{KeyOps, KeyOpsSet}; + +mod parse; +pub use self::parse::JwkParts; + +#[derive(Clone, Debug)] +pub enum Jwk<'a> { + Encoded(Cow<'a, str>), + Parts(JwkParts<'a>), +} + +impl Jwk<'_> { + pub fn to_parts(&self) -> Result, Error> { + match self { + Self::Encoded(s) => Ok(Jwk::Parts( + serde_json::from_str(s.as_ref()).map_err(err_map!("Error deserializing JWK"))?, + )), + Self::Parts(p) => Ok(Jwk::Parts(*p)), + } + } +} + +impl Zeroize for Jwk<'_> { + fn zeroize(&mut self) { + match self { + Self::Encoded(Cow::Owned(s)) => s.zeroize(), + Self::Encoded(_) => (), + Self::Parts(..) => (), + } + } +} + +struct JwkBuffer<'s, B>(&'s mut B); + +impl bs58::encode::EncodeTarget for JwkBuffer<'_, B> { + fn encode_with( + &mut self, + max_len: usize, + f: impl for<'a> FnOnce(&'a mut [u8]) -> Result, + ) -> Result { + if let Some(ext) = self.0.extend_buffer(max_len) { + let len = f(ext)?; + if len < max_len { + self.0.truncate_by(max_len - len); + } + Ok(len) + } else { + Err(bs58::encode::Error::BufferTooSmall) + } + } +} + +pub struct JwkEncoder<'b, B: WriteBuffer> { + buffer: &'b mut B, +} + +impl<'b, B: WriteBuffer> JwkEncoder<'b, B> { + pub fn new(buffer: &'b mut B, kty: &str) -> Result { + buffer.extend_from_slice(b"{\"kty\":\"")?; + buffer.extend_from_slice(kty.as_bytes())?; + buffer.extend_from_slice(b"\"")?; + Ok(Self { buffer }) + } + + pub fn add_str(&mut self, key: &str, value: &str) -> Result<(), Error> { + let buffer = &mut *self.buffer; + buffer.extend_from_slice(b",\"")?; + buffer.extend_from_slice(key.as_bytes())?; + buffer.extend_from_slice(b"\":\"")?; + buffer.extend_from_slice(value.as_bytes())?; + buffer.extend_from_slice(b"\"")?; + Ok(()) + } + + pub fn add_as_base58(&mut self, key: &str, value: &[u8]) -> Result<(), Error> { + let buffer = &mut *self.buffer; + buffer.extend_from_slice(b",\"")?; + buffer.extend_from_slice(key.as_bytes())?; + buffer.extend_from_slice(b"\":\"")?; + bs58::encode(value) + .into(JwkBuffer(buffer)) + .map_err(|_| err_msg!("buffer too small"))?; + buffer.extend_from_slice(b"\"")?; + Ok(()) + } + + pub fn add_key_ops(&mut self, ops: impl Into) -> Result<(), Error> { + let buffer = &mut *self.buffer; + buffer.extend_from_slice(b",\"key_ops\":[")?; + for (idx, op) in ops.into().into_iter().enumerate() { + if idx > 0 { + buffer.extend_from_slice(b",\"")?; + } else { + buffer.extend_from_slice(b"\"")?; + } + buffer.extend_from_slice(op.as_str().as_bytes())?; + buffer.extend_from_slice(b"\"")?; + } + buffer.extend_from_slice(b"]")?; + Ok(()) + } + + pub fn finalize(self) -> Result<(), Error> { + self.buffer.extend_from_slice(b"}")?; + Ok(()) + } +} + +pub trait KeyToJwk { + const KTY: &'static str; + + fn to_jwk(&self) -> Result { + let mut v = Vec::with_capacity(128); + let mut buf = JwkEncoder::new(&mut v, Self::KTY)?; + self.to_jwk_buffer(&mut buf)?; + Ok(String::from_utf8(v).unwrap()) + } + + fn to_jwk_buffer(&self, buffer: &mut JwkEncoder) -> Result<(), Error>; +} + +pub trait KeyToJwkPrivate: KeyToJwk { + fn to_jwk_private(&self) -> Result { + let mut v = Vec::with_capacity(128); + let mut buf = JwkEncoder::new(&mut v, Self::KTY)?; + self.to_jwk_buffer_private(&mut buf)?; + Ok(String::from_utf8(v).unwrap()) + } + + fn to_jwk_buffer_private( + &self, + buffer: &mut JwkEncoder, + ) -> Result<(), Error>; +} + +// pub trait JwkBuilder<'s> { +// // key type +// kty: &'a str, +// // curve type +// crv: Option<&'a str>, +// // curve key public y coordinate +// x: Option<&'a str>, +// // curve key public y coordinate +// y: Option<&'a str>, +// // curve key private key bytes +// d: Option<&'a str>, +// // used by symmetric keys like AES +// k: Option<&'a str>, +// } + +// impl<'de> Serialize for JwkParts<'de> { +// fn serialize(&self, serializer: S) -> Result +// where +// S: Serializer, +// { +// let ret = serializer.serialize_map(None).unwrap(); + +// let add_attr = |name: &str, val: &str| { +// ret.serialize_key(name); +// ret.serialize_value(val); +// }; + +// add_attr("kty", self.kty.as_ref()); +// if let Some(attr) = self.crv.as_ref() { +// add_attr("crv", attr.as_ref()); +// if let Some(attr) = self.x.as_ref() { +// add_attr("x", attr.as_ref()); +// } +// if let Some(attr) = self.y.as_ref() { +// add_attr("y", attr.as_ref()); +// } +// if let Some(attr) = self.d.as_ref() { +// add_attr("d", attr.as_ref()); +// } +// } +// if let Some(attr) = self.k.as_ref() { +// add_attr("k", attr.as_ref()); +// } +// ret.end() +// } +// } diff --git a/askar-keys/src/jwk/ops.rs b/askar-keys/src/jwk/ops.rs new file mode 100644 index 00000000..e4af4082 --- /dev/null +++ b/askar-keys/src/jwk/ops.rs @@ -0,0 +1,203 @@ +use core::{ + fmt::{self, Debug, Display, Formatter}, + ops::{BitAnd, BitOr}, +}; + +static OPS: &[KeyOps] = &[ + KeyOps::Encrypt, + KeyOps::Decrypt, + KeyOps::Sign, + KeyOps::Verify, + KeyOps::WrapKey, + KeyOps::UnwrapKey, + KeyOps::DeriveKey, + KeyOps::DeriveBits, +]; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[repr(usize)] +pub enum KeyOps { + Encrypt = 1 << 0, + Decrypt = 1 << 1, + Sign = 1 << 2, + Verify = 1 << 3, + WrapKey = 1 << 4, + UnwrapKey = 1 << 5, + DeriveKey = 1 << 6, + DeriveBits = 1 << 7, +} + +impl Display for KeyOps { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +impl KeyOps { + pub fn as_str(&self) -> &'static str { + match self { + Self::Encrypt => "encrypt", + Self::Decrypt => "decrypt", + Self::Sign => "sign", + Self::Verify => "verify", + Self::WrapKey => "wrapKey", + Self::UnwrapKey => "unwrapKey", + Self::DeriveKey => "deriveKey", + Self::DeriveBits => "deriveBits", + } + } + + pub fn from_str(key: &str) -> Option { + match key { + "sign" => Some(Self::Sign), + "verify" => Some(Self::Verify), + "encrypt" => Some(Self::Encrypt), + "decrypt" => Some(Self::Decrypt), + "wrapKey" => Some(Self::WrapKey), + "unwrapKey" => Some(Self::UnwrapKey), + "deriveKey" => Some(Self::DeriveKey), + "deriveBits" => Some(Self::DeriveBits), + _ => None, + } + } +} + +impl BitOr for KeyOps { + type Output = KeyOpsSet; + + fn bitor(self, rhs: Self) -> Self::Output { + KeyOpsSet { + value: self as usize | rhs as usize, + } + } +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(transparent)] +pub struct KeyOpsSet { + value: usize, +} + +impl KeyOpsSet { + pub const fn new() -> Self { + Self { value: 0 } + } + + pub fn is_empty(&self) -> bool { + self.value == 0 + } +} + +impl BitOr for KeyOpsSet { + type Output = Self; + + fn bitor(self, rhs: Self) -> Self::Output { + KeyOpsSet { + value: self.value | rhs.value, + } + } +} + +impl BitOr for KeyOpsSet { + type Output = KeyOpsSet; + + fn bitor(self, rhs: KeyOps) -> Self::Output { + KeyOpsSet { + value: self.value | rhs as usize, + } + } +} + +impl BitAnd for KeyOpsSet { + type Output = bool; + + fn bitand(self, rhs: KeyOps) -> Self::Output { + self.value & rhs as usize != 0 + } +} + +impl BitAnd for KeyOpsSet { + type Output = bool; + + fn bitand(self, rhs: Self) -> Self::Output { + self.value & rhs.value != 0 + } +} + +impl Debug for KeyOpsSet { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let mut vals = &mut f.debug_set(); + for op in self { + vals = vals.entry(&op.as_str()); + } + vals.finish() + } +} + +impl From for KeyOpsSet { + fn from(op: KeyOps) -> Self { + Self { value: op as usize } + } +} + +impl IntoIterator for &KeyOpsSet { + type IntoIter = KeyOpsIter; + type Item = KeyOps; + + fn into_iter(self) -> Self::IntoIter { + KeyOpsIter { + index: 0, + value: *self, + } + } +} + +pub struct KeyOpsIter { + index: usize, + value: KeyOpsSet, +} + +impl Iterator for KeyOpsIter { + type Item = KeyOps; + + fn next(&mut self) -> Option { + while self.index < OPS.len() { + let op = OPS[self.index]; + self.index += 1; + if self.value & op { + return Some(op); + } + } + None + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn invariants() { + assert_eq!(KeyOpsSet::new().is_empty(), true); + assert_eq!(KeyOpsSet::from(KeyOps::Decrypt).is_empty(), false); + assert_eq!(KeyOpsSet::new(), KeyOpsSet::new()); + assert_ne!(KeyOpsSet::from(KeyOps::Decrypt), KeyOpsSet::new()); + assert_ne!(KeyOps::Decrypt, KeyOps::Encrypt); + assert_ne!( + KeyOpsSet::from(KeyOps::Decrypt), + KeyOpsSet::from(KeyOps::Encrypt) + ); + assert_eq!( + KeyOps::Decrypt | KeyOps::Encrypt, + KeyOps::Encrypt | KeyOps::Decrypt + ); + } + + #[test] + fn debug_format() { + assert_eq!( + format!("{:?}", KeyOps::Decrypt | KeyOps::Encrypt), + "{\"encrypt\", \"decrypt\"}" + ); + } +} diff --git a/askar-keys/src/jwk/parse.rs b/askar-keys/src/jwk/parse.rs new file mode 100644 index 00000000..516442e0 --- /dev/null +++ b/askar-keys/src/jwk/parse.rs @@ -0,0 +1,156 @@ +use core::{fmt, marker::PhantomData}; + +use serde::de::{Deserialize, Deserializer, MapAccess, SeqAccess, Visitor}; + +use super::ops::{KeyOps, KeyOpsSet}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct JwkParts<'a> { + // key type + kty: &'a str, + // key ID + kid: Option<&'a str>, + // curve type + crv: Option<&'a str>, + // curve key public y coordinate + x: Option<&'a str>, + // curve key public y coordinate + y: Option<&'a str>, + // curve key private key bytes + d: Option<&'a str>, + // used by symmetric keys like AES + k: Option<&'a str>, + // recognized key operations + key_ops: Option, +} + +struct JwkMapVisitor<'de>(PhantomData<&'de ()>); + +impl<'de> Visitor<'de> for JwkMapVisitor<'de> { + type Value = JwkParts<'de>; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("an object representing a JWK") + } + + fn visit_map(self, mut access: M) -> Result + where + M: MapAccess<'de>, + { + let mut kty = None; + let mut kid = None; + let mut crv = None; + let mut x = None; + let mut y = None; + let mut d = None; + let mut k = None; + let mut key_ops = None; + + while let Some(key) = access.next_key::<&str>()? { + match key { + "kty" => kty = Some(access.next_value()?), + "kid" => kid = Some(access.next_value()?), + "crv" => crv = Some(access.next_value()?), + "x" => x = Some(access.next_value()?), + "y" => y = Some(access.next_value()?), + "d" => d = Some(access.next_value()?), + "k" => k = Some(access.next_value()?), + "key_ops" => key_ops = Some(access.next_value()?), + _ => (), + } + } + + if let Some(kty) = kty { + Ok(JwkParts { + kty, + kid, + crv, + x, + y, + d, + k, + key_ops, + }) + } else { + Err(serde::de::Error::custom("missing 'kty' property for JWK")) + } + } +} + +impl<'de> Deserialize<'de> for JwkParts<'de> { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_map(JwkMapVisitor(PhantomData)) + } +} + +struct KeyOpsVisitor; + +impl<'de> Visitor<'de> for KeyOpsVisitor { + type Value = KeyOpsSet; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("an array of key operations") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let mut ops = KeyOpsSet::new(); + while let Some(op) = seq.next_element()? { + if let Some(op) = KeyOps::from_str(op) { + if ops & op { + return Err(serde::de::Error::custom(alloc::format!( + "duplicate key operation: {}", + op + ))); + } else { + ops = ops | op; + } + } + } + Ok(ops) + } +} + +impl<'de> Deserialize<'de> for KeyOpsSet { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_seq(KeyOpsVisitor) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_sample_okp() { + let jwk = r#"{ + "kty": "OKP", + "crv": "Ed25519", + "x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo", + "d": "nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A", + "key_ops": ["sign", "verify"], + "kid": "FdFYFzERwC2uCBB46pZQi4GG85LujR8obt-KWRBICVQ" + }"#; + assert_eq!( + serde_json::from_str::(jwk).unwrap(), + JwkParts { + kty: "OKP", + kid: Some("FdFYFzERwC2uCBB46pZQi4GG85LujR8obt-KWRBICVQ"), + crv: Some("Ed25519"), + x: Some("11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"), + y: None, + d: Some("nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A"), + k: None, + key_ops: Some(KeyOps::Sign | KeyOps::Verify) + } + ) + } +} diff --git a/askar-keys/src/kdf/argon2.rs b/askar-keys/src/kdf/argon2.rs new file mode 100644 index 00000000..d0c69e6a --- /dev/null +++ b/askar-keys/src/kdf/argon2.rs @@ -0,0 +1,78 @@ +use zeroize::Zeroize; + +use crate::{buffer::SecretBytes, encrypt::aead::ChaChaEncrypt, error::Error, random::random_vec}; + +pub const LEVEL_INTERACTIVE: &'static str = "13:int"; +pub const LEVEL_MODERATE: &'static str = "13:mod"; + +pub const HASH_SIZE: usize = 32; +pub const SALT_SIZE: usize = 16; + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum Level { + Interactive, + Moderate, +} + +impl Default for Level { + fn default() -> Self { + Self::Moderate + } +} + +impl Level { + pub fn from_str(level: &str) -> Option { + match level { + "int" | LEVEL_INTERACTIVE => Some(Self::Interactive), + "mod" | LEVEL_MODERATE => Some(Self::Moderate), + "" => Some(Self::default()), + _ => None, + } + } + + pub fn as_str(&self) -> &'static str { + match self { + Self::Interactive => LEVEL_INTERACTIVE, + Self::Moderate => LEVEL_MODERATE, + } + } + + // pub fn derive_key(&self, salt: &[u8], password: &str) -> Result> { + // let (mem_cost, time_cost) = match self { + // Self::Interactive => (32768, 4), + // Self::Moderate => (131072, 6), + // }; + // derive_key(password, salt, mem_cost, time_cost) + // } +} + +// fn derive_key( +// password: &str, +// salt: &[u8], +// mem_cost: u32, +// time_cost: u32, +// ) -> Result> { +// if salt.len() < SALT_SIZE { +// return Err(err_msg!(Encryption, "Invalid salt for argon2i hash")); +// } +// let config = argon2::Config { +// variant: argon2::Variant::Argon2i, +// version: argon2::Version::Version13, +// mem_cost, +// time_cost, +// lanes: 1, +// thread_mode: argon2::ThreadMode::Sequential, +// secret: &[], +// ad: &[], +// hash_length: HASH_SIZE as u32, +// }; +// let mut hashed = argon2::hash_raw(password.as_bytes(), &salt[..SALT_SIZE], &config) +// .map_err(|e| err_msg!(Encryption, "Error deriving key: {}", e))?; +// let key = EncKey::::from_slice(&hashed); +// hashed.zeroize(); +// Ok(key) +// } + +pub fn generate_salt() -> SecretBytes { + random_vec(SALT_SIZE) +} diff --git a/askar-keys/src/kdf/mod.rs b/askar-keys/src/kdf/mod.rs new file mode 100644 index 00000000..ab978feb --- /dev/null +++ b/askar-keys/src/kdf/mod.rs @@ -0,0 +1 @@ +pub mod argon2; diff --git a/askar-keys/src/lib.rs b/askar-keys/src/lib.rs new file mode 100644 index 00000000..3bfaa09e --- /dev/null +++ b/askar-keys/src/lib.rs @@ -0,0 +1,48 @@ +#![no_std] +// #![deny(missing_debug_implementations)] +// #![deny(missing_docs)] +// #![deny(unsafe_code)] + +// #[cfg(feature="alloc")] +extern crate alloc; + +#[cfg(test)] +#[macro_use] +extern crate std; + +#[macro_use] +mod error; + +#[macro_use] +mod serde_utils; + +// re-export +pub use aead::generic_array; + +#[cfg(feature = "any")] +pub mod any; +#[cfg(feature = "any")] +pub use self::any::{AnyPrivateKey, AnyPublicKey}; + +pub mod alg; + +mod buffer; +pub use self::buffer::{PassKey, SecretBytes}; + +pub mod caps; +pub use self::caps::{ + KeyAlg, /*KeyCapGetPublic,*/ KeyCapSign, KeyCapVerify, KeyCategory, SignatureFormat, + SignatureType, +}; + +pub mod encrypt; + +pub mod jwk; + +pub mod kdf; + +pub mod pack; + +pub mod random; + +// pub mod wrap; diff --git a/src/didcomm/pack/alg.rs b/askar-keys/src/pack/alg.rs similarity index 97% rename from src/didcomm/pack/alg.rs rename to askar-keys/src/pack/alg.rs index 102cfd7f..83da7be0 100644 --- a/src/didcomm/pack/alg.rs +++ b/askar-keys/src/pack/alg.rs @@ -3,13 +3,17 @@ use chacha20poly1305::{ ChaCha20Poly1305, Key as ChaChaKey, }; +pub use crate::alg::ed25519::Ed25519KeyPair as KeyPair; + use std::string::ToString; use super::nacl_box::*; -use super::types::*; -use crate::error::Error; -use crate::random::{random_array, random_vec}; -use crate::types::SecretBytes; +// use super::types::*; +use crate::{ + buffer::SecretBytes, + error::Error, + random::{random_array, random_vec}, +}; pub const PROTECTED_HEADER_ENC: &'static str = "xchacha20poly1305_ietf"; pub const PROTECTED_HEADER_TYP: &'static str = "JWM/1.0"; @@ -23,7 +27,7 @@ const TAG_SIZE: usize = ::TagSize::USIZE; pub fn pack_message>( message: M, - receiver_list: Vec, + receiver_list: Vec, sender_key: Option, ) -> Result, Error> { // break early and error out if no receivers keys are provided @@ -60,7 +64,7 @@ pub fn pack_message>( format_pack_message(&base64_protected, &ciphertext, &iv, &tag) } -fn prepare_protected_anoncrypt(cek: &[u8], receiver_list: Vec) -> Result { +fn prepare_protected_anoncrypt(cek: &[u8], receiver_list: Vec) -> Result { let mut encrypted_recipients_struct: Vec = Vec::with_capacity(receiver_list.len()); for their_vk in receiver_list { diff --git a/askar-keys/src/pack/mod.rs b/askar-keys/src/pack/mod.rs new file mode 100644 index 00000000..1f266fbc --- /dev/null +++ b/askar-keys/src/pack/mod.rs @@ -0,0 +1,3 @@ +// mod alg; + +// mod nacl_box; diff --git a/src/didcomm/pack/nacl_box.rs b/askar-keys/src/pack/nacl_box.rs similarity index 97% rename from src/didcomm/pack/nacl_box.rs rename to askar-keys/src/pack/nacl_box.rs index 79460e87..70c24fdd 100644 --- a/src/didcomm/pack/nacl_box.rs +++ b/askar-keys/src/pack/nacl_box.rs @@ -6,8 +6,8 @@ use crypto_box::{ aead::{generic_array::typenum::Unsigned, Aead}, }; +use crate::alg::x25519::X25519KeyPair; use crate::error::Error; -use crate::keys::alg::edwards::X25519KeyPair; use crate::random::random_vec; const CBOX_NONCE_SIZE: usize = ::NonceSize::USIZE; @@ -107,7 +107,7 @@ pub fn crypto_box_seal_open( #[cfg(test)] mod tests { use super::*; - use crate::keys::alg::edwards::Ed25519KeyPair; + use crate::alg::ed25519::Ed25519KeyPair; #[test] fn crypto_box_open_expected() { diff --git a/src/random.rs b/askar-keys/src/random.rs similarity index 81% rename from src/random.rs rename to askar-keys/src/random.rs index 7eb292b3..2f5d91e3 100644 --- a/src/random.rs +++ b/askar-keys/src/random.rs @@ -5,6 +5,8 @@ use chacha20::{ }; use rand::{rngs::OsRng, RngCore}; +use crate::buffer::SecretBytes; + pub type SeedSize = ::KeySize; /// Fill a mutable slice with random data using the @@ -24,18 +26,14 @@ pub fn random_array>() -> GenericArray { /// Written to be compatible with randombytes_deterministic in libsodium, /// used to generate a deterministic wallet raw key. -pub fn random_deterministic(seed: &GenericArray, len: usize) -> Vec { +pub fn random_deterministic(seed: &GenericArray, len: usize) -> SecretBytes { let nonce = GenericArray::from_slice(b"LibsodiumDRG"); let mut cipher = ChaCha20::new(seed, &nonce); - let mut data = vec![0; len]; - cipher.apply_keystream(data.as_mut_slice()); - data + SecretBytes::new_with(len, |buf| cipher.apply_keystream(buf)) } /// Create a new `Vec` instance with random data. #[inline(always)] -pub fn random_vec(sz: usize) -> Vec { - let mut buf = vec![0; sz]; - fill_random(buf.as_mut_slice()); - buf +pub fn random_vec(len: usize) -> SecretBytes { + SecretBytes::new_with(len, fill_random) } diff --git a/src/serde_utils.rs b/askar-keys/src/serde_utils.rs similarity index 91% rename from src/serde_utils.rs rename to askar-keys/src/serde_utils.rs index 94c1bd04..e9d79d80 100644 --- a/src/serde_utils.rs +++ b/askar-keys/src/serde_utils.rs @@ -1,12 +1,12 @@ -use std::fmt::{self, Display}; -use std::marker::PhantomData; -use std::str::FromStr; - -use indy_utils::base58; +use core::{ + fmt::{self, Display}, + marker::PhantomData, + str::FromStr, +}; use serde::{de::Visitor, Deserializer, Serializer}; -use super::types::SecretBytes; +use crate::buffer::SecretBytes; macro_rules! serde_as_str_impl { ($t:ident) => { @@ -73,10 +73,11 @@ pub mod as_str { } } -// structure borrowed from serde_bytes crate: +// code structure borrowed from serde_bytes crate: pub mod as_base58 { use super::*; + use alloc::{string::String, vec::Vec}; pub fn deserialize<'de, D, T>(deserializer: D) -> Result where @@ -105,7 +106,7 @@ pub mod as_base58 { where S: Serializer, { - serializer.serialize_str(&base58::encode(self)) + serializer.serialize_str(&bs58::encode(self).into_string()) } } @@ -114,7 +115,10 @@ pub mod as_base58 { where S: Serializer, { - serializer.serialize_str(&base58::encode(self)) + let mut s = bs58::encode(self).into_string(); + let ret = serializer.serialize_str(&s); + ::zeroize(&mut s); + ret } } @@ -183,7 +187,7 @@ pub mod as_base58 { where E: serde::de::Error, { - base58::decode(v).map_err(E::custom) + bs58::decode(v).into_vec().map_err(E::custom) } } diff --git a/src/keys/store.rs b/askar-keys/src/store.rs similarity index 100% rename from src/keys/store.rs rename to askar-keys/src/store.rs diff --git a/src/didcomm/pack/types.rs b/src/didcomm/pack/types.rs index cc6f00b6..53a02510 100644 --- a/src/didcomm/pack/types.rs +++ b/src/didcomm/pack/types.rs @@ -1,6 +1,6 @@ use std::future::Future; -pub use crate::keys::alg::edwards::{Ed25519KeyPair as KeyPair, Ed25519PublicKey as PublicKey}; +pub use crate::keys::alg::ed25519::{Ed25519KeyPair as KeyPair, Ed25519PublicKey as PublicKey}; #[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)] pub struct JWE { diff --git a/src/keys/alg/edwards.rs b/src/keys/alg/edwards.rs deleted file mode 100644 index 74282813..00000000 --- a/src/keys/alg/edwards.rs +++ /dev/null @@ -1,464 +0,0 @@ -use std::convert::{TryFrom, TryInto}; - -use curve25519_dalek::edwards::CompressedEdwardsY; -use ed25519_dalek::{Keypair, PublicKey, SecretKey, Signer, KEYPAIR_LENGTH}; -use rand::rngs::OsRng; -use serde_json::json; -use sha2::{self, Digest}; -use x25519_dalek::{PublicKey as XPublicKey, StaticSecret as XSecretKey}; - -use crate::{ - error::Error, - keys::any::{AnyPrivateKey, AnyPublicKey}, - keys::caps::{KeyAlg, KeyCapSign, KeyCapVerify, SignatureFormat, SignatureType}, - types::SecretBytes, -}; - -// FIXME - check for low-order points when loading public keys? -// https://github.com/tendermint/tmkms/pull/279 - -#[derive(Debug)] -pub struct Ed25519KeyPair(Keypair); - -impl Ed25519KeyPair { - pub fn generate() -> Result { - let mut rng = OsRng; - Ok(Self(Keypair::generate(&mut rng))) - } - - pub fn from_seed(ikm: &[u8]) -> Result { - let secret = SecretKey::from_bytes(ikm).map_err(|_| err_msg!("Invalid key material"))?; - let public = PublicKey::from(&secret); - Ok(Self(Keypair { secret, public })) - } - - pub fn from_bytes(kp: &[u8]) -> Result { - let kp = if kp.len() == 32 { - let secret = - SecretKey::from_bytes(kp).map_err(|_| err_msg!("Invalid keypair bytes"))?; - let public = PublicKey::from(&secret); - Keypair { secret, public } - } else { - Keypair::from_bytes(kp).map_err(|_| err_msg!("Invalid keypair bytes"))? - }; - Ok(Self(kp)) - } - - pub fn to_bytes(&self) -> SecretBytes { - SecretBytes::from(self.0.to_bytes().to_vec()) - } - - pub fn to_x25519(&self) -> X25519KeyPair { - let hash = sha2::Sha512::digest(&self.0.secret.to_bytes()[..]); - let output: [u8; 32] = (&hash[..32]).try_into().unwrap(); - // clamp result - let secret = XSecretKey::from(output); - let public = XPublicKey::from(&secret); - X25519KeyPair(secret, public) - } - - pub fn private_key(&self) -> SecretBytes { - SecretBytes::from(self.0.secret.to_bytes().to_vec()) - } - - pub fn public_key(&self) -> Ed25519PublicKey { - Ed25519PublicKey(self.0.public.clone()) - } - - pub fn sign(&self, message: &[u8]) -> [u8; 64] { - self.0.sign(message).to_bytes() - } - - pub fn verify(&self, message: &[u8], signature: [u8; 64]) -> bool { - self.0.verify_strict(message, &signature.into()).is_ok() - } -} - -impl Clone for Ed25519KeyPair { - fn clone(&self) -> Self { - Self(Keypair { - secret: SecretKey::from_bytes(&self.0.secret.as_bytes()[..]).unwrap(), - public: self.0.public.clone(), - }) - } -} - -impl KeyCapSign for Ed25519KeyPair { - fn key_sign( - &self, - data: &[u8], - sig_type: Option, - sig_format: Option, - ) -> Result, Error> { - match sig_type { - None | Some(SignatureType::EdDSA) => { - let sig = self.sign(data); - encode_signature(&sig, sig_format) - } - #[allow(unreachable_patterns)] - _ => Err(err_msg!(Unsupported, "Unsupported signature type")), - } - } -} - -impl KeyCapVerify for Ed25519KeyPair { - fn key_verify( - &self, - data: &[u8], - signature: &[u8], - sig_type: Option, - sig_format: Option, - ) -> Result { - match sig_type { - None | Some(SignatureType::EdDSA) => { - let mut sig = [0u8; 64]; - decode_signature(signature, &mut sig, sig_format)?; - Ok(self.verify(data, sig)) - } - #[allow(unreachable_patterns)] - _ => Err(err_msg!(Unsupported, "Unsupported signature type")), - } - } -} - -impl TryFrom<&AnyPrivateKey> for Ed25519KeyPair { - type Error = Error; - - fn try_from(value: &AnyPrivateKey) -> Result { - if value.alg == KeyAlg::Ed25519 { - Self::from_bytes(value.data.as_ref()) - } else { - Err(err_msg!(Unsupported, "Expected ed25519 key type")) - } - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Ed25519PublicKey(PublicKey); - -impl Ed25519PublicKey { - pub fn from_str(key: impl AsRef) -> Result { - let key = key.as_ref(); - let key = key.strip_suffix(":ed25519").unwrap_or(key); - let mut bval = [0u8; 32]; - bs58::decode(key) - .into(&mut bval) - .map_err(|_| err_msg!("Invalid base58 public key"))?; - Self::from_bytes(bval) - } - - pub fn from_bytes(pk: impl AsRef<[u8]>) -> Result { - let pk = - PublicKey::from_bytes(pk.as_ref()).map_err(|_| err_msg!("Invalid public key bytes"))?; - Ok(Self(pk)) - } - - pub fn to_base58(&self) -> String { - bs58::encode(self.to_bytes()).into_string() - } - - pub fn to_string(&self) -> String { - let mut sval = String::with_capacity(64); - bs58::encode(self.to_bytes()).into(&mut sval).unwrap(); - sval.push_str(":ed25519"); - sval - } - - pub fn to_bytes(&self) -> [u8; 32] { - self.0.to_bytes() - } - - pub fn to_jwk(&self) -> Result { - let x = base64::encode_config(self.to_bytes(), base64::URL_SAFE_NO_PAD); - Ok(json!({ - "kty": "OKP", - "crv": "Ed25519", - "x": x, - "key_ops": ["verify"] - })) - } - - pub fn to_x25519(&self) -> X25519PublicKey { - let public = XPublicKey::from( - CompressedEdwardsY(self.0.to_bytes()) - .decompress() - .unwrap() - .to_montgomery() - .to_bytes(), - ); - X25519PublicKey(public) - } - - pub fn verify(&self, message: &[u8], signature: [u8; 64]) -> bool { - self.0.verify_strict(message, &signature.into()).is_ok() - } -} - -impl KeyCapVerify for Ed25519PublicKey { - fn key_verify( - &self, - data: &[u8], - signature: &[u8], - sig_type: Option, - sig_format: Option, - ) -> Result { - match sig_type { - None | Some(SignatureType::EdDSA) => { - let mut sig = [0u8; 64]; - decode_signature(signature, &mut sig, sig_format)?; - Ok(self.verify(data, sig)) - } - #[allow(unreachable_patterns)] - _ => Err(err_msg!(Unsupported, "Unsupported signature type")), - } - } -} - -impl TryFrom<&AnyPublicKey> for Ed25519PublicKey { - type Error = Error; - - fn try_from(value: &AnyPublicKey) -> Result { - if value.alg == KeyAlg::Ed25519 { - Self::from_bytes(&value.data) - } else { - Err(err_msg!(Unsupported, "Expected ed25519 key type")) - } - } -} - -#[derive(Clone)] -pub struct X25519KeyPair(XSecretKey, XPublicKey); - -impl X25519KeyPair { - pub fn generate() -> Result { - let sk = XSecretKey::new(OsRng); - let pk = XPublicKey::from(&sk); - Ok(Self(sk, pk)) - } - - pub fn private_key(&self) -> SecretBytes { - SecretBytes::from(self.0.to_bytes().to_vec()) - } - - pub fn public_key(&self) -> X25519PublicKey { - X25519PublicKey(self.1.clone()) - } - - pub fn key_exchange_with(&self, pk: &X25519PublicKey) -> SecretBytes { - let xk = self.0.diffie_hellman(&pk.0); - SecretBytes::from(xk.as_bytes().to_vec()) - } - - pub fn from_bytes(pair: &[u8]) -> Result { - if pair.len() != KEYPAIR_LENGTH { - return Err(err_msg!("Invalid keypair bytes")); - } - let sk: [u8; 32] = pair[..32].try_into().unwrap(); - let pk: [u8; 32] = pair[32..].try_into().unwrap(); - Ok(Self(XSecretKey::from(sk), XPublicKey::from(pk))) - } - - pub fn to_bytes(&self) -> SecretBytes { - let mut bytes = Vec::with_capacity(KEYPAIR_LENGTH); - bytes.extend_from_slice(&self.0.to_bytes()); - bytes.extend_from_slice(self.1.as_bytes()); - SecretBytes::from(bytes) - } -} - -impl TryFrom<&AnyPrivateKey> for X25519KeyPair { - type Error = Error; - - fn try_from(value: &AnyPrivateKey) -> Result { - if value.alg == KeyAlg::X25519 { - Self::from_bytes(value.data.as_ref()) - } else { - Err(err_msg!(Unsupported, "Expected x25519 key type")) - } - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct X25519PublicKey(XPublicKey); - -impl X25519PublicKey { - pub fn from_str(key: impl AsRef) -> Result { - let key = key.as_ref(); - let key = key.strip_suffix(":x25519").unwrap_or(key); - let mut bval = [0u8; 32]; - bs58::decode(key) - .into(&mut bval) - .map_err(|_| err_msg!("Invalid base58 public key"))?; - Self::from_bytes(bval) - } - - pub fn from_bytes(pk: impl AsRef<[u8]>) -> Result { - let pk: [u8; 32] = pk - .as_ref() - .try_into() - .map_err(|_| err_msg!("Invalid public key bytes"))?; - Ok(Self(XPublicKey::from(pk))) - } - - pub fn to_base58(&self) -> String { - bs58::encode(self.to_bytes()).into_string() - } - - pub fn to_jwk(&self) -> Result { - let x = base64::encode_config(self.to_bytes(), base64::URL_SAFE_NO_PAD); - Ok(json!({ - "kty": "OKP", - "crv": "X25519", - "x": x, - "key_ops": ["deriveKey"] - })) - } - - pub fn to_string(&self) -> String { - let mut sval = String::with_capacity(64); - bs58::encode(self.to_bytes()).into(&mut sval).unwrap(); - sval.push_str(":x25519"); - sval - } - - pub fn to_bytes(&self) -> [u8; 32] { - self.0.to_bytes() - } -} - -impl TryFrom<&AnyPublicKey> for X25519PublicKey { - type Error = Error; - - fn try_from(value: &AnyPublicKey) -> Result { - if value.alg == KeyAlg::X25519 { - Self::from_bytes(&value.data) - } else { - Err(err_msg!(Unsupported, "Expected x25519 key type")) - } - } -} - -pub(super) fn encode_signature( - signature: &[u8], - sig_format: Option, -) -> Result, Error> { - match sig_format { - None | Some(SignatureFormat::Base58) => Ok(bs58::encode(signature).into_vec()), - Some(SignatureFormat::Raw) => Ok(signature.to_vec()), - #[allow(unreachable_patterns)] - _ => Err(err_msg!(Unsupported, "Unsupported signature format")), - } -} - -pub(super) fn decode_signature( - sig_input: &[u8], - sig_output: &mut impl AsMut<[u8]>, - sig_format: Option, -) -> Result<(), Error> { - match sig_format { - None | Some(SignatureFormat::Base58) => { - bs58::decode(sig_input) - .into(sig_output) - .map_err(|_| err_msg!("Invalid base58 signature"))?; - Ok(()) - } - Some(SignatureFormat::Raw) => { - if sig_input.len() != sig_output.as_mut().len() { - return Err(err_msg!("Invalid raw signature")); - } - sig_output.as_mut().copy_from_slice(sig_input); - Ok(()) - } - #[allow(unreachable_patterns)] - _ => Err(err_msg!(Unsupported, "Unsupported signature format")), - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn expand_keypair() { - let seed = b"000000000000000000000000Trustee1"; - let test_sk = hex::decode("3030303030303030303030303030303030303030303030305472757374656531e33aaf381fffa6109ad591fdc38717945f8fabf7abf02086ae401c63e9913097").unwrap(); - - let kp = Ed25519KeyPair::from_seed(seed).unwrap(); - assert_eq!(kp.to_bytes(), test_sk); - - // test round trip - let cmp = Ed25519KeyPair::from_bytes(&test_sk).unwrap(); - assert_eq!(cmp.to_bytes(), test_sk); - } - - #[test] - fn ed25519_to_x25519() { - let test_keypair = hex::decode("1c1179a560d092b90458fe6ab8291215a427fcd6b3927cb240701778ef55201927c96646f2d4632d4fc241f84cbc427fbc3ecaa95becba55088d6c7b81fc5bbf").unwrap(); - let x_sk = hex::decode("08e7286c232ec71b37918533ea0229bf0c75d3db4731df1c5c03c45bc909475f") - .unwrap(); - let x_pk = hex::decode("9b4260484c889158c128796103dc8d8b883977f2ef7efb0facb12b6ca9b2ae3d") - .unwrap(); - let x_pair = Ed25519KeyPair::from_bytes(&test_keypair) - .unwrap() - .to_x25519() - .to_bytes(); - assert_eq!(&x_pair[..32], x_sk); - assert_eq!(&x_pair[32..], x_pk); - } - - #[test] - fn jwk_expected() { - // from https://www.connect2id.com/blog/nimbus-jose-jwt-6 - // { - // "kty" : "OKP", - // "crv" : "Ed25519", - // "x" : "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo", - // "d" : "nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A" - // "use" : "sig", - // "kid" : "FdFYFzERwC2uCBB46pZQi4GG85LujR8obt-KWRBICVQ" - // } - let test_pvt_b64 = "nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A"; - let test_pvt = base64::decode_config(test_pvt_b64, base64::URL_SAFE).unwrap(); - let sk = Ed25519KeyPair::from_bytes(&test_pvt).expect("Error creating signing key"); - let vk = sk.public_key(); - let jwk = vk.to_jwk().expect("Error converting public key to JWK"); - assert_eq!(jwk["kty"], "OKP"); - assert_eq!(jwk["crv"], "Ed25519"); - assert_eq!(jwk["x"], "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"); - assert_eq!( - base64::encode_config(sk.private_key(), base64::URL_SAFE_NO_PAD), - test_pvt_b64 - ); - } - - #[test] - fn sign_verify_expected() { - let test_msg = b"This is a dummy message for use with tests"; - let test_sig = hex::decode("451b5b8e8725321541954997781de51f4142e4a56bab68d24f6a6b92615de5eefb74134138315859a32c7cf5fe5a488bc545e2e08e5eedfd1fb10188d532d808").unwrap(); - - let test_keypair = hex::decode("1c1179a560d092b90458fe6ab8291215a427fcd6b3927cb240701778ef55201927c96646f2d4632d4fc241f84cbc427fbc3ecaa95becba55088d6c7b81fc5bbf").unwrap(); - let kp = Ed25519KeyPair::from_bytes(&test_keypair).unwrap(); - let sig = kp.sign(&test_msg[..]); - assert_eq!(sig, test_sig.as_slice()); - assert_eq!(kp.public_key().verify(&test_msg[..], sig), true); - assert_eq!(kp.public_key().verify(b"Not the message", sig), false); - assert_eq!(kp.public_key().verify(&test_msg[..], [0u8; 64]), false); - } - - #[test] - fn key_exchange_random() { - let kp1 = X25519KeyPair::generate().unwrap(); - let kp2 = X25519KeyPair::generate().unwrap(); - assert_ne!(kp1.to_bytes(), kp2.to_bytes()); - - let xch1 = kp1.key_exchange_with(&kp2.public_key()); - let xch2 = kp2.key_exchange_with(&kp1.public_key()); - assert_eq!(xch1, xch2); - - // test round trip - let xch3 = X25519KeyPair::from_bytes(&kp1.to_bytes()) - .unwrap() - .key_exchange_with(&kp2.public_key()); - assert_eq!(xch3, xch1); - } -} diff --git a/src/keys/alg/mod.rs b/src/keys/alg/mod.rs deleted file mode 100644 index bfaef043..00000000 --- a/src/keys/alg/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod edwards; - -pub mod p256; diff --git a/src/keys/kdf/mod.rs b/src/keys/kdf.rs similarity index 90% rename from src/keys/kdf/mod.rs rename to src/keys/kdf.rs index d5cf3649..1791194a 100644 --- a/src/keys/kdf/mod.rs +++ b/src/keys/kdf.rs @@ -1,12 +1,9 @@ -use indy_utils::base58; - -use super::wrap::PREFIX_KDF; +// use super::wrap::PREFIX_KDF; use crate::error::Result; -use crate::keys::wrap::WrapKey; -use crate::options::Options; +// use crate::keys::wrap::WrapKey; +// use crate::options::Options; -pub mod argon2; -use self::argon2::{generate_salt, Level as Argon2Level, SALT_SIZE}; +use askar_keys::kdf::argon2::{generate_salt, Level as Argon2Level, SALT_SIZE}; pub const METHOD_ARGON2I: &'static str = "argon2i"; @@ -62,7 +59,7 @@ impl KdfMethod { Self::Argon2i(level) => { let salt = generate_salt(); let key = level.derive_key(&salt, password)?; - let detail = format!("?salt={}", base58::encode(&salt)); + let detail = format!("?salt={}", bs58::encode(&salt)); Ok((key.into(), detail)) } } diff --git a/src/keys/kdf/argon2.rs b/src/keys/kdf/argon2.rs deleted file mode 100644 index 3a909635..00000000 --- a/src/keys/kdf/argon2.rs +++ /dev/null @@ -1,80 +0,0 @@ -use zeroize::Zeroize; - -use crate::error::Result; -use crate::keys::{encrypt::aead::ChaChaEncrypt, store::EncKey}; -use crate::random::random_vec; - -pub const LEVEL_INTERACTIVE: &'static str = "13:int"; -pub const LEVEL_MODERATE: &'static str = "13:mod"; - -pub const HASH_SIZE: usize = 32; -pub const SALT_SIZE: usize = 16; - -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] -pub enum Level { - Interactive, - Moderate, -} - -impl Default for Level { - fn default() -> Self { - Self::Moderate - } -} - -impl Level { - pub fn from_str(level: &str) -> Option { - match level { - "int" | LEVEL_INTERACTIVE => Some(Self::Interactive), - "mod" | LEVEL_MODERATE => Some(Self::Moderate), - "" => Some(Self::default()), - _ => None, - } - } - - pub fn as_str(&self) -> &'static str { - match self { - Self::Interactive => LEVEL_INTERACTIVE, - Self::Moderate => LEVEL_MODERATE, - } - } - - pub fn derive_key(&self, salt: &[u8], password: &str) -> Result> { - let (mem_cost, time_cost) = match self { - Self::Interactive => (32768, 4), - Self::Moderate => (131072, 6), - }; - derive_key(password, salt, mem_cost, time_cost) - } -} - -fn derive_key( - password: &str, - salt: &[u8], - mem_cost: u32, - time_cost: u32, -) -> Result> { - if salt.len() < SALT_SIZE { - return Err(err_msg!(Encryption, "Invalid salt for argon2i hash")); - } - let config = argon2::Config { - variant: argon2::Variant::Argon2i, - version: argon2::Version::Version13, - mem_cost, - time_cost, - lanes: 1, - thread_mode: argon2::ThreadMode::Sequential, - secret: &[], - ad: &[], - hash_length: HASH_SIZE as u32, - }; - let mut hashed = argon2::hash_raw(password.as_bytes(), &salt[..SALT_SIZE], &config) - .map_err(|e| err_msg!(Encryption, "Error deriving key: {}", e))?; - let key = EncKey::::from_slice(&hashed); - hashed.zeroize(); - Ok(key) -} - -pub fn generate_salt() -> Vec { - random_vec(SALT_SIZE) -} diff --git a/src/keys/mod.rs b/src/keys/mod.rs index 5c2b4b6d..c52b5ebb 100644 --- a/src/keys/mod.rs +++ b/src/keys/mod.rs @@ -1,37 +1,12 @@ use std::collections::HashMap; use std::sync::Arc; -use async_mutex::Mutex; use zeroize::Zeroize; -use super::error::Result; -use super::future::unblock; -use super::types::{EncEntryTag, EntryTag, ProfileId, SecretBytes}; - -use self::store::StoreKey; -use self::wrap::WrapKey; - -pub mod any; -pub use self::any::{AnyPrivateKey, AnyPublicKey}; - -pub mod alg; -use self::alg::edwards::{Ed25519KeyPair, Ed25519PublicKey}; - -pub mod caps; -pub use self::caps::{ - KeyAlg, KeyCapGetPublic, KeyCapSign, KeyCapVerify, KeyCategory, SignatureFormat, SignatureType, -}; - -pub mod encrypt; +pub use askar_keys::PassKey; pub mod kdf; -pub mod store; -pub use self::store::{KeyEntry, KeyParams}; - -mod types; -pub use self::types::PassKey; - pub mod wrap; /// Derive the (public) verification key for a keypair diff --git a/src/keys/types.rs b/src/keys/types.rs deleted file mode 100644 index 46ad4d60..00000000 --- a/src/keys/types.rs +++ /dev/null @@ -1,212 +0,0 @@ -use std::borrow::Cow; -use std::cmp::Ordering; -use std::fmt::{self, Debug, Formatter}; -use std::mem::ManuallyDrop; -use std::ops::Deref; - -use aead::generic_array::{ArrayLength, GenericArray}; -use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; -use zeroize::Zeroize; - -use crate::random::random_array; - -/// A secure key representation for fixed-length keys -#[derive(Clone, Debug, Hash, Zeroize)] -pub struct ArrayKey>(GenericArray); - -impl> ArrayKey { - pub const SIZE: usize = L::USIZE; - - #[inline] - pub fn from_slice>(data: D) -> Self { - Self(GenericArray::clone_from_slice(data.as_ref())) - } - - #[inline] - pub fn extract(self) -> GenericArray { - self.0 - } - - #[inline] - pub fn random() -> Self { - Self(random_array()) - } -} - -impl> Default for ArrayKey { - #[inline] - fn default() -> Self { - Self(GenericArray::default()) - } -} - -impl> From> for ArrayKey { - fn from(key: GenericArray) -> Self { - Self(key) - } -} - -impl> std::ops::Deref for ArrayKey { - type Target = GenericArray; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl> std::ops::DerefMut for ArrayKey { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl> PartialEq for ArrayKey { - fn eq(&self, other: &Self) -> bool { - **self == **other - } -} -impl> Eq for ArrayKey {} - -impl> PartialOrd for ArrayKey { - fn partial_cmp(&self, other: &Self) -> Option { - self.0.partial_cmp(&*other) - } -} -impl> Ord for ArrayKey { - fn cmp(&self, other: &Self) -> Ordering { - self.0.cmp(&*other) - } -} - -impl> Serialize for ArrayKey { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(hex::encode(&self.0.as_slice()).as_str()) - } -} - -impl<'a, L: ArrayLength> Deserialize<'a> for ArrayKey { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'a>, - { - deserializer.deserialize_str(KeyVisitor { - _pd: std::marker::PhantomData, - }) - } -} - -struct KeyVisitor> { - _pd: std::marker::PhantomData, -} - -impl<'a, L: ArrayLength> Visitor<'a> for KeyVisitor { - type Value = ArrayKey; - - fn expecting(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { - formatter.write_str(stringify!($name)) - } - - fn visit_str(self, value: &str) -> Result, E> - where - E: serde::de::Error, - { - let key = hex::decode(value).map_err(E::custom)?; - Ok(ArrayKey(GenericArray::clone_from_slice(key.as_slice()))) - } -} - -/// A possibly-empty password or key used to derive a store wrap key -#[derive(Clone)] -pub struct PassKey<'a>(Option>); - -impl PassKey<'_> { - /// Create a scoped reference to the passkey - pub fn as_ref(&self) -> PassKey<'_> { - PassKey(Some(Cow::Borrowed(&**self))) - } - - pub(crate) fn is_none(&self) -> bool { - self.0.is_none() - } - - pub(crate) fn into_owned(self) -> PassKey<'static> { - let mut slf = ManuallyDrop::new(self); - let val = slf.0.take(); - PassKey(match val { - None => None, - Some(Cow::Borrowed(s)) => Some(Cow::Owned(s.to_string())), - Some(Cow::Owned(s)) => Some(Cow::Owned(s)), - }) - } -} - -impl Debug for PassKey<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - if cfg!(test) { - f.debug_tuple("PassKey").field(&*self).finish() - } else { - f.debug_tuple("PassKey").field(&"").finish() - } - } -} - -impl Default for PassKey<'_> { - fn default() -> Self { - Self(None) - } -} - -impl Deref for PassKey<'_> { - type Target = str; - - fn deref(&self) -> &str { - match self.0.as_ref() { - None => "", - Some(s) => s.as_ref(), - } - } -} - -impl Drop for PassKey<'_> { - fn drop(&mut self) { - self.zeroize(); - } -} - -impl<'a> From<&'a str> for PassKey<'a> { - fn from(inner: &'a str) -> Self { - Self(Some(Cow::Borrowed(inner))) - } -} - -impl From for PassKey<'_> { - fn from(inner: String) -> Self { - Self(Some(Cow::Owned(inner))) - } -} - -impl<'a> From> for PassKey<'a> { - fn from(inner: Option<&'a str>) -> Self { - Self(inner.map(Cow::Borrowed)) - } -} - -impl<'a, 'b> PartialEq> for PassKey<'a> { - fn eq(&self, other: &PassKey<'b>) -> bool { - &**self == &**other - } -} -impl Eq for PassKey<'_> {} - -impl Zeroize for PassKey<'_> { - fn zeroize(&mut self) { - match self.0.take() { - Some(Cow::Owned(mut s)) => { - s.zeroize(); - } - _ => (), - } - } -} diff --git a/src/keys/wrap.rs b/src/keys/wrap.rs index 52eb2adc..0c63cf50 100644 --- a/src/keys/wrap.rs +++ b/src/keys/wrap.rs @@ -1,9 +1,9 @@ -use indy_utils::base58; - -use super::encrypt::{aead::ChaChaEncrypt, SymEncrypt, SymEncryptKey}; use super::kdf::KdfMethod; -use super::types::PassKey; -use crate::{error::Result, SecretBytes}; + +use askar_keys::{ + encrypt::{aead::ChaChaEncrypt, SymEncrypt, SymEncryptKey}, + PassKey, SecretBytes, +}; pub const PREFIX_KDF: &'static str = "kdf"; pub const PREFIX_RAW: &'static str = "raw"; diff --git a/src/store.rs b/src/store.rs index e8eb175b..195fdb59 100644 --- a/src/store.rs +++ b/src/store.rs @@ -12,7 +12,7 @@ use super::didcomm::pack::{pack_message, unpack_message, KeyLookup}; use super::error::Result; use super::future::BoxFuture; use super::keys::{ - alg::edwards::{Ed25519KeyPair, Ed25519PublicKey}, + alg::ed25519::{Ed25519KeyPair, Ed25519PublicKey}, caps::KeyCapSign, wrap::WrapKeyMethod, AnyPrivateKey, KeyAlg, KeyCategory, KeyEntry, KeyParams, PassKey, diff --git a/src/types.rs b/src/types.rs index f8338541..2be2c196 100644 --- a/src/types.rs +++ b/src/types.rs @@ -3,7 +3,6 @@ use std::mem; use std::ops::Deref; use std::str::FromStr; -use aead::Buffer; use serde::{ de::{Error as SerdeError, MapAccess, SeqAccess, Visitor}, ser::SerializeMap, @@ -311,149 +310,6 @@ pub(crate) struct EncEntryTag { pub plaintext: bool, } -struct MaybeStr<'a>(&'a [u8]); - -impl Debug for MaybeStr<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - if let Ok(sval) = std::str::from_utf8(self.0) { - write!(f, "{:?}", sval) - } else { - write!(f, "_\"{}\"", hex::encode(self.0)) - } - } -} - -/// A protected byte buffer -#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Zeroize)] -pub struct SecretBytes(Vec); - -impl SecretBytes { - pub(crate) fn as_buffer(&mut self) -> SecretBytesMut<'_> { - SecretBytesMut(&mut self.0) - } - - /// Try to convert the buffer value to a string reference - pub fn as_opt_str(&self) -> Option<&str> { - std::str::from_utf8(self.0.as_slice()).ok() - } - - pub(crate) fn into_vec(mut self) -> Vec { - let mut v = vec![]; // note: no heap allocation for empty vec - mem::swap(&mut v, &mut self.0); - mem::forget(self); - v - } -} - -impl Debug for SecretBytes { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - if cfg!(test) { - f.debug_tuple("Secret") - .field(&MaybeStr(self.0.as_slice())) - .finish() - } else { - f.write_str("") - } - } -} - -impl AsRef<[u8]> for SecretBytes { - fn as_ref(&self) -> &[u8] { - self.0.as_slice() - } -} - -impl Deref for SecretBytes { - type Target = [u8]; - - fn deref(&self) -> &Self::Target { - self.0.as_slice() - } -} - -impl Drop for SecretBytes { - fn drop(&mut self) { - self.zeroize(); - } -} - -impl From<&[u8]> for SecretBytes { - fn from(inner: &[u8]) -> Self { - Self(inner.to_vec()) - } -} - -impl From<&str> for SecretBytes { - fn from(inner: &str) -> Self { - Self(inner.as_bytes().to_vec()) - } -} - -impl From for SecretBytes { - fn from(inner: String) -> Self { - Self(inner.into_bytes()) - } -} - -impl From> for SecretBytes { - fn from(inner: Vec) -> Self { - Self(inner) - } -} - -impl PartialEq<&[u8]> for SecretBytes { - fn eq(&self, other: &&[u8]) -> bool { - self.0.eq(other) - } -} - -impl PartialEq> for SecretBytes { - fn eq(&self, other: &Vec) -> bool { - self.0.eq(other) - } -} - -pub(crate) struct SecretBytesMut<'m>(&'m mut Vec); - -impl SecretBytesMut<'_> { - /// Obtain a large-enough SecretBytes without creating unsafe copies of - /// the contained data - pub fn reserve_extra(&mut self, extra: usize) { - let len = self.0.len(); - if extra + len > self.0.capacity() { - // allocate a new buffer and copy the secure data over - let mut buf = Vec::with_capacity(extra + len); - buf.extend_from_slice(&self.0[..]); - mem::swap(&mut buf, &mut self.0); - buf.zeroize() - } - } -} - -impl Buffer for SecretBytesMut<'_> { - fn extend_from_slice(&mut self, other: &[u8]) -> Result<(), aead::Error> { - self.reserve_extra(other.len()); - self.0.extend_from_slice(other); - Ok(()) - } - - fn truncate(&mut self, len: usize) { - self.0.truncate(len); - } -} - -impl AsRef<[u8]> for SecretBytesMut<'_> { - fn as_ref(&self) -> &[u8] { - self.0.as_slice() - } -} - -impl AsMut<[u8]> for SecretBytesMut<'_> { - fn as_mut(&mut self) -> &mut [u8] { - self.0.as_mut_slice() - } -} - /// A WQL filter used to restrict record queries #[derive(Clone, Debug, PartialEq, Eq)] #[repr(transparent)] From c454a8f02b650949c19b3fdc34618d3fa13f0e8d Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Fri, 9 Apr 2021 12:09:27 -0700 Subject: [PATCH 006/116] remove std dependency; switch to argon2 crate Signed-off-by: Andrew Whitehead --- askar-keys/Cargo.toml | 44 ++++------ askar-keys/src/alg/bls.rs | 24 ------ askar-keys/src/buffer.rs | 26 ++++-- askar-keys/src/kdf/argon2.rs | 153 +++++++++++++++++++++-------------- askar-keys/src/kdf/concat.rs | 83 +++++++++++++++++++ askar-keys/src/kdf/mod.rs | 2 + askar-keys/src/lib.rs | 4 + askar-keys/src/random.rs | 4 +- 8 files changed, 222 insertions(+), 118 deletions(-) create mode 100644 askar-keys/src/kdf/concat.rs diff --git a/askar-keys/Cargo.toml b/askar-keys/Cargo.toml index 64b011f2..488afc71 100644 --- a/askar-keys/Cargo.toml +++ b/askar-keys/Cargo.toml @@ -10,51 +10,39 @@ repository = "https://github.com/hyperledger/aries-askar/" categories = ["cryptography", "database"] keywords = ["hyperledger", "aries", "ssi", "verifiable", "credentials"] -[lib] -name = "askar_keys" -path = "src/lib.rs" -crate-type = ["rlib"] - [package.metadata.docs.rs] -features = ["all"] -no-default-features = true rustdoc-args = ["--cfg", "docsrs"] [features] -default = ["logger"] +default = [] any = [] -logger = ["env_logger", "log"] std = [] +[dev-dependencies] +hex-literal = "0.3" + [dependencies] aead = "0.3" -base64 = "0.13" -blake2 = "0.9" +argon2 = "0.1" +base64 = { version = "0.13", default-features = false, features = ["alloc"] } +blake2 = { version = "0.9", default-features = false } bls12_381 = { version = "0.4.0", path = "../../bls12_381" } -bs58 = "0.4" +bs58 = { version = "0.4", default-features = false, features = ["alloc"] } chacha20 = "0.6" # should match version from chacha20poly1305 chacha20poly1305 = { version = "0.7", default-features = false, features = ["alloc", "chacha20"] } crypto_box = "0.5" -curve25519-dalek = "3.0" -ed25519-dalek = { version = "1.0", features = ["std"] } -env_logger = { version = "0.7", optional = true } +curve25519-dalek = { version = "3.0", default-features = false, features = ["alloc", "u64_backend"] } +ed25519-dalek = { version = "1.0", default-features = false, features = ["alloc", "u64_backend"] } digest = "0.9" group = "0.9" -hex = "0.4" +hex = { version = "0.4", default-features = false } hkdf = "0.10" hmac = "0.10" -k256 = { version = "0.7", features = ["ecdsa", "ecdh", "sha256"] } -log = { version = "0.4", optional = true } -p256 = { version = "0.7", features = ["ecdsa", "ecdh"] } -rand = "0.7" -rust-argon2 = "0.8" +k256 = { version = "0.7", default-features = false, features = ["arithmetic", "ecdsa", "ecdh", "sha256"] } +p256 = { version = "0.7", default-features = false, features = ["arithmetic", "ecdsa", "ecdh"] } +rand = { version = "0.7", default-features = false, features = ["getrandom"] } serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } -serde_bytes = "0.11" serde_json = { version = "1.0", default-features = false, features = ["alloc"] } -sha2 = "0.9" -x25519-dalek = "1.1" +sha2 = { version = "0.9", default-features = false } +x25519-dalek = { version = "1.1", default-features = false, features = ["u64_backend"] } zeroize = { version = "1.1.0", features = ["zeroize_derive"] } - -[profile.release] -lto = true -codegen-units = 1 diff --git a/askar-keys/src/alg/bls.rs b/askar-keys/src/alg/bls.rs index 2f8b1af4..ad996b43 100644 --- a/askar-keys/src/alg/bls.rs +++ b/askar-keys/src/alg/bls.rs @@ -428,30 +428,6 @@ impl Bls12G2PublicKey { mod tests { use super::*; - #[test] - fn fingerprint() { - let mut secret = [0u8; 64]; - OsRng.fill_bytes(&mut secret); - - let sk = Scalar::from_bytes_wide(&secret); - let pk_g1 = G1Affine::from(G1Affine::generator() * sk); - let pk_g2 = G2Affine::from(G2Affine::generator() * sk); - println!( - "sk {}\ng1 {}\ng2: {}", - hex::encode(sk.to_bytes()), - hex::encode(pk_g1.to_compressed()), - hex::encode(pk_g2.to_compressed()) - ); - - let cross_0 = pairing(&G1Affine::generator(), &pk_g2); - let cross_1 = pairing(&pk_g1, &G2Affine::generator()); - println!("gt_0 {:?}", cross_0); - println!("gt_1 {:?}", cross_1); - println!("eq {:?}", cross_0 == cross_1); - - assert!(false); - } - #[test] fn sig_basic_g1() { // test vectors from https://github.com/algorand/bls_sigs_ref/pull/7/files diff --git a/askar-keys/src/buffer.rs b/askar-keys/src/buffer.rs index 7fe73731..8973f9eb 100644 --- a/askar-keys/src/buffer.rs +++ b/askar-keys/src/buffer.rs @@ -10,9 +10,10 @@ use core::{ mem::{self, ManuallyDrop}, ops::{Deref, DerefMut}, }; +use fmt::Write; use aead::{ - generic_array::{ArrayLength, GenericArray}, + generic_array::{typenum, ArrayLength, GenericArray}, Buffer, }; use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; @@ -92,7 +93,10 @@ impl> Serialize for ArrayKey { where S: Serializer, { - serializer.serialize_str(hex::encode(&self.0.as_slice()).as_str()) + // create an array twice the size of L on the stack (could be made clearer with const generics) + let mut hex_str = GenericArray::>::default(); + hex::encode_to_slice(&self.0.as_slice(), &mut hex_str).unwrap(); + serializer.serialize_str(core::str::from_utf8(&hex_str[..]).unwrap()) } } @@ -126,8 +130,9 @@ impl<'a, L: ArrayLength> Visitor<'a> for KeyVisitor { where E: serde::de::Error, { - let key = hex::decode(value).map_err(E::custom)?; - Ok(ArrayKey(GenericArray::clone_from_slice(key.as_slice()))) + let mut arr = GenericArray::default(); + hex::decode_to_slice(value, &mut arr[..]).map_err(E::custom)?; + Ok(ArrayKey(arr)) } } @@ -403,7 +408,12 @@ impl Debug for MaybeStr<'_> { if let Ok(sval) = core::str::from_utf8(self.0) { write!(f, "{:?}", sval) } else { - write!(f, "_\"{}\"", hex::encode(self.0)) + f.write_char('<')?; + for c in self.0 { + f.write_fmt(format_args!("{:02x}", c))?; + } + f.write_char('>')?; + Ok(()) } } } @@ -448,4 +458,10 @@ mod tests { b.extend_from_slice(b"y").unwrap(); assert_eq!(&v[..], b"hey"); } + + #[test] + fn test_maybe_str() { + assert_eq!(format!("{:?}", MaybeStr(&[])), "\"\""); + assert_eq!(format!("{:?}", MaybeStr(&[255, 0])), ""); + } } diff --git a/askar-keys/src/kdf/argon2.rs b/askar-keys/src/kdf/argon2.rs index d0c69e6a..b32d1f16 100644 --- a/askar-keys/src/kdf/argon2.rs +++ b/askar-keys/src/kdf/argon2.rs @@ -1,6 +1,6 @@ -use zeroize::Zeroize; +use crate::{buffer::SecretBytes, error::Error, random::random_secret}; -use crate::{buffer::SecretBytes, encrypt::aead::ChaChaEncrypt, error::Error, random::random_vec}; +pub use argon2::{Algorithm, Version}; pub const LEVEL_INTERACTIVE: &'static str = "13:int"; pub const LEVEL_MODERATE: &'static str = "13:mod"; @@ -8,71 +8,106 @@ pub const LEVEL_MODERATE: &'static str = "13:mod"; pub const HASH_SIZE: usize = 32; pub const SALT_SIZE: usize = 16; -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] -pub enum Level { - Interactive, - Moderate, -} +pub const PARAMS_INTERACTIVE: Params = Params { + alg: Algorithm::Argon2i, + version: Version::V0x13, + mem_cost: 32768, + time_cost: 4, +}; +pub const PARAMS_MODERATE: Params = Params { + alg: Algorithm::Argon2i, + version: Version::V0x13, + mem_cost: 131072, + time_cost: 6, +}; -impl Default for Level { - fn default() -> Self { - Self::Moderate - } +pub struct Params { + alg: Algorithm, + version: Version, + mem_cost: u32, + time_cost: u32, } -impl Level { - pub fn from_str(level: &str) -> Option { - match level { - "int" | LEVEL_INTERACTIVE => Some(Self::Interactive), - "mod" | LEVEL_MODERATE => Some(Self::Moderate), - "" => Some(Self::default()), - _ => None, - } - } +// #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +// pub enum Level { +// Interactive, +// Moderate, +// } - pub fn as_str(&self) -> &'static str { - match self { - Self::Interactive => LEVEL_INTERACTIVE, - Self::Moderate => LEVEL_MODERATE, - } - } +// impl Default for Level { +// fn default() -> Self { +// Self::Moderate +// } +// } - // pub fn derive_key(&self, salt: &[u8], password: &str) -> Result> { - // let (mem_cost, time_cost) = match self { - // Self::Interactive => (32768, 4), - // Self::Moderate => (131072, 6), - // }; - // derive_key(password, salt, mem_cost, time_cost) - // } -} +// impl Level { +// pub fn from_str(level: &str) -> Option { +// match level { +// "int" | LEVEL_INTERACTIVE => Some(Self::Interactive), +// "mod" | LEVEL_MODERATE => Some(Self::Moderate), +// "" => Some(Self::default()), +// _ => None, +// } +// } -// fn derive_key( -// password: &str, -// salt: &[u8], -// mem_cost: u32, -// time_cost: u32, -// ) -> Result> { -// if salt.len() < SALT_SIZE { -// return Err(err_msg!(Encryption, "Invalid salt for argon2i hash")); +// pub fn as_str(&self) -> &'static str { +// match self { +// Self::Interactive => LEVEL_INTERACTIVE, +// Self::Moderate => LEVEL_MODERATE, +// } // } -// let config = argon2::Config { -// variant: argon2::Variant::Argon2i, -// version: argon2::Version::Version13, -// mem_cost, -// time_cost, -// lanes: 1, -// thread_mode: argon2::ThreadMode::Sequential, -// secret: &[], -// ad: &[], -// hash_length: HASH_SIZE as u32, -// }; -// let mut hashed = argon2::hash_raw(password.as_bytes(), &salt[..SALT_SIZE], &config) -// .map_err(|e| err_msg!(Encryption, "Error deriving key: {}", e))?; -// let key = EncKey::::from_slice(&hashed); -// hashed.zeroize(); -// Ok(key) + +// // pub fn derive_key(&self, salt: &[u8], password: &str) -> Result> { +// // let (mem_cost, time_cost) = match self { +// // Self::Interactive => (32768, 4), +// // Self::Moderate => (131072, 6), +// // }; +// // derive_key(password, salt, mem_cost, time_cost) +// // } // } +pub struct Argon2; + +impl Argon2 { + pub fn derive_key( + password: &[u8], + salt: &[u8], + params: Params, + output: &mut [u8], + ) -> Result<(), Error> { + if salt.len() < SALT_SIZE { + return Err(err_msg!("invalid salt for argon2i hash")); + } + if output.len() > u32::MAX as usize { + return Err(err_msg!("output length exceeds max for argon2i hash")); + } + let context = + argon2::Argon2::new(None, params.time_cost, params.mem_cost, 1, params.version) + .map_err(|e| err_msg!("Error creating hasher: {}", e))?; + context + .hash_password_into(params.alg, password, salt, &[], output) + .map_err(|e| err_msg!("Error deriving key: {}", e)) + } +} + +// FIXME generate into buffer pub fn generate_salt() -> SecretBytes { - random_vec(SALT_SIZE) + random_secret(SALT_SIZE) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn expected() { + let pass = b"my password"; + let salt = b"long enough salt"; + let mut output = [0u8; 32]; + Argon2::derive_key(pass, salt, PARAMS_INTERACTIVE, &mut output).unwrap(); + assert_eq!( + output, + hex!("9ef87bcf828c46c0136a0d1d9e391d713f75b327c6dc190455bd36c1bae33259") + ); + } } diff --git a/askar-keys/src/kdf/concat.rs b/askar-keys/src/kdf/concat.rs new file mode 100644 index 00000000..4f6ba68d --- /dev/null +++ b/askar-keys/src/kdf/concat.rs @@ -0,0 +1,83 @@ +//! ConcatKDF from NIST 800-56ar for ECDH-ES / ECDH-1PU + +use core::marker::PhantomData; + +use digest::{Digest, FixedOutput}; + +use crate::error::Error; + +pub struct ConcatKDF(PhantomData); + +#[derive(Default)] +pub struct Params<'p> { + alg: &'p [u8], + apu: &'p [u8], + apv: &'p [u8], +} + +impl ConcatKDF +where + H: Digest + FixedOutput, +{ + pub fn derive_key( + message: &[u8], + params: Params<'_>, + mut output: &mut [u8], + ) -> Result<(), Error> { + let output_len = output.len(); + if output_len > u32::MAX as usize / 8 { + // output_len is used as SuppPubInfo later + return Err(err_msg!("exceeded max output size for concat KDF")); + } + let mut counter = 1u32; + let mut remain = output_len; + let mut hash = H::new(); + while remain > 0 { + hash.update(counter.to_be_bytes()); + hash.update(message); + hash.update((params.alg.len() as u32).to_be_bytes()); + hash.update(params.alg); + hash.update((params.apu.len() as u32).to_be_bytes()); + hash.update(params.apu); + hash.update((params.apv.len() as u32).to_be_bytes()); + hash.update(params.apv); + hash.update((output_len as u32 * 8).to_be_bytes()); + let hashed = hash.finalize_reset(); + let cp_size = hashed.len().min(remain); + &output[..cp_size].copy_from_slice(&hashed[..cp_size]); + output = &mut output[cp_size..]; + remain -= cp_size; + counter += 1; + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use sha2::Sha256; + + #[test] + fn expected_1pu_output() { + let z = hex!( + "9e56d91d817135d372834283bf84269cfb316ea3da806a48f6daa7798cfe90c4 + e3ca3474384c9f62b30bfd4c688b3e7d4110a1b4badc3cc54ef7b81241efd50d" + ); + let mut output = [0u8; 32]; + ConcatKDF::::derive_key( + &z, + Params { + alg: b"A256GCM", + apu: b"Alice", + apv: b"Bob", + }, + &mut output, + ) + .unwrap(); + assert_eq!( + output, + hex!("6caf13723d14850ad4b42cd6dde935bffd2fff00a9ba70de05c203a5e1722ca7") + ); + } +} diff --git a/askar-keys/src/kdf/mod.rs b/askar-keys/src/kdf/mod.rs index ab978feb..00c77c25 100644 --- a/askar-keys/src/kdf/mod.rs +++ b/askar-keys/src/kdf/mod.rs @@ -1 +1,3 @@ pub mod argon2; + +pub mod concat; diff --git a/askar-keys/src/lib.rs b/askar-keys/src/lib.rs index 3bfaa09e..7ec7a83e 100644 --- a/askar-keys/src/lib.rs +++ b/askar-keys/src/lib.rs @@ -10,6 +10,10 @@ extern crate alloc; #[macro_use] extern crate std; +#[cfg(test)] +#[macro_use] +extern crate hex_literal; + #[macro_use] mod error; diff --git a/askar-keys/src/random.rs b/askar-keys/src/random.rs index 2f5d91e3..43141b84 100644 --- a/askar-keys/src/random.rs +++ b/askar-keys/src/random.rs @@ -32,8 +32,8 @@ pub fn random_deterministic(seed: &GenericArray, len: usize) -> Se SecretBytes::new_with(len, |buf| cipher.apply_keystream(buf)) } -/// Create a new `Vec` instance with random data. +/// Create a new `SecretBytes` instance with random data. #[inline(always)] -pub fn random_vec(len: usize) -> SecretBytes { +pub fn random_secret(len: usize) -> SecretBytes { SecretBytes::new_with(len, fill_random) } From 053d87ccd7ecff292ae9f31827b8dcfbe453dea7 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Fri, 9 Apr 2021 20:33:58 -0700 Subject: [PATCH 007/116] fix jwk representation; ed25519/x25519 Signed-off-by: Andrew Whitehead --- askar-keys/src/alg/ed25519.rs | 365 +++++++++------------- askar-keys/src/alg/mod.rs | 4 +- askar-keys/src/alg/x25519.rs | 200 ++++++------ askar-keys/src/buffer.rs | 2 +- askar-keys/src/error.rs | 14 +- askar-keys/src/jwk/encode.rs | 85 +++++ askar-keys/src/jwk/mod.rs | 110 ++----- askar-keys/src/jwk/ops.rs | 6 + askar-keys/src/jwk/{parse.rs => parts.rs} | 133 ++++++-- 9 files changed, 475 insertions(+), 444 deletions(-) rename askar-keys/src/jwk/{parse.rs => parts.rs} (55%) diff --git a/askar-keys/src/alg/ed25519.rs b/askar-keys/src/alg/ed25519.rs index 5ecd90b1..7c39a0b2 100644 --- a/askar-keys/src/alg/ed25519.rs +++ b/askar-keys/src/alg/ed25519.rs @@ -2,19 +2,18 @@ use alloc::boxed::Box; use core::convert::{TryFrom, TryInto}; use curve25519_dalek::edwards::CompressedEdwardsY; -use ed25519_dalek::{ExpandedSecretKey, PublicKey, SecretKey, Signature, Signer}; +use ed25519_dalek::{ExpandedSecretKey, PublicKey, SecretKey, Signature}; use rand::rngs::OsRng; -use serde_json::json; use sha2::{self, Digest}; use x25519_dalek::{PublicKey as XPublicKey, StaticSecret as XSecretKey}; -use zeroize::Zeroize; use super::x25519::X25519KeyPair; use crate::{ // any::{AnyPrivateKey, AnyPublicKey}, buffer::{SecretBytes, WriteBuffer}, - caps::{KeyAlg, KeyCapSign, KeyCapVerify, SignatureType}, + caps::{KeyCapSign, KeyCapVerify, SignatureType}, error::Error, + jwk::{JwkEncoder, KeyToJwk, KeyToJwkPrivate}, }; // FIXME - check for low-order points when loading public keys? @@ -24,7 +23,7 @@ pub const EDDSA_SIGNATURE_LENGTH: usize = 64; pub const PUBLIC_KEY_LENGTH: usize = 32; pub const SECRET_KEY_LENGTH: usize = 32; -pub const KEYPAIR_LENGTH: usize = 64; +pub const KEYPAIR_LENGTH: usize = SECRET_KEY_LENGTH + PUBLIC_KEY_LENGTH; // FIXME implement debug pub struct Ed25519KeyPair(Box); @@ -35,27 +34,31 @@ impl Ed25519KeyPair { } pub fn from_seed(ikm: &[u8]) -> Result { - Ok(Self::from_secret_key( - SecretKey::from_bytes(ikm).map_err(|_| err_msg!("Invalid key material"))?, - )) + Self::from_secret_key_bytes(ikm) } pub fn from_keypair_bytes(kp: &[u8]) -> Result { - let kp = if kp.len() == 32 { - Self::from_secret_key( - SecretKey::from_bytes(kp).map_err(|_| err_msg!("Invalid keypair bytes"))?, - ) - } else if kp.len() == KEYPAIR_LENGTH { - let sk = SecretKey::from_bytes(kp).map_err(|_| err_msg!("Invalid keypair bytes"))?; - let pk = PublicKey::from_bytes(kp).map_err(|_| err_msg!("Invalid keypair bytes"))?; - Self(Box::new(Keypair { - secret: Some(sk), - public: pk, - })) - } else { + if kp.len() != KEYPAIR_LENGTH { return Err(err_msg!("Invalid keypair bytes")); - }; - Ok(kp) + } + let sk = SecretKey::from_bytes(&kp[..SECRET_KEY_LENGTH]) + .map_err(|_| err_msg!("Invalid keypair bytes"))?; + let pk = PublicKey::from_bytes(&kp[SECRET_KEY_LENGTH..]) + .map_err(|_| err_msg!("Invalid keypair bytes"))?; + // FIXME: derive pk from sk and check value? + + Ok(Self(Box::new(Keypair { + secret: Some(sk), + public: pk, + }))) + } + + pub fn from_secret_key_bytes(key: &[u8]) -> Result { + if key.len() != SECRET_KEY_LENGTH { + return Err(err_msg!("Invalid ed25519 key length")); + } + let sk = SecretKey::from_bytes(key).expect("Error loading ed25519 key"); + Ok(Self::from_secret_key(sk)) } pub(crate) fn from_secret_key(sk: SecretKey) -> Self { @@ -66,32 +69,56 @@ impl Ed25519KeyPair { })) } - pub fn keypair_bytes(&self) -> Option { - SecretBytes::from(self.0.to_bytes().to_vec()) + pub fn from_public_key_bytes(key: &[u8]) -> Result { + if key.len() != PUBLIC_KEY_LENGTH { + return Err(err_msg!("Invalid ed25519 key length")); + } + Ok(Self(Box::new(Keypair { + secret: None, + public: PublicKey::from_bytes(key).map_err(|_| err_msg!("Invalid keypair bytes"))?, + }))) + } + + pub fn to_keypair_bytes(&self) -> Option { + if let Some(secret) = self.0.secret.as_ref() { + let output = SecretBytes::new_with(KEYPAIR_LENGTH, |buf| { + buf[..SECRET_KEY_LENGTH].copy_from_slice(secret.as_bytes()); + buf[SECRET_KEY_LENGTH..].copy_from_slice(self.0.public.as_bytes()); + }); + Some(output) + } else { + None + } } pub fn to_x25519(&self) -> X25519KeyPair { - let hash = sha2::Sha512::digest(&self.0.secret.to_bytes()[..]); - let output: [u8; 32] = (&hash[..32]).try_into().unwrap(); - // clamp result - let secret = XSecretKey::from(output); - let public = XPublicKey::from(&secret); - X25519KeyPair::new(Some(secret), public) - - // pub fn to_x25519(&self) -> X25519PublicKey { - // let public = XPublicKey::from( - // CompressedEdwardsY(self.0.to_bytes()) - // .decompress() - // .unwrap() - // .to_montgomery() - // .to_bytes(), - // ); - // X25519PublicKey(public) - // } + if let Some(secret) = self.0.secret.as_ref() { + let hash = sha2::Sha512::digest(secret.as_bytes()); + // clamp result + let secret = XSecretKey::from(TryInto::<[u8; 32]>::try_into(&hash[..32]).unwrap()); + let public = XPublicKey::from(&secret); + X25519KeyPair::new(Some(secret), public) + } else { + let public = XPublicKey::from( + CompressedEdwardsY(self.0.public.to_bytes()) + .decompress() + .unwrap() + .to_montgomery() + .to_bytes(), + ); + X25519KeyPair::new(None, public) + } + } + + pub fn public_key_bytes(&self) -> [u8; PUBLIC_KEY_LENGTH] { + self.0.public.to_bytes() } - pub fn private_key_bytes(&self) -> Option { - SecretBytes::from(self.0.secret.to_bytes().to_vec()) + pub fn secret_key_bytes(&self) -> Option { + self.0 + .secret + .as_ref() + .map(|sk| SecretBytes::from_slice(sk.as_bytes())) } pub fn signing_key(&self) -> Option> { @@ -101,6 +128,10 @@ impl Ed25519KeyPair { .map(|sk| Ed25519SigningKey(ExpandedSecretKey::from(sk), &self.0.public)) } + pub fn sign(&self, message: &[u8]) -> Option<[u8; EDDSA_SIGNATURE_LENGTH]> { + self.signing_key().map(|sk| sk.sign(message)) + } + pub fn verify_signature(&self, message: &[u8], signature: &[u8]) -> bool { if let Ok(sig) = Signature::try_from(signature) { self.0.public.verify_strict(message, &sig).is_ok() @@ -134,10 +165,10 @@ impl KeyCapSign for Ed25519KeyPair { None | Some(SignatureType::EdDSA) => { if let Some(signer) = self.signing_key() { let sig = signer.sign(data); - out.extend_from_slice(&sig[..]); + out.extend_from_slice(&sig[..])?; Ok(EDDSA_SIGNATURE_LENGTH) } else { - Err(err_msg!(Unsupported, "Undefined private key")) + Err(err_msg!(Unsupported, "Undefined secret key")) } } #[allow(unreachable_patterns)] @@ -161,6 +192,33 @@ impl KeyCapVerify for Ed25519KeyPair { } } +impl KeyToJwk for Ed25519KeyPair { + const KTY: &'static str = "OKP"; + + fn to_jwk_buffer(&self, buffer: &mut JwkEncoder) -> Result<(), Error> { + buffer.add_str("crv", "Ed25519")?; + buffer.add_as_base64("x", &self.public_key_bytes()[..])?; + buffer.add_str("use", "sig")?; + Ok(()) + } +} + +impl KeyToJwkPrivate for Ed25519KeyPair { + fn to_jwk_buffer_private( + &self, + buffer: &mut JwkEncoder, + ) -> Result<(), Error> { + if let Some(sk) = self.0.secret.as_ref() { + buffer.add_str("crv", "Ed25519")?; + buffer.add_as_base64("x", &self.public_key_bytes()[..])?; + buffer.add_as_base64("d", sk.as_bytes())?; + Ok(()) + } else { + self.to_jwk_buffer(buffer) + } + } +} + struct Keypair { // SECURITY: SecretKey zeroizes on drop secret: Option, @@ -189,136 +247,6 @@ impl Ed25519SigningKey<'_> { // } // } -// #[derive(Clone, Debug, PartialEq, Eq)] -// pub struct Ed25519PublicKey(PublicKey); - -// impl Ed25519PublicKey { -// pub fn from_str(key: impl AsRef) -> Result { -// let key = key.as_ref(); -// let key = key.strip_suffix(":ed25519").unwrap_or(key); -// let mut bval = [0u8; 32]; -// bs58::decode(key) -// .into(&mut bval) -// .map_err(|_| err_msg!("Invalid base58 public key"))?; -// Self::from_bytes(bval) -// } - -// pub fn from_bytes(pk: impl AsRef<[u8]>) -> Result { -// let pk = -// PublicKey::from_bytes(pk.as_ref()).map_err(|_| err_msg!("Invalid public key bytes"))?; -// Ok(Self(pk)) -// } - -// pub fn to_base58(&self) -> String { -// bs58::encode(self.to_bytes()).into_string() -// } - -// pub fn to_string(&self) -> String { -// let mut sval = String::with_capacity(64); -// bs58::encode(self.to_bytes()).into(&mut sval).unwrap(); -// sval.push_str(":ed25519"); -// sval -// } - -// pub fn to_bytes(&self) -> [u8; 32] { -// self.0.to_bytes() -// } - -// pub fn to_jwk(&self) -> Result { -// let x = base64::encode_config(self.to_bytes(), base64::URL_SAFE_NO_PAD); -// Ok(json!({ -// "kty": "OKP", -// "crv": "Ed25519", -// "x": x, -// "key_ops": ["verify"] -// })) -// } - -// pub fn to_x25519(&self) -> X25519PublicKey { -// let public = XPublicKey::from( -// CompressedEdwardsY(self.0.to_bytes()) -// .decompress() -// .unwrap() -// .to_montgomery() -// .to_bytes(), -// ); -// X25519PublicKey(public) -// } - -// pub fn verify(&self, message: &[u8], signature: &[u8; EDDSA_SIGNATURE_LENGTH]) -> bool { -// self.0.verify_strict(message, &signature.into()).is_ok() -// } -// } - -// impl KeyCapVerify for Ed25519PublicKey { -// fn key_verify( -// &self, -// data: &[u8], -// signature: &[u8], -// sig_type: Option, -// ) -> Result { -// match sig_type { -// None | Some(SignatureType::EdDSA) => { -// if let Ok(sig) = TryInto::<&[u8; EDDSA_SIGNATURE_LENGTH]>::try_into(signature) { -// Ok(self.verify(data, sig)) -// } else { -// Ok(false) -// } -// } -// #[allow(unreachable_patterns)] -// _ => Err(err_msg!(Unsupported, "Unsupported signature type")), -// } -// } -// } - -// impl TryFrom<&AnyPublicKey> for Ed25519PublicKey { -// type Error = Error; - -// fn try_from(value: &AnyPublicKey) -> Result { -// if value.alg == KeyAlg::Ed25519 { -// Self::from_bytes(&value.data) -// } else { -// Err(err_msg!(Unsupported, "Expected ed25519 key type")) -// } -// } -// } - -// pub(super) fn encode_signature( -// signature: &[u8], -// sig_format: Option, -// ) -> Result, Error> { -// match sig_format { -// None | Some(SignatureFormat::Base58) => Ok(bs58::encode(signature).into_vec()), -// Some(SignatureFormat::Raw) => Ok(signature.to_vec()), -// #[allow(unreachable_patterns)] -// _ => Err(err_msg!(Unsupported, "Unsupported signature format")), -// } -// } - -// pub(super) fn decode_signature( -// sig_input: &[u8], -// sig_output: &mut impl AsMut<[u8]>, -// sig_format: Option, -// ) -> Result<(), Error> { -// match sig_format { -// None | Some(SignatureFormat::Base58) => { -// bs58::decode(sig_input) -// .into(sig_output) -// .map_err(|_| err_msg!("Invalid base58 signature"))?; -// Ok(()) -// } -// Some(SignatureFormat::Raw) => { -// if sig_input.len() != sig_output.as_mut().len() { -// return Err(err_msg!("Invalid raw signature")); -// } -// sig_output.as_mut().copy_from_slice(sig_input); -// Ok(()) -// } -// #[allow(unreachable_patterns)] -// _ => Err(err_msg!(Unsupported, "Unsupported signature format")), -// } -// } - #[cfg(test)] mod tests { use super::*; @@ -326,27 +254,26 @@ mod tests { #[test] fn expand_keypair() { let seed = b"000000000000000000000000Trustee1"; - let test_sk = hex::decode("3030303030303030303030303030303030303030303030305472757374656531e33aaf381fffa6109ad591fdc38717945f8fabf7abf02086ae401c63e9913097").unwrap(); + let test_sk = hex!("3030303030303030303030303030303030303030303030305472757374656531e33aaf381fffa6109ad591fdc38717945f8fabf7abf02086ae401c63e9913097"); let kp = Ed25519KeyPair::from_seed(seed).unwrap(); - assert_eq!(kp.to_bytes(), test_sk); + assert_eq!(kp.to_keypair_bytes().unwrap(), &test_sk[..]); // test round trip - let cmp = Ed25519KeyPair::from_bytes(&test_sk).unwrap(); - assert_eq!(cmp.to_bytes(), test_sk); + let cmp = Ed25519KeyPair::from_keypair_bytes(&test_sk).unwrap(); + assert_eq!(cmp.to_keypair_bytes().unwrap(), &test_sk[..]); } #[test] fn ed25519_to_x25519() { - let test_keypair = hex::decode("1c1179a560d092b90458fe6ab8291215a427fcd6b3927cb240701778ef55201927c96646f2d4632d4fc241f84cbc427fbc3ecaa95becba55088d6c7b81fc5bbf").unwrap(); - let x_sk = hex::decode("08e7286c232ec71b37918533ea0229bf0c75d3db4731df1c5c03c45bc909475f") - .unwrap(); - let x_pk = hex::decode("9b4260484c889158c128796103dc8d8b883977f2ef7efb0facb12b6ca9b2ae3d") - .unwrap(); - let x_pair = Ed25519KeyPair::from_bytes(&test_keypair) + let test_keypair = hex!("1c1179a560d092b90458fe6ab8291215a427fcd6b3927cb240701778ef55201927c96646f2d4632d4fc241f84cbc427fbc3ecaa95becba55088d6c7b81fc5bbf"); + let x_sk = hex!("08e7286c232ec71b37918533ea0229bf0c75d3db4731df1c5c03c45bc909475f"); + let x_pk = hex!("9b4260484c889158c128796103dc8d8b883977f2ef7efb0facb12b6ca9b2ae3d"); + let x_pair = Ed25519KeyPair::from_keypair_bytes(&test_keypair) .unwrap() .to_x25519() - .to_bytes(); + .to_keypair_bytes() + .unwrap(); assert_eq!(&x_pair[..32], x_sk); assert_eq!(&x_pair[32..], x_pk); } @@ -364,46 +291,40 @@ mod tests { // } let test_pvt_b64 = "nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A"; let test_pvt = base64::decode_config(test_pvt_b64, base64::URL_SAFE).unwrap(); - let sk = Ed25519KeyPair::from_bytes(&test_pvt).expect("Error creating signing key"); - let vk = sk.public_key(); - let jwk = vk.to_jwk().expect("Error converting public key to JWK"); - assert_eq!(jwk["kty"], "OKP"); - assert_eq!(jwk["crv"], "Ed25519"); - assert_eq!(jwk["x"], "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"); - assert_eq!( - base64::encode_config(sk.private_key(), base64::URL_SAFE_NO_PAD), - test_pvt_b64 - ); + let kp = + Ed25519KeyPair::from_secret_key_bytes(&test_pvt).expect("Error creating signing key"); + let jwk = kp.to_jwk().expect("Error converting public key to JWK"); + let jwk = jwk.to_parts().expect("Error parsing JWK output"); + assert_eq!(jwk.kty, "OKP"); + assert_eq!(jwk.crv, "Ed25519"); + assert_eq!(jwk.x, "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"); + + let jwk = kp + .to_jwk_private() + .expect("Error converting private key to JWK"); + let jwk = jwk.to_parts().expect("Error parsing JWK output"); + assert_eq!(jwk.kty, "OKP"); + assert_eq!(jwk.crv, "Ed25519"); + assert_eq!(jwk.x, "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"); + assert_eq!(jwk.d, test_pvt_b64); } #[test] fn sign_verify_expected() { let test_msg = b"This is a dummy message for use with tests"; - let test_sig = hex::decode("451b5b8e8725321541954997781de51f4142e4a56bab68d24f6a6b92615de5eefb74134138315859a32c7cf5fe5a488bc545e2e08e5eedfd1fb10188d532d808").unwrap(); - - let test_keypair = hex::decode("1c1179a560d092b90458fe6ab8291215a427fcd6b3927cb240701778ef55201927c96646f2d4632d4fc241f84cbc427fbc3ecaa95becba55088d6c7b81fc5bbf").unwrap(); - let kp = Ed25519KeyPair::from_bytes(&test_keypair).unwrap(); - let sig = kp.sign(&test_msg[..]); - assert_eq!(sig, test_sig.as_slice()); - assert_eq!(kp.public_key().verify(&test_msg[..], sig), true); - assert_eq!(kp.public_key().verify(b"Not the message", sig), false); - assert_eq!(kp.public_key().verify(&test_msg[..], [0u8; 64]), false); - } - - #[test] - fn key_exchange_random() { - let kp1 = X25519KeyPair::generate().unwrap(); - let kp2 = X25519KeyPair::generate().unwrap(); - assert_ne!(kp1.to_bytes(), kp2.to_bytes()); - - let xch1 = kp1.key_exchange_with(&kp2.public_key()); - let xch2 = kp2.key_exchange_with(&kp1.public_key()); - assert_eq!(xch1, xch2); - - // test round trip - let xch3 = X25519KeyPair::from_bytes(&kp1.to_bytes()) - .unwrap() - .key_exchange_with(&kp2.public_key()); - assert_eq!(xch3, xch1); + let test_sig = hex!( + "451b5b8e8725321541954997781de51f4142e4a56bab68d24f6a6b92615de5ee + fb74134138315859a32c7cf5fe5a488bc545e2e08e5eedfd1fb10188d532d808" + ); + let test_keypair = hex!( + "1c1179a560d092b90458fe6ab8291215a427fcd6b3927cb240701778ef552019 + 27c96646f2d4632d4fc241f84cbc427fbc3ecaa95becba55088d6c7b81fc5bbf" + ); + let kp = Ed25519KeyPair::from_keypair_bytes(&test_keypair).unwrap(); + let sig = kp.sign(&test_msg[..]).unwrap(); + assert_eq!(sig, test_sig); + assert_eq!(kp.verify_signature(&test_msg[..], &sig[..]), true); + assert_eq!(kp.verify_signature(b"Not the message", &sig[..]), false); + assert_eq!(kp.verify_signature(&test_msg[..], &[0u8; 64]), false); } } diff --git a/askar-keys/src/alg/mod.rs b/askar-keys/src/alg/mod.rs index 867b2210..2b26ac1e 100644 --- a/askar-keys/src/alg/mod.rs +++ b/askar-keys/src/alg/mod.rs @@ -1,7 +1,7 @@ // pub mod bls; -// pub mod ed25519; -// pub mod x25519; +pub mod ed25519; +pub mod x25519; // pub mod k256; diff --git a/askar-keys/src/alg/x25519.rs b/askar-keys/src/alg/x25519.rs index 1296f277..484b72fc 100644 --- a/askar-keys/src/alg/x25519.rs +++ b/askar-keys/src/alg/x25519.rs @@ -13,6 +13,7 @@ use crate::{ pub const PUBLIC_KEY_LENGTH: usize = 32; pub const SECRET_KEY_LENGTH: usize = 32; +pub const KEYPAIR_LENGTH: usize = SECRET_KEY_LENGTH + PUBLIC_KEY_LENGTH; #[derive(Clone)] // FIXME implement zeroize @@ -21,7 +22,10 @@ pub struct X25519KeyPair(Box); impl X25519KeyPair { #[inline(always)] pub(crate) fn new(sk: Option, pk: PublicKey) -> Self { - Self(Box::new(Keypair { sk, pk })) + Self(Box::new(Keypair { + secret: sk, + public: pk, + })) } pub fn generate() -> Result { @@ -31,15 +35,41 @@ impl X25519KeyPair { } pub fn key_exchange_with(&self, other: &Self) -> Option { - match &self.0.sk { + match self.0.secret.as_ref() { Some(sk) => { - let xk = sk.diffie_hellman(&other.0.pk); + let xk = sk.diffie_hellman(&other.0.public); Some(SecretBytes::from(xk.as_bytes().to_vec())) } None => None, } } + pub fn from_keypair_bytes(kp: &[u8]) -> Result { + if kp.len() != KEYPAIR_LENGTH { + return Err(err_msg!("Invalid keypair bytes")); + } + let sk = SecretKey::from( + TryInto::<[u8; SECRET_KEY_LENGTH]>::try_into(&kp[..SECRET_KEY_LENGTH]).unwrap(), + ); + let pk = PublicKey::from( + TryInto::<[u8; PUBLIC_KEY_LENGTH]>::try_into(&kp[SECRET_KEY_LENGTH..]).unwrap(), + ); + // FIXME: derive pk from sk and check value? + + Ok(Self(Box::new(Keypair { + secret: Some(sk), + public: pk, + }))) + } + + pub(crate) fn from_secret_key(sk: SecretKey) -> Self { + let public = PublicKey::from(&sk); + Self(Box::new(Keypair { + secret: Some(sk), + public, + })) + } + pub fn from_public_key_bytes(key: &[u8]) -> Result { if key.len() != PUBLIC_KEY_LENGTH { return Err(err_msg!("Invalid x25519 key length")); @@ -54,21 +84,46 @@ impl X25519KeyPair { if key.len() != SECRET_KEY_LENGTH { return Err(err_msg!("Invalid x25519 key length")); } + + // pre-check key to ensure that clamping has no effect + if key[0] & 7 != 0 || (key[31] & 127 | 64) != key[31] { + return Err(err_msg!("invalid x25519 secret key")); + } + let sk = SecretKey::from(TryInto::<[u8; SECRET_KEY_LENGTH]>::try_into(key).unwrap()); - let pk = PublicKey::from(&sk); - Ok(Self::new(Some(sk), pk)) + + // post-check key + // let mut check = sk.to_bytes(); + // if &check[..] != key { + // return Err(err_msg!("invalid x25519 secret key")); + // } + // check.zeroize(); + + Ok(Self::from_secret_key(sk)) } pub fn public_key_bytes(&self) -> [u8; PUBLIC_KEY_LENGTH] { - self.0.pk.to_bytes() + self.0.public.to_bytes() } pub fn secret_key_bytes(&self) -> Option { self.0 - .sk + .secret .as_ref() .map(|sk| SecretBytes::from_slice(&sk.to_bytes()[..])) } + + pub fn to_keypair_bytes(&self) -> Option { + if let Some(secret) = self.0.secret.as_ref() { + let output = SecretBytes::new_with(KEYPAIR_LENGTH, |buf| { + buf[..SECRET_KEY_LENGTH].copy_from_slice(&secret.to_bytes()[..]); + buf[SECRET_KEY_LENGTH..].copy_from_slice(self.0.public.as_bytes()); + }); + Some(output) + } else { + None + } + } } impl KeyToJwk for X25519KeyPair { @@ -76,8 +131,8 @@ impl KeyToJwk for X25519KeyPair { fn to_jwk_buffer(&self, buffer: &mut JwkEncoder) -> Result<(), Error> { buffer.add_str("crv", "X25519")?; - buffer.add_as_base58("x", &self.public_key_bytes()[..])?; - buffer.add_str("use", "enc"); + buffer.add_as_base64("x", &self.public_key_bytes()[..])?; + buffer.add_str("use", "enc")?; Ok(()) } } @@ -87,11 +142,11 @@ impl KeyToJwkPrivate for X25519KeyPair { &self, buffer: &mut JwkEncoder, ) -> Result<(), Error> { - if let Some(sk) = self.0.sk.as_ref() { + if let Some(sk) = self.0.secret.as_ref() { let mut sk = sk.to_bytes(); buffer.add_str("crv", "X25519")?; - buffer.add_as_base58("x", &self.public_key_bytes()[..])?; - buffer.add_as_base58("d", &sk[..])?; + buffer.add_as_base64("x", &self.public_key_bytes()[..])?; + buffer.add_as_base64("d", &sk[..])?; sk.zeroize(); Ok(()) } else { @@ -102,8 +157,8 @@ impl KeyToJwkPrivate for X25519KeyPair { #[derive(Clone)] struct Keypair { - sk: Option, - pk: PublicKey, + secret: Option, + public: PublicKey, } // impl TryFrom<&AnyPrivateKey> for X25519KeyPair { @@ -118,116 +173,61 @@ struct Keypair { // } // } -// #[derive(Clone, Debug, PartialEq, Eq)] -// pub struct X25519PublicKey(PublicKey); - -// impl X25519PublicKey { -// pub fn from_str(key: impl AsRef) -> Result { -// let key = key.as_ref(); -// let key = key.strip_suffix(":x25519").unwrap_or(key); -// let mut bval = [0u8; 32]; -// bs58::decode(key) -// .into(&mut bval) -// .map_err(|_| err_msg!("Invalid base58 public key"))?; -// Self::from_bytes(bval) -// } - -// pub fn from_bytes(pk: impl AsRef<[u8]>) -> Result { -// let pk: [u8; 32] = pk -// .as_ref() -// .try_into() -// .map_err(|_| err_msg!("Invalid public key bytes"))?; -// Ok(Self(XPublicKey::from(pk))) -// } - -// pub fn to_base58(&self) -> String { -// bs58::encode(self.to_bytes()).into_string() -// } - -// pub fn to_jwk(&self) -> Result { -// let x = base64::encode_config(self.to_bytes(), base64::URL_SAFE_NO_PAD); -// Ok(json!({ -// "kty": "OKP", -// "crv": "X25519", -// "x": x, -// "key_ops": ["deriveKey"] -// })) -// } - -// pub fn to_string(&self) -> String { -// let mut sval = String::with_capacity(64); -// bs58::encode(self.to_bytes()).into(&mut sval).unwrap(); -// sval.push_str(":x25519"); -// sval -// } - -// pub fn to_bytes(&self) -> [u8; 32] { -// self.0.to_bytes() -// } -// } - -// impl TryFrom<&AnyPublicKey> for X25519PublicKey { -// type Error = Error; - -// fn try_from(value: &AnyPublicKey) -> Result { -// if value.alg == KeyAlg::X25519 { -// Self::from_bytes(&value.data) -// } else { -// Err(err_msg!(Unsupported, "Expected x25519 key type")) -// } -// } -// } - #[cfg(test)] mod tests { use super::*; #[test] fn jwk_expected() { - // from https://www.connect2id.com/blog/nimbus-jose-jwt-6 // { - // "kty" : "OKP", - // "crv" : "Ed25519", - // "x" : "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo", - // "d" : "nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A" - // "use" : "sig", - // "kid" : "FdFYFzERwC2uCBB46pZQi4GG85LujR8obt-KWRBICVQ" - // } - let test_pvt_b64 = "nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A"; + // "kty": "OKP", + // "d": "qL25gw-HkNJC9m4EsRzCoUx1KntjwHPzxo6a2xUcyFQ", + // "use": "enc", + // "crv": "X25519", + // "x": "tGskN_ae61DP4DLY31_fjkbvnKqf-ze7kA6Cj2vyQxU" + // } + let test_pvt_b64 = "qL25gw-HkNJC9m4EsRzCoUx1KntjwHPzxo6a2xUcyFQ"; let test_pvt = base64::decode_config(test_pvt_b64, base64::URL_SAFE).unwrap(); let kp = - X25519KeyPair::from_secret_key_bytes(&test_pvt).expect("Error creating signing key"); + X25519KeyPair::from_secret_key_bytes(&test_pvt).expect("Error creating x25519 keypair"); let jwk = kp.to_jwk().expect("Error converting public key to JWK"); - let jwk = serde_json::to_value(&jwk).expect("Error parsing JWK output"); - assert_eq!(jwk["kty"], "OKP"); - assert_eq!(jwk["crv"], "X25519"); - assert_eq!(jwk["x"], "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"); - assert_eq!(jwk["d"].is_null(), true); + let jwk = jwk.to_parts().expect("Error parsing JWK output"); + assert_eq!(jwk.kty, "OKP"); + assert_eq!(jwk.crv, "X25519"); + assert_eq!(jwk.x, "tGskN_ae61DP4DLY31_fjkbvnKqf-ze7kA6Cj2vyQxU"); + assert_eq!(jwk.d, None); let jwk = kp .to_jwk_private() .expect("Error converting private key to JWK"); - let jwk = serde_json::to_value(&jwk).expect("Error parsing JWK output"); - assert_eq!(jwk["kty"], "OKP"); - assert_eq!(jwk["crv"], "X25519"); - assert_eq!(jwk["x"], "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"); - assert_eq!(jwk["d"], test_pvt_b64); + let jwk = jwk.to_parts().expect("Error parsing JWK output"); + assert_eq!(jwk.kty, "OKP"); + assert_eq!(jwk.crv, "X25519"); + assert_eq!(jwk.x, "tGskN_ae61DP4DLY31_fjkbvnKqf-ze7kA6Cj2vyQxU"); + assert_eq!(jwk.d, test_pvt_b64); } #[test] fn key_exchange_random() { let kp1 = X25519KeyPair::generate().unwrap(); let kp2 = X25519KeyPair::generate().unwrap(); - assert_ne!(kp1.to_jwk(), kp2.to_jwk()); + assert_ne!( + kp1.to_keypair_bytes().unwrap(), + kp2.to_keypair_bytes().unwrap() + ); let xch1 = kp1.key_exchange_with(&kp2); let xch2 = kp2.key_exchange_with(&kp1); assert_eq!(xch1, xch2); + } - // // test round trip - // let xch3 = X25519KeyPair::from_bytes(&kp1.to_bytes()) - // .unwrap() - // .key_exchange_with(&kp2.public_key()); - // assert_eq!(xch3, xch1); + #[test] + fn round_trip_bytes() { + let kp = X25519KeyPair::generate().unwrap(); + let cmp = X25519KeyPair::from_keypair_bytes(&kp.to_keypair_bytes().unwrap()).unwrap(); + assert_eq!( + kp.to_keypair_bytes().unwrap(), + cmp.to_keypair_bytes().unwrap() + ); } } diff --git a/askar-keys/src/buffer.rs b/askar-keys/src/buffer.rs index 8973f9eb..0706e86d 100644 --- a/askar-keys/src/buffer.rs +++ b/askar-keys/src/buffer.rs @@ -452,7 +452,7 @@ mod tests { #[test] fn test_write_vec() { let mut v = Vec::new(); - let mut b = &mut v as &mut dyn WriteBuffer; + let b = &mut v as &mut dyn WriteBuffer; b.extend_from_slice(b"hello").unwrap(); b.truncate_by(3); b.extend_from_slice(b"y").unwrap(); diff --git a/askar-keys/src/error.rs b/askar-keys/src/error.rs index 80ca30a4..ddbc4ed4 100644 --- a/askar-keys/src/error.rs +++ b/askar-keys/src/error.rs @@ -42,6 +42,8 @@ impl Display for ErrorKind { #[derive(Debug)] pub struct Error { pub(crate) kind: ErrorKind, + #[cfg(not(feature = "std"))] + pub(crate) cause: Option, #[cfg(feature = "std")] pub(crate) cause: Option>, pub(crate) message: Option, @@ -51,7 +53,6 @@ impl Error { pub(crate) fn from_msg>(kind: ErrorKind, msg: T) -> Self { Self { kind, - #[cfg(feature = "std")] cause: None, message: Some(msg.into()), } @@ -61,7 +62,6 @@ impl Error { pub(crate) fn from_opt_msg>(kind: ErrorKind, msg: Option) -> Self { Self { kind, - #[cfg(feature = "std")] cause: None, message: msg.map(Into::into), } @@ -72,6 +72,12 @@ impl Error { self.kind } + #[cfg(not(feature = "std"))] + pub(crate) fn with_cause>>(mut self, err: T) -> Self { + self.cause = err.into(); + self + } + #[cfg(feature = "std")] pub(crate) fn with_cause>>(mut self, err: T) -> Self { self.cause = Some(err.into()); @@ -86,7 +92,6 @@ impl Display for Error { } else { f.write_str(self.kind.as_str())?; } - #[cfg(feature = "std")] if let Some(cause) = self.cause.as_ref() { write!(f, "\nCaused by: {}", cause)?; } @@ -113,7 +118,6 @@ impl From for Error { fn from(kind: ErrorKind) -> Self { Self { kind, - #[cfg(feature = "std")] cause: None, message: None, } @@ -145,6 +149,6 @@ macro_rules! err_map { #[cfg(not(feature = "std"))] macro_rules! err_map { ($($params:tt)*) => { - |_| err_msg!($($params)*) + |err| err_msg!($($params)*).with_cause(alloc::string::ToString::to_string(&err)) }; } diff --git a/askar-keys/src/jwk/encode.rs b/askar-keys/src/jwk/encode.rs index e69de29b..79738d46 100644 --- a/askar-keys/src/jwk/encode.rs +++ b/askar-keys/src/jwk/encode.rs @@ -0,0 +1,85 @@ +use crate::{buffer::WriteBuffer, error::Error}; + +use super::ops::KeyOpsSet; + +struct JwkBuffer<'s, B>(&'s mut B); + +impl bs58::encode::EncodeTarget for JwkBuffer<'_, B> { + fn encode_with( + &mut self, + max_len: usize, + f: impl for<'a> FnOnce(&'a mut [u8]) -> Result, + ) -> Result { + if let Some(ext) = self.0.extend_buffer(max_len) { + let len = f(ext)?; + if len < max_len { + self.0.truncate_by(max_len - len); + } + Ok(len) + } else { + Err(bs58::encode::Error::BufferTooSmall) + } + } +} + +pub struct JwkEncoder<'b, B: WriteBuffer> { + buffer: &'b mut B, +} + +impl<'b, B: WriteBuffer> JwkEncoder<'b, B> { + pub fn new(buffer: &'b mut B, kty: &str) -> Result { + buffer.extend_from_slice(b"{\"kty\":\"")?; + buffer.extend_from_slice(kty.as_bytes())?; + buffer.extend_from_slice(b"\"")?; + Ok(Self { buffer }) + } + + pub fn add_str(&mut self, key: &str, value: &str) -> Result<(), Error> { + let buffer = &mut *self.buffer; + buffer.extend_from_slice(b",\"")?; + buffer.extend_from_slice(key.as_bytes())?; + buffer.extend_from_slice(b"\":\"")?; + buffer.extend_from_slice(value.as_bytes())?; + buffer.extend_from_slice(b"\"")?; + Ok(()) + } + + pub fn add_as_base64(&mut self, key: &str, value: &[u8]) -> Result<(), Error> { + let buffer = &mut *self.buffer; + buffer.extend_from_slice(b",\"")?; + buffer.extend_from_slice(key.as_bytes())?; + buffer.extend_from_slice(b"\":\"")?; + let enc_size = ((value.len() << 2) + 2) / 3; + if let Some(mbuf) = buffer.extend_buffer(enc_size) { + let len = base64::encode_config_slice(value, base64::URL_SAFE_NO_PAD, mbuf); + if len < enc_size { + buffer.truncate_by(enc_size - len); + } + } else { + return Err(err_msg!("buffer too small")); + } + buffer.extend_from_slice(b"\"")?; + Ok(()) + } + + pub fn add_key_ops(&mut self, ops: impl Into) -> Result<(), Error> { + let buffer = &mut *self.buffer; + buffer.extend_from_slice(b",\"key_ops\":[")?; + for (idx, op) in ops.into().into_iter().enumerate() { + if idx > 0 { + buffer.extend_from_slice(b",\"")?; + } else { + buffer.extend_from_slice(b"\"")?; + } + buffer.extend_from_slice(op.as_str().as_bytes())?; + buffer.extend_from_slice(b"\"")?; + } + buffer.extend_from_slice(b"]")?; + Ok(()) + } + + pub fn finalize(self) -> Result<(), Error> { + self.buffer.extend_from_slice(b"}")?; + Ok(()) + } +} diff --git a/askar-keys/src/jwk/mod.rs b/askar-keys/src/jwk/mod.rs index 56b624b8..442b420f 100644 --- a/askar-keys/src/jwk/mod.rs +++ b/askar-keys/src/jwk/mod.rs @@ -4,11 +4,14 @@ use zeroize::Zeroize; use crate::{buffer::WriteBuffer, error::Error}; +mod encode; +pub use encode::JwkEncoder; + mod ops; pub use self::ops::{KeyOps, KeyOpsSet}; -mod parse; -pub use self::parse::JwkParts; +mod parts; +pub use self::parts::JwkParts; #[derive(Clone, Debug)] pub enum Jwk<'a> { @@ -17,12 +20,19 @@ pub enum Jwk<'a> { } impl Jwk<'_> { - pub fn to_parts(&self) -> Result, Error> { + pub fn to_parts(&self) -> Result, Error> { + match self { + Self::Encoded(s) => Ok( + serde_json::from_str(s.as_ref()).map_err(err_map!("Error deserializing JWK"))? + ), + Self::Parts(p) => Ok(*p), + } + } + + pub fn as_opt_str(&self) -> Option<&str> { match self { - Self::Encoded(s) => Ok(Jwk::Parts( - serde_json::from_str(s.as_ref()).map_err(err_map!("Error deserializing JWK"))?, - )), - Self::Parts(p) => Ok(Jwk::Parts(*p)), + Self::Encoded(s) => Some(s.as_ref()), + Self::Parts(_) => None, } } } @@ -37,101 +47,27 @@ impl Zeroize for Jwk<'_> { } } -struct JwkBuffer<'s, B>(&'s mut B); - -impl bs58::encode::EncodeTarget for JwkBuffer<'_, B> { - fn encode_with( - &mut self, - max_len: usize, - f: impl for<'a> FnOnce(&'a mut [u8]) -> Result, - ) -> Result { - if let Some(ext) = self.0.extend_buffer(max_len) { - let len = f(ext)?; - if len < max_len { - self.0.truncate_by(max_len - len); - } - Ok(len) - } else { - Err(bs58::encode::Error::BufferTooSmall) - } - } -} - -pub struct JwkEncoder<'b, B: WriteBuffer> { - buffer: &'b mut B, -} - -impl<'b, B: WriteBuffer> JwkEncoder<'b, B> { - pub fn new(buffer: &'b mut B, kty: &str) -> Result { - buffer.extend_from_slice(b"{\"kty\":\"")?; - buffer.extend_from_slice(kty.as_bytes())?; - buffer.extend_from_slice(b"\"")?; - Ok(Self { buffer }) - } - - pub fn add_str(&mut self, key: &str, value: &str) -> Result<(), Error> { - let buffer = &mut *self.buffer; - buffer.extend_from_slice(b",\"")?; - buffer.extend_from_slice(key.as_bytes())?; - buffer.extend_from_slice(b"\":\"")?; - buffer.extend_from_slice(value.as_bytes())?; - buffer.extend_from_slice(b"\"")?; - Ok(()) - } - - pub fn add_as_base58(&mut self, key: &str, value: &[u8]) -> Result<(), Error> { - let buffer = &mut *self.buffer; - buffer.extend_from_slice(b",\"")?; - buffer.extend_from_slice(key.as_bytes())?; - buffer.extend_from_slice(b"\":\"")?; - bs58::encode(value) - .into(JwkBuffer(buffer)) - .map_err(|_| err_msg!("buffer too small"))?; - buffer.extend_from_slice(b"\"")?; - Ok(()) - } - - pub fn add_key_ops(&mut self, ops: impl Into) -> Result<(), Error> { - let buffer = &mut *self.buffer; - buffer.extend_from_slice(b",\"key_ops\":[")?; - for (idx, op) in ops.into().into_iter().enumerate() { - if idx > 0 { - buffer.extend_from_slice(b",\"")?; - } else { - buffer.extend_from_slice(b"\"")?; - } - buffer.extend_from_slice(op.as_str().as_bytes())?; - buffer.extend_from_slice(b"\"")?; - } - buffer.extend_from_slice(b"]")?; - Ok(()) - } - - pub fn finalize(self) -> Result<(), Error> { - self.buffer.extend_from_slice(b"}")?; - Ok(()) - } -} - pub trait KeyToJwk { const KTY: &'static str; - fn to_jwk(&self) -> Result { + fn to_jwk(&self) -> Result, Error> { let mut v = Vec::with_capacity(128); let mut buf = JwkEncoder::new(&mut v, Self::KTY)?; self.to_jwk_buffer(&mut buf)?; - Ok(String::from_utf8(v).unwrap()) + buf.finalize()?; + Ok(Jwk::Encoded(Cow::Owned(String::from_utf8(v).unwrap()))) } fn to_jwk_buffer(&self, buffer: &mut JwkEncoder) -> Result<(), Error>; } pub trait KeyToJwkPrivate: KeyToJwk { - fn to_jwk_private(&self) -> Result { + fn to_jwk_private(&self) -> Result, Error> { let mut v = Vec::with_capacity(128); let mut buf = JwkEncoder::new(&mut v, Self::KTY)?; self.to_jwk_buffer_private(&mut buf)?; - Ok(String::from_utf8(v).unwrap()) + buf.finalize()?; + Ok(Jwk::Encoded(Cow::Owned(String::from_utf8(v).unwrap()))) } fn to_jwk_buffer_private( diff --git a/askar-keys/src/jwk/ops.rs b/askar-keys/src/jwk/ops.rs index e4af4082..70e8b3e3 100644 --- a/askar-keys/src/jwk/ops.rs +++ b/askar-keys/src/jwk/ops.rs @@ -88,6 +88,12 @@ impl KeyOpsSet { } } +impl Default for KeyOpsSet { + fn default() -> Self { + Self::new() + } +} + impl BitOr for KeyOpsSet { type Output = Self; diff --git a/askar-keys/src/jwk/parse.rs b/askar-keys/src/jwk/parts.rs similarity index 55% rename from askar-keys/src/jwk/parse.rs rename to askar-keys/src/jwk/parts.rs index 516442e0..82b60365 100644 --- a/askar-keys/src/jwk/parse.rs +++ b/askar-keys/src/jwk/parts.rs @@ -1,4 +1,8 @@ -use core::{fmt, marker::PhantomData}; +use core::{ + fmt::{self, Debug, Formatter}, + marker::PhantomData, + ops::Deref, +}; use serde::de::{Deserialize, Deserializer, MapAccess, SeqAccess, Visitor}; @@ -7,21 +11,85 @@ use super::ops::{KeyOps, KeyOpsSet}; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct JwkParts<'a> { // key type - kty: &'a str, + pub kty: &'a str, // key ID - kid: Option<&'a str>, + pub kid: OptStr<'a>, // curve type - crv: Option<&'a str>, + pub crv: OptStr<'a>, // curve key public y coordinate - x: Option<&'a str>, + pub x: OptStr<'a>, // curve key public y coordinate - y: Option<&'a str>, + pub y: OptStr<'a>, // curve key private key bytes - d: Option<&'a str>, + pub d: OptStr<'a>, // used by symmetric keys like AES - k: Option<&'a str>, + pub k: OptStr<'a>, // recognized key operations - key_ops: Option, + pub key_ops: Option, +} + +#[derive(Copy, Clone, Default, PartialEq, Eq)] +#[repr(transparent)] +pub struct OptStr<'a>(Option<&'a str>); + +impl OptStr<'_> { + pub fn is_none(&self) -> bool { + self.0.is_none() + } + + pub fn to_option(&self) -> Option<&str> { + self.0 + } +} + +impl AsRef<[u8]> for OptStr<'_> { + fn as_ref(&self) -> &[u8] { + self.0.unwrap_or_default().as_bytes() + } +} + +impl Debug for OptStr<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self.0 { + None => f.write_str("None"), + Some(s) => f.write_fmt(format_args!("{:?}", s)), + } + } +} + +impl Deref for OptStr<'_> { + type Target = str; + + fn deref(&self) -> &Self::Target { + self.0.unwrap_or_default() + } +} + +impl<'o> From<&'o str> for OptStr<'o> { + fn from(s: &'o str) -> Self { + Self(Some(s)) + } +} + +impl<'o> From> for OptStr<'o> { + fn from(s: Option<&'o str>) -> Self { + Self(s) + } +} + +impl PartialEq> for OptStr<'_> { + fn eq(&self, other: &Option<&str>) -> bool { + self.0 == *other + } +} + +impl PartialEq<&str> for OptStr<'_> { + fn eq(&self, other: &&str) -> bool { + match self.0 { + None => false, + Some(s) => (*other) == s, + } + } } struct JwkMapVisitor<'de>(PhantomData<&'de ()>); @@ -55,6 +123,18 @@ impl<'de> Visitor<'de> for JwkMapVisitor<'de> { "y" => y = Some(access.next_value()?), "d" => d = Some(access.next_value()?), "k" => k = Some(access.next_value()?), + "use" => { + let ops = match access.next_value()? { + "enc" => { + KeyOps::Encrypt | KeyOps::Decrypt | KeyOps::WrapKey | KeyOps::UnwrapKey + } + "sig" => KeyOps::Sign | KeyOps::Verify, + _ => KeyOpsSet::new(), + }; + if !ops.is_empty() { + key_ops = Some(key_ops.unwrap_or_default() | ops); + } + } "key_ops" => key_ops = Some(access.next_value()?), _ => (), } @@ -63,12 +143,12 @@ impl<'de> Visitor<'de> for JwkMapVisitor<'de> { if let Some(kty) = kty { Ok(JwkParts { kty, - kid, - crv, - x, - y, - d, - k, + kid: OptStr::from(kid), + crv: OptStr::from(crv), + x: OptStr::from(x), + y: OptStr::from(y), + d: OptStr::from(d), + k: OptStr::from(k), key_ops, }) } else { @@ -139,18 +219,17 @@ mod tests { "key_ops": ["sign", "verify"], "kid": "FdFYFzERwC2uCBB46pZQi4GG85LujR8obt-KWRBICVQ" }"#; + let parts = serde_json::from_str::(jwk).unwrap(); + assert_eq!(parts.kty, "OKP"); assert_eq!( - serde_json::from_str::(jwk).unwrap(), - JwkParts { - kty: "OKP", - kid: Some("FdFYFzERwC2uCBB46pZQi4GG85LujR8obt-KWRBICVQ"), - crv: Some("Ed25519"), - x: Some("11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"), - y: None, - d: Some("nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A"), - k: None, - key_ops: Some(KeyOps::Sign | KeyOps::Verify) - } - ) + parts.kid, + Some("FdFYFzERwC2uCBB46pZQi4GG85LujR8obt-KWRBICVQ") + ); + assert_eq!(parts.crv, Some("Ed25519")); + assert_eq!(parts.x, Some("11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo")); + assert_eq!(parts.y, None); + assert_eq!(parts.d, Some("nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A")); + assert_eq!(parts.k, None); + assert_eq!(parts.key_ops, Some(KeyOps::Sign | KeyOps::Verify)); } } From 7e562a2f7a1ffc9bf2dc5b789a105ee9fee49951 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Fri, 9 Apr 2021 20:36:28 -0700 Subject: [PATCH 008/116] askar-keys -> askar-crypto Signed-off-by: Andrew Whitehead --- {askar-keys => askar-crypto}/Cargo.toml | 2 +- {askar-keys => askar-crypto}/src/alg/bls.rs | 0 {askar-keys => askar-crypto}/src/alg/ed25519.rs | 0 {askar-keys => askar-crypto}/src/alg/k256.rs | 0 {askar-keys => askar-crypto}/src/alg/mod.rs | 0 {askar-keys => askar-crypto}/src/alg/p256.rs | 0 {askar-keys => askar-crypto}/src/alg/x25519.rs | 0 {askar-keys => askar-crypto}/src/any.rs | 0 {askar-keys => askar-crypto}/src/buffer.rs | 0 {askar-keys => askar-crypto}/src/caps.rs | 0 {askar-keys => askar-crypto}/src/encrypt.rs | 0 {askar-keys => askar-crypto}/src/error.rs | 0 {askar-keys => askar-crypto}/src/jwk/encode.rs | 0 {askar-keys => askar-crypto}/src/jwk/mod.rs | 0 {askar-keys => askar-crypto}/src/jwk/ops.rs | 0 {askar-keys => askar-crypto}/src/jwk/parts.rs | 0 {askar-keys => askar-crypto}/src/kdf/argon2.rs | 0 {askar-keys => askar-crypto}/src/kdf/concat.rs | 0 {askar-keys => askar-crypto}/src/kdf/mod.rs | 0 {askar-keys => askar-crypto}/src/lib.rs | 0 {askar-keys => askar-crypto}/src/pack/alg.rs | 0 {askar-keys => askar-crypto}/src/pack/mod.rs | 0 {askar-keys => askar-crypto}/src/pack/nacl_box.rs | 0 {askar-keys => askar-crypto}/src/random.rs | 0 {askar-keys => askar-crypto}/src/serde_utils.rs | 0 {askar-keys => askar-crypto}/src/store.rs | 0 26 files changed, 1 insertion(+), 1 deletion(-) rename {askar-keys => askar-crypto}/Cargo.toml (98%) rename {askar-keys => askar-crypto}/src/alg/bls.rs (100%) rename {askar-keys => askar-crypto}/src/alg/ed25519.rs (100%) rename {askar-keys => askar-crypto}/src/alg/k256.rs (100%) rename {askar-keys => askar-crypto}/src/alg/mod.rs (100%) rename {askar-keys => askar-crypto}/src/alg/p256.rs (100%) rename {askar-keys => askar-crypto}/src/alg/x25519.rs (100%) rename {askar-keys => askar-crypto}/src/any.rs (100%) rename {askar-keys => askar-crypto}/src/buffer.rs (100%) rename {askar-keys => askar-crypto}/src/caps.rs (100%) rename {askar-keys => askar-crypto}/src/encrypt.rs (100%) rename {askar-keys => askar-crypto}/src/error.rs (100%) rename {askar-keys => askar-crypto}/src/jwk/encode.rs (100%) rename {askar-keys => askar-crypto}/src/jwk/mod.rs (100%) rename {askar-keys => askar-crypto}/src/jwk/ops.rs (100%) rename {askar-keys => askar-crypto}/src/jwk/parts.rs (100%) rename {askar-keys => askar-crypto}/src/kdf/argon2.rs (100%) rename {askar-keys => askar-crypto}/src/kdf/concat.rs (100%) rename {askar-keys => askar-crypto}/src/kdf/mod.rs (100%) rename {askar-keys => askar-crypto}/src/lib.rs (100%) rename {askar-keys => askar-crypto}/src/pack/alg.rs (100%) rename {askar-keys => askar-crypto}/src/pack/mod.rs (100%) rename {askar-keys => askar-crypto}/src/pack/nacl_box.rs (100%) rename {askar-keys => askar-crypto}/src/random.rs (100%) rename {askar-keys => askar-crypto}/src/serde_utils.rs (100%) rename {askar-keys => askar-crypto}/src/store.rs (100%) diff --git a/askar-keys/Cargo.toml b/askar-crypto/Cargo.toml similarity index 98% rename from askar-keys/Cargo.toml rename to askar-crypto/Cargo.toml index 488afc71..5877597c 100644 --- a/askar-keys/Cargo.toml +++ b/askar-crypto/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "askar-keys" +name = "askar-crypto" version = "0.1.0" authors = ["Hyperledger Aries Contributors "] edition = "2018" diff --git a/askar-keys/src/alg/bls.rs b/askar-crypto/src/alg/bls.rs similarity index 100% rename from askar-keys/src/alg/bls.rs rename to askar-crypto/src/alg/bls.rs diff --git a/askar-keys/src/alg/ed25519.rs b/askar-crypto/src/alg/ed25519.rs similarity index 100% rename from askar-keys/src/alg/ed25519.rs rename to askar-crypto/src/alg/ed25519.rs diff --git a/askar-keys/src/alg/k256.rs b/askar-crypto/src/alg/k256.rs similarity index 100% rename from askar-keys/src/alg/k256.rs rename to askar-crypto/src/alg/k256.rs diff --git a/askar-keys/src/alg/mod.rs b/askar-crypto/src/alg/mod.rs similarity index 100% rename from askar-keys/src/alg/mod.rs rename to askar-crypto/src/alg/mod.rs diff --git a/askar-keys/src/alg/p256.rs b/askar-crypto/src/alg/p256.rs similarity index 100% rename from askar-keys/src/alg/p256.rs rename to askar-crypto/src/alg/p256.rs diff --git a/askar-keys/src/alg/x25519.rs b/askar-crypto/src/alg/x25519.rs similarity index 100% rename from askar-keys/src/alg/x25519.rs rename to askar-crypto/src/alg/x25519.rs diff --git a/askar-keys/src/any.rs b/askar-crypto/src/any.rs similarity index 100% rename from askar-keys/src/any.rs rename to askar-crypto/src/any.rs diff --git a/askar-keys/src/buffer.rs b/askar-crypto/src/buffer.rs similarity index 100% rename from askar-keys/src/buffer.rs rename to askar-crypto/src/buffer.rs diff --git a/askar-keys/src/caps.rs b/askar-crypto/src/caps.rs similarity index 100% rename from askar-keys/src/caps.rs rename to askar-crypto/src/caps.rs diff --git a/askar-keys/src/encrypt.rs b/askar-crypto/src/encrypt.rs similarity index 100% rename from askar-keys/src/encrypt.rs rename to askar-crypto/src/encrypt.rs diff --git a/askar-keys/src/error.rs b/askar-crypto/src/error.rs similarity index 100% rename from askar-keys/src/error.rs rename to askar-crypto/src/error.rs diff --git a/askar-keys/src/jwk/encode.rs b/askar-crypto/src/jwk/encode.rs similarity index 100% rename from askar-keys/src/jwk/encode.rs rename to askar-crypto/src/jwk/encode.rs diff --git a/askar-keys/src/jwk/mod.rs b/askar-crypto/src/jwk/mod.rs similarity index 100% rename from askar-keys/src/jwk/mod.rs rename to askar-crypto/src/jwk/mod.rs diff --git a/askar-keys/src/jwk/ops.rs b/askar-crypto/src/jwk/ops.rs similarity index 100% rename from askar-keys/src/jwk/ops.rs rename to askar-crypto/src/jwk/ops.rs diff --git a/askar-keys/src/jwk/parts.rs b/askar-crypto/src/jwk/parts.rs similarity index 100% rename from askar-keys/src/jwk/parts.rs rename to askar-crypto/src/jwk/parts.rs diff --git a/askar-keys/src/kdf/argon2.rs b/askar-crypto/src/kdf/argon2.rs similarity index 100% rename from askar-keys/src/kdf/argon2.rs rename to askar-crypto/src/kdf/argon2.rs diff --git a/askar-keys/src/kdf/concat.rs b/askar-crypto/src/kdf/concat.rs similarity index 100% rename from askar-keys/src/kdf/concat.rs rename to askar-crypto/src/kdf/concat.rs diff --git a/askar-keys/src/kdf/mod.rs b/askar-crypto/src/kdf/mod.rs similarity index 100% rename from askar-keys/src/kdf/mod.rs rename to askar-crypto/src/kdf/mod.rs diff --git a/askar-keys/src/lib.rs b/askar-crypto/src/lib.rs similarity index 100% rename from askar-keys/src/lib.rs rename to askar-crypto/src/lib.rs diff --git a/askar-keys/src/pack/alg.rs b/askar-crypto/src/pack/alg.rs similarity index 100% rename from askar-keys/src/pack/alg.rs rename to askar-crypto/src/pack/alg.rs diff --git a/askar-keys/src/pack/mod.rs b/askar-crypto/src/pack/mod.rs similarity index 100% rename from askar-keys/src/pack/mod.rs rename to askar-crypto/src/pack/mod.rs diff --git a/askar-keys/src/pack/nacl_box.rs b/askar-crypto/src/pack/nacl_box.rs similarity index 100% rename from askar-keys/src/pack/nacl_box.rs rename to askar-crypto/src/pack/nacl_box.rs diff --git a/askar-keys/src/random.rs b/askar-crypto/src/random.rs similarity index 100% rename from askar-keys/src/random.rs rename to askar-crypto/src/random.rs diff --git a/askar-keys/src/serde_utils.rs b/askar-crypto/src/serde_utils.rs similarity index 100% rename from askar-keys/src/serde_utils.rs rename to askar-crypto/src/serde_utils.rs diff --git a/askar-keys/src/store.rs b/askar-crypto/src/store.rs similarity index 100% rename from askar-keys/src/store.rs rename to askar-crypto/src/store.rs From fdffa51b92fd5d17bd622145aed315ef1e162a46 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Fri, 9 Apr 2021 23:13:30 -0700 Subject: [PATCH 009/116] update k256 to use combined keypair; misc cleanups Signed-off-by: Andrew Whitehead --- askar-crypto/src/alg/ed25519.rs | 93 ++++--- askar-crypto/src/alg/k256.rs | 434 ++++++++++++-------------------- askar-crypto/src/alg/mod.rs | 2 +- askar-crypto/src/alg/x25519.rs | 17 +- askar-crypto/src/jwk/mod.rs | 12 +- 5 files changed, 231 insertions(+), 327 deletions(-) diff --git a/askar-crypto/src/alg/ed25519.rs b/askar-crypto/src/alg/ed25519.rs index 7c39a0b2..369e045f 100644 --- a/askar-crypto/src/alg/ed25519.rs +++ b/askar-crypto/src/alg/ed25519.rs @@ -13,7 +13,7 @@ use crate::{ buffer::{SecretBytes, WriteBuffer}, caps::{KeyCapSign, KeyCapVerify, SignatureType}, error::Error, - jwk::{JwkEncoder, KeyToJwk, KeyToJwkPrivate}, + jwk::{JwkEncoder, KeyToJwk, KeyToJwkSecret}, }; // FIXME - check for low-order points when loading public keys? @@ -42,9 +42,9 @@ impl Ed25519KeyPair { return Err(err_msg!("Invalid keypair bytes")); } let sk = SecretKey::from_bytes(&kp[..SECRET_KEY_LENGTH]) - .map_err(|_| err_msg!("Invalid keypair bytes"))?; + .map_err(|_| err_msg!("Invalid ed25519 secret key bytes"))?; let pk = PublicKey::from_bytes(&kp[SECRET_KEY_LENGTH..]) - .map_err(|_| err_msg!("Invalid keypair bytes"))?; + .map_err(|_| err_msg!("Invalid ed25519 public key bytes"))?; // FIXME: derive pk from sk and check value? Ok(Self(Box::new(Keypair { @@ -53,14 +53,25 @@ impl Ed25519KeyPair { }))) } + pub fn from_public_key_bytes(key: &[u8]) -> Result { + if key.len() != PUBLIC_KEY_LENGTH { + return Err(err_msg!("Invalid ed25519 public key length")); + } + Ok(Self(Box::new(Keypair { + secret: None, + public: PublicKey::from_bytes(key).map_err(|_| err_msg!("Invalid keypair bytes"))?, + }))) + } + pub fn from_secret_key_bytes(key: &[u8]) -> Result { if key.len() != SECRET_KEY_LENGTH { - return Err(err_msg!("Invalid ed25519 key length")); + return Err(err_msg!("Invalid ed25519 secret key length")); } let sk = SecretKey::from_bytes(key).expect("Error loading ed25519 key"); Ok(Self::from_secret_key(sk)) } + #[inline] pub(crate) fn from_secret_key(sk: SecretKey) -> Self { let public = PublicKey::from(&sk); Self(Box::new(Keypair { @@ -69,16 +80,6 @@ impl Ed25519KeyPair { })) } - pub fn from_public_key_bytes(key: &[u8]) -> Result { - if key.len() != PUBLIC_KEY_LENGTH { - return Err(err_msg!("Invalid ed25519 key length")); - } - Ok(Self(Box::new(Keypair { - secret: None, - public: PublicKey::from_bytes(key).map_err(|_| err_msg!("Invalid keypair bytes"))?, - }))) - } - pub fn to_keypair_bytes(&self) -> Option { if let Some(secret) = self.0.secret.as_ref() { let output = SecretBytes::new_with(KEYPAIR_LENGTH, |buf| { @@ -91,7 +92,25 @@ impl Ed25519KeyPair { } } - pub fn to_x25519(&self) -> X25519KeyPair { + pub fn to_public_key_bytes(&self) -> [u8; PUBLIC_KEY_LENGTH] { + self.0.public.to_bytes() + } + + pub fn to_secret_key_bytes(&self) -> Option { + self.0 + .secret + .as_ref() + .map(|sk| SecretBytes::from_slice(sk.as_bytes())) + } + + pub fn to_signing_key(&self) -> Option> { + self.0 + .secret + .as_ref() + .map(|sk| Ed25519SigningKey(ExpandedSecretKey::from(sk), &self.0.public)) + } + + pub fn to_x25519_keypair(&self) -> X25519KeyPair { if let Some(secret) = self.0.secret.as_ref() { let hash = sha2::Sha512::digest(secret.as_bytes()); // clamp result @@ -110,26 +129,8 @@ impl Ed25519KeyPair { } } - pub fn public_key_bytes(&self) -> [u8; PUBLIC_KEY_LENGTH] { - self.0.public.to_bytes() - } - - pub fn secret_key_bytes(&self) -> Option { - self.0 - .secret - .as_ref() - .map(|sk| SecretBytes::from_slice(sk.as_bytes())) - } - - pub fn signing_key(&self) -> Option> { - self.0 - .secret - .as_ref() - .map(|sk| Ed25519SigningKey(ExpandedSecretKey::from(sk), &self.0.public)) - } - pub fn sign(&self, message: &[u8]) -> Option<[u8; EDDSA_SIGNATURE_LENGTH]> { - self.signing_key().map(|sk| sk.sign(message)) + self.to_signing_key().map(|sk| sk.sign(message)) } pub fn verify_signature(&self, message: &[u8], signature: &[u8]) -> bool { @@ -163,7 +164,7 @@ impl KeyCapSign for Ed25519KeyPair { ) -> Result { match sig_type { None | Some(SignatureType::EdDSA) => { - if let Some(signer) = self.signing_key() { + if let Some(signer) = self.to_signing_key() { let sig = signer.sign(data); out.extend_from_slice(&sig[..])?; Ok(EDDSA_SIGNATURE_LENGTH) @@ -197,20 +198,20 @@ impl KeyToJwk for Ed25519KeyPair { fn to_jwk_buffer(&self, buffer: &mut JwkEncoder) -> Result<(), Error> { buffer.add_str("crv", "Ed25519")?; - buffer.add_as_base64("x", &self.public_key_bytes()[..])?; + buffer.add_as_base64("x", &self.to_public_key_bytes()[..])?; buffer.add_str("use", "sig")?; Ok(()) } } -impl KeyToJwkPrivate for Ed25519KeyPair { - fn to_jwk_buffer_private( +impl KeyToJwkSecret for Ed25519KeyPair { + fn to_jwk_buffer_secret( &self, buffer: &mut JwkEncoder, ) -> Result<(), Error> { if let Some(sk) = self.0.secret.as_ref() { buffer.add_str("crv", "Ed25519")?; - buffer.add_as_base64("x", &self.public_key_bytes()[..])?; + buffer.add_as_base64("x", &self.to_public_key_bytes()[..])?; buffer.add_as_base64("d", sk.as_bytes())?; Ok(()) } else { @@ -271,7 +272,7 @@ mod tests { let x_pk = hex!("9b4260484c889158c128796103dc8d8b883977f2ef7efb0facb12b6ca9b2ae3d"); let x_pair = Ed25519KeyPair::from_keypair_bytes(&test_keypair) .unwrap() - .to_x25519() + .to_x25519_keypair() .to_keypair_bytes() .unwrap(); assert_eq!(&x_pair[..32], x_sk); @@ -300,7 +301,7 @@ mod tests { assert_eq!(jwk.x, "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"); let jwk = kp - .to_jwk_private() + .to_jwk_secret() .expect("Error converting private key to JWK"); let jwk = jwk.to_parts().expect("Error parsing JWK output"); assert_eq!(jwk.kty, "OKP"); @@ -327,4 +328,14 @@ mod tests { assert_eq!(kp.verify_signature(b"Not the message", &sig[..]), false); assert_eq!(kp.verify_signature(&test_msg[..], &[0u8; 64]), false); } + + #[test] + fn round_trip_bytes() { + let kp = Ed25519KeyPair::generate().unwrap(); + let cmp = Ed25519KeyPair::from_keypair_bytes(&kp.to_keypair_bytes().unwrap()).unwrap(); + assert_eq!( + kp.to_keypair_bytes().unwrap(), + cmp.to_keypair_bytes().unwrap() + ); + } } diff --git a/askar-crypto/src/alg/k256.rs b/askar-crypto/src/alg/k256.rs index b0469cb7..c7e4f074 100644 --- a/askar-crypto/src/alg/k256.rs +++ b/askar-crypto/src/alg/k256.rs @@ -1,65 +1,120 @@ -use core::convert::TryInto; +use alloc::boxed::Box; +use core::convert::{TryFrom, TryInto}; use k256::{ ecdsa::{ signature::{Signer, Verifier}, Signature, SigningKey, VerifyingKey, }, - elliptic_curve::{ - ecdh::diffie_hellman, - sec1::{Coordinates, FromEncodedPoint}, - }, + elliptic_curve::{ecdh::diffie_hellman, sec1::Coordinates}, EncodedPoint, PublicKey, SecretKey, }; use rand::rngs::OsRng; -use serde_json::json; use crate::{ // any::{AnyPrivateKey, AnyPublicKey}, buffer::{SecretBytes, WriteBuffer}, - caps::{EcCurves, KeyAlg, KeyCapSign, KeyCapVerify, SignatureFormat, SignatureType}, + caps::{KeyCapSign, KeyCapVerify, SignatureType}, error::Error, + jwk::{JwkEncoder, KeyToJwk, KeyToJwkSecret}, }; pub const ES256K_SIGNATURE_LENGTH: usize = 64; +pub const PUBLIC_KEY_LENGTH: usize = 33; // compressed size +pub const SECRET_KEY_LENGTH: usize = 32; +pub const KEYPAIR_LENGTH: usize = SECRET_KEY_LENGTH + PUBLIC_KEY_LENGTH; + #[derive(Clone, Debug)] -pub struct K256SigningKey(SecretKey); +pub struct K256KeyPair(Box); -impl K256SigningKey { +impl K256KeyPair { pub fn generate() -> Result { - Ok(Self(SecretKey::random(OsRng))) + Ok(Self::from_secret_key(SecretKey::random(OsRng))) + } + + pub fn from_keypair_bytes(kp: &[u8]) -> Result { + if kp.len() != KEYPAIR_LENGTH { + return Err(err_msg!("Invalid keypair bytes")); + } + let sk = SecretKey::from_bytes(&kp[..SECRET_KEY_LENGTH]) + .map_err(|_| err_msg!("Invalid k-256 secret key bytes"))?; + let pk = EncodedPoint::from_bytes(&kp[SECRET_KEY_LENGTH..]) + .and_then(|pt| pt.decode()) + .map_err(|_| err_msg!("Invalid k-256 public key bytes"))?; + // FIXME: derive pk from sk and check value? + + Ok(Self(Box::new(Keypair { + secret: Some(sk), + public: pk, + }))) + } + + pub fn from_public_key_bytes(key: &[u8]) -> Result { + let pk = EncodedPoint::from_bytes(key) + .and_then(|pt| pt.decode()) + .map_err(|_| err_msg!("Invalid k-256 public key bytes"))?; + Ok(Self(Box::new(Keypair { + secret: None, + public: pk, + }))) } - pub fn from_bytes(pt: &[u8]) -> Result { - let kp = SecretKey::from_bytes(pt).map_err(|_| err_msg!("Invalid signing key bytes"))?; - Ok(Self(kp)) + pub fn from_secret_key_bytes(key: &[u8]) -> Result { + Ok(Self::from_secret_key( + SecretKey::from_bytes(key).map_err(|_| err_msg!("Invalid k-256 secret key bytes"))?, + )) + } + + #[inline] + pub(crate) fn from_secret_key(sk: SecretKey) -> Self { + let pk = sk.public_key(); + Self(Box::new(Keypair { + secret: Some(sk), + public: pk, + })) + } + + pub fn key_exchange_with(&self, other: &Self) -> Option { + match self.0.secret.as_ref() { + Some(sk) => { + let xk = diffie_hellman(sk.secret_scalar(), other.0.public.as_affine()); + Some(SecretBytes::from(xk.as_bytes().to_vec())) + } + None => None, + } } - pub fn to_bytes(&self) -> SecretBytes { - SecretBytes::from(self.0.to_bytes().to_vec()) + pub fn to_keypair_bytes(&self) -> Option { + if let Some(secret) = self.0.secret.as_ref() { + let encp = EncodedPoint::encode(self.0.public.clone(), true); + let output = SecretBytes::new_with(KEYPAIR_LENGTH, |buf| { + buf[..SECRET_KEY_LENGTH].copy_from_slice(&secret.to_bytes()[..]); + buf[SECRET_KEY_LENGTH..].copy_from_slice(encp.as_ref()); + }); + Some(output) + } else { + None + } } - pub fn public_key(&self) -> K256VerifyingKey { - K256VerifyingKey(SigningKey::from(&self.0).verify_key()) + pub(crate) fn to_signing_key(&self) -> Option { + self.0.secret.as_ref().map(SigningKey::from) } - pub fn sign(&self, message: &[u8]) -> [u8; ES256K_SIGNATURE_LENGTH] { - let skey = SigningKey::from(self.0.clone()); - let sig: Signature = skey.sign(message); - let mut sigb = [0u8; 64]; - sigb[0..32].copy_from_slice(&sig.r().to_bytes()); - sigb[32..].copy_from_slice(&sig.s().to_bytes()); - sigb + pub fn sign(&self, message: &[u8]) -> Option<[u8; ES256K_SIGNATURE_LENGTH]> { + if let Some(skey) = self.to_signing_key() { + let sig: Signature = skey.sign(message); + let sigb: [u8; 64] = sig.as_ref().try_into().unwrap(); + Some(sigb) + } else { + None + } } - pub fn verify(&self, message: &[u8], signature: &[u8; ES256K_SIGNATURE_LENGTH]) -> bool { - let mut r = [0u8; 32]; - let mut s = [0u8; 32]; - r.copy_from_slice(&signature[..32]); - s.copy_from_slice(&signature[32..]); - if let Ok(sig) = Signature::from_scalars(r, s) { - let vk = VerifyingKey::from(SigningKey::from(&self.0)); + pub fn verify_signature(&self, message: &[u8], signature: &[u8]) -> bool { + if let Ok(sig) = Signature::try_from(signature) { + let vk = VerifyingKey::from(self.0.public.as_affine()); vk.verify(message, &sig).is_ok() } else { false @@ -67,18 +122,21 @@ impl K256SigningKey { } } -impl KeyCapSign for K256SigningKey { +impl KeyCapSign for K256KeyPair { fn key_sign( &self, - data: &[u8], + message: &[u8], sig_type: Option, out: &mut B, ) -> Result { match sig_type { None | Some(SignatureType::ES256K) => { - let sig = self.sign(data); - out.extend_from_slice(&sig[..]); - Ok(ES256K_SIGNATURE_LENGTH) + if let Some(sig) = self.sign(message) { + out.extend_from_slice(&sig[..])?; + Ok(ES256K_SIGNATURE_LENGTH) + } else { + Err(err_msg!(Unsupported, "Undefined secret key")) + } } #[allow(unreachable_patterns)] _ => Err(err_msg!(Unsupported, "Unsupported signature type")), @@ -86,7 +144,7 @@ impl KeyCapSign for K256SigningKey { } } -impl KeyCapVerify for K256SigningKey { +impl KeyCapVerify for K256KeyPair { fn key_verify( &self, message: &[u8], @@ -96,7 +154,7 @@ impl KeyCapVerify for K256SigningKey { match sig_type { None | Some(SignatureType::ES256K) => { if let Ok(sig) = TryInto::<&[u8; ES256K_SIGNATURE_LENGTH]>::try_into(signature) { - Ok(self.verify(message, sig)) + Ok(self.verify_signature(message, sig)) } else { Ok(false) } @@ -107,154 +165,45 @@ impl KeyCapVerify for K256SigningKey { } } -// impl TryFrom<&AnyPrivateKey> for K256SigningKey { -// type Error = Error; +impl KeyToJwk for K256KeyPair { + const KTY: &'static str = "EC"; -// fn try_from(value: &AnyPrivateKey) -> Result { -// if value.alg == KeyAlg::Ecdsa(EcCurves::Secp256k1) { -// Self::from_bytes(value.data.as_ref()) -// } else { -// Err(err_msg!(Unsupported, "Expected k-256 key type")) -// } -// } -// } - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct K256VerifyingKey(VerifyingKey); - -impl K256VerifyingKey { - pub fn from_str(key: impl AsRef) -> Result { - let key = key.as_ref(); - let key = key.strip_suffix(":k256/ecdsa").unwrap_or(key); - let mut bval = [0u8; 32]; - bs58::decode(key) - .into(&mut bval) - .map_err(|_| err_msg!("Invalid base58 public key"))?; - Self::from_bytes(bval) - } - - pub fn from_bytes(pk: impl AsRef<[u8]>) -> Result { - let encp = EncodedPoint::from_bytes(pk.as_ref()) - .map_err(|_| err_msg!("Invalid public key bytes"))?; - if encp.is_identity() { - return Err(err_msg!("Invalid public key: identity point")); - } - Ok(Self( - VerifyingKey::from_encoded_point(&encp).map_err(|_| err_msg!("Invalid public key"))?, - )) - } - - pub fn to_bytes(&self) -> [u8; 32] { - let mut k = [0u8; 32]; - let encp = EncodedPoint::from(&self.0); - k.copy_from_slice(encp.as_bytes()); - k - } - - // pub fn to_string(&self) -> String { - // let mut sval = String::with_capacity(64); - // bs58::encode(self.to_bytes()).into(&mut sval).unwrap(); - // sval.push_str(":k256/ecdsa"); - // sval - // } - - pub fn to_jwk(&self) -> Result { - let encp = EncodedPoint::encode(self.0.clone(), false); + fn to_jwk_buffer(&self, buffer: &mut JwkEncoder) -> Result<(), Error> { + let encp = EncodedPoint::encode(self.0.public.clone(), false); let (x, y) = match encp.coordinates() { Coordinates::Identity => return Err(err_msg!("Cannot convert identity point to JWK")), - Coordinates::Uncompressed { x, y } => ( - base64::encode_config(x, base64::URL_SAFE_NO_PAD), - base64::encode_config(y, base64::URL_SAFE_NO_PAD), - ), + Coordinates::Uncompressed { x, y } => (x, y), Coordinates::Compressed { .. } => unreachable!(), }; - Ok(json!({ - "kty": "EC", - "crv": "secp256k1", - "x": x, - "y": y, - "key_ops": ["verify"] - })) - } - pub fn verify(&self, message: &[u8], signature: [u8; 64]) -> bool { - let mut r = [0u8; 32]; - let mut s = [0u8; 32]; - r.copy_from_slice(&signature[..32]); - s.copy_from_slice(&signature[32..]); - if let Ok(sig) = Signature::from_scalars(r, s) { - self.0.verify(message, &sig).is_ok() - } else { - false - } + buffer.add_str("crv", "secp256k1")?; + buffer.add_as_base64("x", &x[..])?; + buffer.add_as_base64("y", &y[..])?; + // buffer.add_str("use", "enc")?; + Ok(()) } } -impl KeyCapVerify for K256VerifyingKey { - fn key_verify( +impl KeyToJwkSecret for K256KeyPair { + fn to_jwk_buffer_secret( &self, - data: &[u8], - signature: &[u8], - sig_type: Option, - ) -> Result { - match sig_type { - None | Some(SignatureType::ES256K) => { - let mut sig = [0u8; ES256K_SIGNATURE_LENGTH]; - if data.len() != ES256K_SIGNATURE_LENGTH { - return Err(err_msg!("Invalid ES256K signature")); - } - Ok(self.verify(data, sig)) - } - #[allow(unreachable_patterns)] - _ => Err(err_msg!(Unsupported, "Unsupported signature type")), + buffer: &mut JwkEncoder, + ) -> Result<(), Error> { + self.to_jwk_buffer(buffer)?; + if let Some(sk) = self.0.secret.as_ref() { + buffer.add_as_base64("d", &sk.to_bytes()[..])?; + Ok(()) + } else { + self.to_jwk_buffer(buffer) } } } -// impl TryFrom<&AnyPublicKey> for K256VerifyingKey { -// type Error = Error; - -// fn try_from(value: &AnyPublicKey) -> Result { -// if value.alg == KeyAlg::Ecdsa(EcCurves::Secp256k1) { -// Self::from_bytes(&value.data) -// } else { -// Err(err_msg!(Unsupported, "Expected k-256 key type")) -// } -// } -// } - -#[derive(Clone, Debug)] -pub struct K256ExchPrivateKey(SecretKey); - -impl K256ExchPrivateKey { - pub fn generate() -> Result { - Ok(Self(SecretKey::random(OsRng))) - } - - pub fn from_bytes(pt: &[u8]) -> Result { - let kp = SecretKey::from_bytes(pt).map_err(|_| err_msg!("Invalid signing key bytes"))?; - Ok(Self(kp)) - } - - pub fn to_bytes(&self) -> SecretBytes { - SecretBytes::from(self.0.to_bytes().to_vec()) - } - - pub fn key_exchange_with(&self, pk: &K256ExchPublicKey) -> SecretBytes { - let xk = diffie_hellman(self.0.secret_scalar(), pk.0.as_affine()); - SecretBytes::from(xk.as_bytes().to_vec()) - } - - pub fn public_key(&self) -> K256ExchPublicKey { - K256ExchPublicKey(self.0.public_key()) - } -} - -// impl TryFrom<&AnyPrivateKey> for K256ExchPrivateKey { +// impl TryFrom<&AnyPrivateKey> for K256SigningKey { // type Error = Error; // fn try_from(value: &AnyPrivateKey) -> Result { -// if value.alg == KeyAlg::Ecdh(EcCurves::Secp256k1) { +// if value.alg == KeyAlg::Ecdsa(EcCurves::Secp256k1) { // Self::from_bytes(value.data.as_ref()) // } else { // Err(err_msg!(Unsupported, "Expected k-256 key type")) @@ -262,133 +211,78 @@ impl K256ExchPrivateKey { // } // } -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct K256ExchPublicKey(PublicKey); - -impl K256ExchPublicKey { - pub fn from_str(key: impl AsRef) -> Result { - let key = key.as_ref(); - let key = key.strip_suffix(":k256/ecdh").unwrap_or(key); - let mut bval = [0u8; 32]; - bs58::decode(key) - .into(&mut bval) - .map_err(|_| err_msg!("Invalid base58 public key"))?; - Self::from_bytes(bval) - } - - pub fn from_bytes(pk: impl AsRef<[u8]>) -> Result { - let encp = EncodedPoint::from_bytes(pk.as_ref()) - .map_err(|_| err_msg!("Invalid public key bytes"))?; - if encp.is_identity() { - return Err(err_msg!("Invalid public key: identity point")); - } - Ok(Self( - PublicKey::from_encoded_point(&encp).ok_or_else(|| err_msg!("Invalid public key"))?, - )) - } - - pub fn to_bytes(&self) -> [u8; 32] { - let mut k = [0u8; 32]; - let encp = EncodedPoint::from(&self.0); - k.copy_from_slice(encp.as_bytes()); - k - } - - pub fn to_jwk(&self) -> Result { - let encp = EncodedPoint::encode(self.0, false); - let (x, y) = match encp.coordinates() { - Coordinates::Identity => return Err(err_msg!("Cannot convert identity point to JWK")), - Coordinates::Uncompressed { x, y } => ( - base64::encode_config(x, base64::URL_SAFE_NO_PAD), - base64::encode_config(y, base64::URL_SAFE_NO_PAD), - ), - Coordinates::Compressed { .. } => unreachable!(), - }; - Ok(json!({ - "kty": "EC", - "crv": "K-256", - "x": x, - "y": y, - "key_ops": ["deriveKey"] - })) - } +#[derive(Clone, Debug)] +struct Keypair { + // SECURITY: SecretKey zeroizes on drop + secret: Option, + public: PublicKey, } -// impl TryFrom<&AnyPublicKey> for K256ExchPublicKey { -// type Error = Error; - -// fn try_from(value: &AnyPublicKey) -> Result { -// if value.alg == KeyAlg::Ecdh(EcCurves::Secp256k1) { -// Self::from_bytes(&value.data) -// } else { -// Err(err_msg!(Unsupported, "Expected k-256 key type")) -// } -// } -// } - #[cfg(test)] mod tests { use super::*; - #[test] - fn create_keypair_random() { - let sk = K256SigningKey::generate().expect("Error creating signing key"); - let _vk = sk.public_key(); - } - #[test] fn jwk_expected() { + // from https://identity.foundation/EcdsaSecp256k1RecoverySignature2020/ // {"kty":"EC", // "crv":"secp256k1", - // "x":"Hr99bGFN0HWT3ZgNNAXvmz-6Wk1HIxwsyFqUZH8PBFc", - // "y":"bonc3DRZe51NzuWetY336VmTYdUFvPK7DivSPHeu_CA", - // "d":"jv_VrhPomm6_WOzb74xF4eMI0hu9p0W1Zlxi0nz8AFs" + // "d": "rhYFsBPF9q3-uZThy7B3c4LDF_8wnozFUAEm5LLC4Zw", + // "kid": "JUvpllMEYUZ2joO59UNui_XYDqxVqiFLLAJ8klWuPBw", + // "kty": "EC", + // "x": "dWCvM4fTdeM0KmloF57zxtBPXTOythHPMm1HCLrdd3A", + // "y": "36uMVGM7hnw-N6GnjFcihWE3SkrhMLzzLCdPMXPEXlA" // } - let test_pvt = base64::decode_config( - "jv_VrhPomm6_WOzb74xF4eMI0hu9p0W1Zlxi0nz8AFs", - base64::URL_SAFE, - ) - .unwrap(); - let sk = K256SigningKey::from_bytes(&test_pvt).expect("Error creating signing key"); - let vk = sk.public_key(); - let jwk = vk.to_jwk().expect("Error converting public key to JWK"); - assert_eq!(jwk["kty"], "EC"); - assert_eq!(jwk["crv"], "secp256k1"); - assert_eq!(jwk["x"], "Hr99bGFN0HWT3ZgNNAXvmz-6Wk1HIxwsyFqUZH8PBFc"); - assert_eq!(jwk["y"], "bonc3DRZe51NzuWetY336VmTYdUFvPK7DivSPHeu_CA"); + let test_pvt_b64 = "rhYFsBPF9q3-uZThy7B3c4LDF_8wnozFUAEm5LLC4Zw"; + let test_pvt = base64::decode_config(test_pvt_b64, base64::URL_SAFE).unwrap(); + let sk = K256KeyPair::from_secret_key_bytes(&test_pvt).expect("Error creating signing key"); + let jwk = sk.to_jwk_secret().expect("Error converting key to JWK"); + let jwk = jwk.to_parts().expect("Error parsing JWK"); + assert_eq!(jwk.kty, "EC"); + assert_eq!(jwk.crv, "secp256k1"); + assert_eq!(jwk.x, "dWCvM4fTdeM0KmloF57zxtBPXTOythHPMm1HCLrdd3A"); + assert_eq!(jwk.y, "36uMVGM7hnw-N6GnjFcihWE3SkrhMLzzLCdPMXPEXlA"); + assert_eq!(jwk.d, test_pvt_b64); } #[test] fn sign_verify_expected() { let test_msg = b"This is a dummy message for use with tests"; - let test_sig = hex::decode("a2a3affbe18cda8c5a7b6375f05b304c2303ab8beb21428709a43a519f8f946f6ffa7966afdb337e9b1f70bb575282e71d4fe5bbe6bfa97b229d6bd7e97df1e5").unwrap(); + let test_sig = hex!("a2a3affbe18cda8c5a7b6375f05b304c2303ab8beb21428709a43a519f8f946f6ffa7966afdb337e9b1f70bb575282e71d4fe5bbe6bfa97b229d6bd7e97df1e5"); let test_pvt = base64::decode_config( "jv_VrhPomm6_WOzb74xF4eMI0hu9p0W1Zlxi0nz8AFs", base64::URL_SAFE_NO_PAD, ) .unwrap(); - let kp = K256SigningKey::from_bytes(&&test_pvt).unwrap(); - let sig = kp.sign(&test_msg[..]); - assert_eq!(sig, test_sig.as_slice()); - assert_eq!(kp.public_key().verify(&test_msg[..], sig), true); - assert_eq!(kp.public_key().verify(b"Not the message", sig), false); - assert_eq!(kp.public_key().verify(&test_msg[..], [0u8; 64]), false); + let kp = K256KeyPair::from_secret_key_bytes(&test_pvt).unwrap(); + let sig = kp.sign(&test_msg[..]).unwrap(); + assert_eq!(sig, &test_sig[..]); + assert_eq!(kp.verify_signature(&test_msg[..], &sig[..]), true); + assert_eq!(kp.verify_signature(b"Not the message", &sig[..]), false); + assert_eq!(kp.verify_signature(&test_msg[..], &[0u8; 64]), false); } #[test] fn key_exchange_random() { - let kp1 = K256ExchPrivateKey::generate().unwrap(); - let kp2 = K256ExchPrivateKey::generate().unwrap(); - assert_ne!(kp1.to_bytes(), kp2.to_bytes()); - - let xch1 = kp1.key_exchange_with(&kp2.public_key()); - let xch2 = kp2.key_exchange_with(&kp1.public_key()); + let kp1 = K256KeyPair::generate().unwrap(); + let kp2 = K256KeyPair::generate().unwrap(); + assert_ne!( + kp1.to_keypair_bytes().unwrap(), + kp2.to_keypair_bytes().unwrap() + ); + + let xch1 = kp1.key_exchange_with(&kp2); + let xch2 = kp2.key_exchange_with(&kp1); assert_eq!(xch1, xch2); + } - // test round trip - let xch3 = K256ExchPrivateKey::from_bytes(&kp1.to_bytes()) - .unwrap() - .key_exchange_with(&kp2.public_key()); - assert_eq!(xch3, xch1); + #[test] + fn round_trip_bytes() { + let kp = K256KeyPair::generate().unwrap(); + let cmp = K256KeyPair::from_keypair_bytes(&kp.to_keypair_bytes().unwrap()).unwrap(); + assert_eq!( + kp.to_keypair_bytes().unwrap(), + cmp.to_keypair_bytes().unwrap() + ); } } diff --git a/askar-crypto/src/alg/mod.rs b/askar-crypto/src/alg/mod.rs index 2b26ac1e..373414b7 100644 --- a/askar-crypto/src/alg/mod.rs +++ b/askar-crypto/src/alg/mod.rs @@ -3,6 +3,6 @@ pub mod ed25519; pub mod x25519; -// pub mod k256; +pub mod k256; // pub mod p256; diff --git a/askar-crypto/src/alg/x25519.rs b/askar-crypto/src/alg/x25519.rs index 484b72fc..822c604e 100644 --- a/askar-crypto/src/alg/x25519.rs +++ b/askar-crypto/src/alg/x25519.rs @@ -8,7 +8,7 @@ use zeroize::Zeroize; use crate::{ buffer::{SecretBytes, WriteBuffer}, error::Error, - jwk::{JwkEncoder, KeyToJwk, KeyToJwkPrivate}, + jwk::{JwkEncoder, KeyToJwk, KeyToJwkSecret}, }; pub const PUBLIC_KEY_LENGTH: usize = 32; @@ -62,6 +62,7 @@ impl X25519KeyPair { }))) } + #[inline] pub(crate) fn from_secret_key(sk: SecretKey) -> Self { let public = PublicKey::from(&sk); Self(Box::new(Keypair { @@ -102,11 +103,11 @@ impl X25519KeyPair { Ok(Self::from_secret_key(sk)) } - pub fn public_key_bytes(&self) -> [u8; PUBLIC_KEY_LENGTH] { + pub fn to_public_key_bytes(&self) -> [u8; PUBLIC_KEY_LENGTH] { self.0.public.to_bytes() } - pub fn secret_key_bytes(&self) -> Option { + pub fn to_secret_key_bytes(&self) -> Option { self.0 .secret .as_ref() @@ -131,21 +132,21 @@ impl KeyToJwk for X25519KeyPair { fn to_jwk_buffer(&self, buffer: &mut JwkEncoder) -> Result<(), Error> { buffer.add_str("crv", "X25519")?; - buffer.add_as_base64("x", &self.public_key_bytes()[..])?; + buffer.add_as_base64("x", &self.to_public_key_bytes()[..])?; buffer.add_str("use", "enc")?; Ok(()) } } -impl KeyToJwkPrivate for X25519KeyPair { - fn to_jwk_buffer_private( +impl KeyToJwkSecret for X25519KeyPair { + fn to_jwk_buffer_secret( &self, buffer: &mut JwkEncoder, ) -> Result<(), Error> { if let Some(sk) = self.0.secret.as_ref() { let mut sk = sk.to_bytes(); buffer.add_str("crv", "X25519")?; - buffer.add_as_base64("x", &self.public_key_bytes()[..])?; + buffer.add_as_base64("x", &self.to_public_key_bytes()[..])?; buffer.add_as_base64("d", &sk[..])?; sk.zeroize(); Ok(()) @@ -198,7 +199,7 @@ mod tests { assert_eq!(jwk.d, None); let jwk = kp - .to_jwk_private() + .to_jwk_secret() .expect("Error converting private key to JWK"); let jwk = jwk.to_parts().expect("Error parsing JWK output"); assert_eq!(jwk.kty, "OKP"); diff --git a/askar-crypto/src/jwk/mod.rs b/askar-crypto/src/jwk/mod.rs index 442b420f..a4a7f36a 100644 --- a/askar-crypto/src/jwk/mod.rs +++ b/askar-crypto/src/jwk/mod.rs @@ -61,19 +61,17 @@ pub trait KeyToJwk { fn to_jwk_buffer(&self, buffer: &mut JwkEncoder) -> Result<(), Error>; } -pub trait KeyToJwkPrivate: KeyToJwk { - fn to_jwk_private(&self) -> Result, Error> { +pub trait KeyToJwkSecret: KeyToJwk { + fn to_jwk_secret(&self) -> Result, Error> { let mut v = Vec::with_capacity(128); let mut buf = JwkEncoder::new(&mut v, Self::KTY)?; - self.to_jwk_buffer_private(&mut buf)?; + self.to_jwk_buffer_secret(&mut buf)?; buf.finalize()?; Ok(Jwk::Encoded(Cow::Owned(String::from_utf8(v).unwrap()))) } - fn to_jwk_buffer_private( - &self, - buffer: &mut JwkEncoder, - ) -> Result<(), Error>; + fn to_jwk_buffer_secret(&self, buffer: &mut JwkEncoder) + -> Result<(), Error>; } // pub trait JwkBuilder<'s> { From 61eb58c9f0d2e8fa5ea2eb72de2c9710b515c7f9 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Sat, 10 Apr 2021 11:03:22 -0700 Subject: [PATCH 010/116] update p-256, k-256 to use keypairs; more cleanups Signed-off-by: Andrew Whitehead --- askar-crypto/Cargo.toml | 4 +- askar-crypto/src/alg/ed25519.rs | 37 +-- askar-crypto/src/alg/k256.rs | 40 ++- askar-crypto/src/alg/mod.rs | 2 +- askar-crypto/src/alg/p256.rs | 473 ++++++++++++-------------------- askar-crypto/src/alg/x25519.rs | 10 +- 6 files changed, 229 insertions(+), 337 deletions(-) diff --git a/askar-crypto/Cargo.toml b/askar-crypto/Cargo.toml index 5877597c..3e373a44 100644 --- a/askar-crypto/Cargo.toml +++ b/askar-crypto/Cargo.toml @@ -38,8 +38,8 @@ group = "0.9" hex = { version = "0.4", default-features = false } hkdf = "0.10" hmac = "0.10" -k256 = { version = "0.7", default-features = false, features = ["arithmetic", "ecdsa", "ecdh", "sha256"] } -p256 = { version = "0.7", default-features = false, features = ["arithmetic", "ecdsa", "ecdh"] } +k256 = { version = "0.7", default-features = false, features = ["arithmetic", "ecdsa", "ecdh", "sha256", "zeroize"] } +p256 = { version = "0.7", default-features = false, features = ["arithmetic", "ecdsa", "ecdh", "zeroize"] } rand = { version = "0.7", default-features = false, features = ["getrandom"] } serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } serde_json = { version = "1.0", default-features = false, features = ["alloc"] } diff --git a/askar-crypto/src/alg/ed25519.rs b/askar-crypto/src/alg/ed25519.rs index 369e045f..b15fb829 100644 --- a/askar-crypto/src/alg/ed25519.rs +++ b/askar-crypto/src/alg/ed25519.rs @@ -25,6 +25,8 @@ pub const PUBLIC_KEY_LENGTH: usize = 32; pub const SECRET_KEY_LENGTH: usize = 32; pub const KEYPAIR_LENGTH: usize = SECRET_KEY_LENGTH + PUBLIC_KEY_LENGTH; +pub static JWK_CURVE: &'static str = "Ed25519"; + // FIXME implement debug pub struct Ed25519KeyPair(Box); @@ -197,7 +199,7 @@ impl KeyToJwk for Ed25519KeyPair { const KTY: &'static str = "OKP"; fn to_jwk_buffer(&self, buffer: &mut JwkEncoder) -> Result<(), Error> { - buffer.add_str("crv", "Ed25519")?; + buffer.add_str("crv", JWK_CURVE)?; buffer.add_as_base64("x", &self.to_public_key_bytes()[..])?; buffer.add_str("use", "sig")?; Ok(()) @@ -210,7 +212,7 @@ impl KeyToJwkSecret for Ed25519KeyPair { buffer: &mut JwkEncoder, ) -> Result<(), Error> { if let Some(sk) = self.0.secret.as_ref() { - buffer.add_str("crv", "Ed25519")?; + buffer.add_str("crv", JWK_CURVE)?; buffer.add_as_base64("x", &self.to_public_key_bytes()[..])?; buffer.add_as_base64("d", sk.as_bytes())?; Ok(()) @@ -255,22 +257,22 @@ mod tests { #[test] fn expand_keypair() { let seed = b"000000000000000000000000Trustee1"; - let test_sk = hex!("3030303030303030303030303030303030303030303030305472757374656531e33aaf381fffa6109ad591fdc38717945f8fabf7abf02086ae401c63e9913097"); + let test_sk = &hex!("3030303030303030303030303030303030303030303030305472757374656531e33aaf381fffa6109ad591fdc38717945f8fabf7abf02086ae401c63e9913097"); let kp = Ed25519KeyPair::from_seed(seed).unwrap(); assert_eq!(kp.to_keypair_bytes().unwrap(), &test_sk[..]); // test round trip - let cmp = Ed25519KeyPair::from_keypair_bytes(&test_sk).unwrap(); + let cmp = Ed25519KeyPair::from_keypair_bytes(test_sk).unwrap(); assert_eq!(cmp.to_keypair_bytes().unwrap(), &test_sk[..]); } #[test] fn ed25519_to_x25519() { - let test_keypair = hex!("1c1179a560d092b90458fe6ab8291215a427fcd6b3927cb240701778ef55201927c96646f2d4632d4fc241f84cbc427fbc3ecaa95becba55088d6c7b81fc5bbf"); - let x_sk = hex!("08e7286c232ec71b37918533ea0229bf0c75d3db4731df1c5c03c45bc909475f"); - let x_pk = hex!("9b4260484c889158c128796103dc8d8b883977f2ef7efb0facb12b6ca9b2ae3d"); - let x_pair = Ed25519KeyPair::from_keypair_bytes(&test_keypair) + let test_keypair = &hex!("1c1179a560d092b90458fe6ab8291215a427fcd6b3927cb240701778ef55201927c96646f2d4632d4fc241f84cbc427fbc3ecaa95becba55088d6c7b81fc5bbf"); + let x_sk = &hex!("08e7286c232ec71b37918533ea0229bf0c75d3db4731df1c5c03c45bc909475f"); + let x_pk = &hex!("9b4260484c889158c128796103dc8d8b883977f2ef7efb0facb12b6ca9b2ae3d"); + let x_pair = Ed25519KeyPair::from_keypair_bytes(test_keypair) .unwrap() .to_x25519_keypair() .to_keypair_bytes() @@ -291,13 +293,14 @@ mod tests { // "kid" : "FdFYFzERwC2uCBB46pZQi4GG85LujR8obt-KWRBICVQ" // } let test_pvt_b64 = "nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A"; + let test_pub_b64 = "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"; let test_pvt = base64::decode_config(test_pvt_b64, base64::URL_SAFE).unwrap(); let kp = Ed25519KeyPair::from_secret_key_bytes(&test_pvt).expect("Error creating signing key"); let jwk = kp.to_jwk().expect("Error converting public key to JWK"); let jwk = jwk.to_parts().expect("Error parsing JWK output"); assert_eq!(jwk.kty, "OKP"); - assert_eq!(jwk.crv, "Ed25519"); + assert_eq!(jwk.crv, JWK_CURVE); assert_eq!(jwk.x, "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"); let jwk = kp @@ -305,28 +308,28 @@ mod tests { .expect("Error converting private key to JWK"); let jwk = jwk.to_parts().expect("Error parsing JWK output"); assert_eq!(jwk.kty, "OKP"); - assert_eq!(jwk.crv, "Ed25519"); - assert_eq!(jwk.x, "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"); + assert_eq!(jwk.crv, JWK_CURVE); + assert_eq!(jwk.x, test_pub_b64); assert_eq!(jwk.d, test_pvt_b64); } #[test] fn sign_verify_expected() { let test_msg = b"This is a dummy message for use with tests"; - let test_sig = hex!( + let test_sig = &hex!( "451b5b8e8725321541954997781de51f4142e4a56bab68d24f6a6b92615de5ee fb74134138315859a32c7cf5fe5a488bc545e2e08e5eedfd1fb10188d532d808" ); - let test_keypair = hex!( + let test_keypair = &hex!( "1c1179a560d092b90458fe6ab8291215a427fcd6b3927cb240701778ef552019 27c96646f2d4632d4fc241f84cbc427fbc3ecaa95becba55088d6c7b81fc5bbf" ); - let kp = Ed25519KeyPair::from_keypair_bytes(&test_keypair).unwrap(); - let sig = kp.sign(&test_msg[..]).unwrap(); + let kp = Ed25519KeyPair::from_keypair_bytes(test_keypair).unwrap(); + let sig = &kp.sign(test_msg).unwrap(); assert_eq!(sig, test_sig); - assert_eq!(kp.verify_signature(&test_msg[..], &sig[..]), true); + assert_eq!(kp.verify_signature(test_msg, &sig[..]), true); assert_eq!(kp.verify_signature(b"Not the message", &sig[..]), false); - assert_eq!(kp.verify_signature(&test_msg[..], &[0u8; 64]), false); + assert_eq!(kp.verify_signature(test_msg, &[0u8; 64]), false); } #[test] diff --git a/askar-crypto/src/alg/k256.rs b/askar-crypto/src/alg/k256.rs index c7e4f074..fe7880c9 100644 --- a/askar-crypto/src/alg/k256.rs +++ b/askar-crypto/src/alg/k256.rs @@ -25,6 +25,8 @@ pub const PUBLIC_KEY_LENGTH: usize = 33; // compressed size pub const SECRET_KEY_LENGTH: usize = 32; pub const KEYPAIR_LENGTH: usize = SECRET_KEY_LENGTH + PUBLIC_KEY_LENGTH; +pub static JWK_CURVE: &'static str = "secp256k1"; + #[derive(Clone, Debug)] pub struct K256KeyPair(Box); @@ -87,7 +89,7 @@ impl K256KeyPair { pub fn to_keypair_bytes(&self) -> Option { if let Some(secret) = self.0.secret.as_ref() { - let encp = EncodedPoint::encode(self.0.public.clone(), true); + let encp = EncodedPoint::encode(self.0.public, true); let output = SecretBytes::new_with(KEYPAIR_LENGTH, |buf| { buf[..SECRET_KEY_LENGTH].copy_from_slice(&secret.to_bytes()[..]); buf[SECRET_KEY_LENGTH..].copy_from_slice(encp.as_ref()); @@ -152,13 +154,7 @@ impl KeyCapVerify for K256KeyPair { sig_type: Option, ) -> Result { match sig_type { - None | Some(SignatureType::ES256K) => { - if let Ok(sig) = TryInto::<&[u8; ES256K_SIGNATURE_LENGTH]>::try_into(signature) { - Ok(self.verify_signature(message, sig)) - } else { - Ok(false) - } - } + None | Some(SignatureType::ES256K) => Ok(self.verify_signature(message, signature)), #[allow(unreachable_patterns)] _ => Err(err_msg!(Unsupported, "Unsupported signature type")), } @@ -169,14 +165,14 @@ impl KeyToJwk for K256KeyPair { const KTY: &'static str = "EC"; fn to_jwk_buffer(&self, buffer: &mut JwkEncoder) -> Result<(), Error> { - let encp = EncodedPoint::encode(self.0.public.clone(), false); + let encp = EncodedPoint::encode(self.0.public, false); let (x, y) = match encp.coordinates() { Coordinates::Identity => return Err(err_msg!("Cannot convert identity point to JWK")), Coordinates::Uncompressed { x, y } => (x, y), Coordinates::Compressed { .. } => unreachable!(), }; - buffer.add_str("crv", "secp256k1")?; + buffer.add_str("crv", JWK_CURVE)?; buffer.add_as_base64("x", &x[..])?; buffer.add_as_base64("y", &y[..])?; // buffer.add_str("use", "enc")?; @@ -234,21 +230,37 @@ mod tests { // "y": "36uMVGM7hnw-N6GnjFcihWE3SkrhMLzzLCdPMXPEXlA" // } let test_pvt_b64 = "rhYFsBPF9q3-uZThy7B3c4LDF_8wnozFUAEm5LLC4Zw"; + let test_pub_b64 = ( + "dWCvM4fTdeM0KmloF57zxtBPXTOythHPMm1HCLrdd3A", + "36uMVGM7hnw-N6GnjFcihWE3SkrhMLzzLCdPMXPEXlA", + ); let test_pvt = base64::decode_config(test_pvt_b64, base64::URL_SAFE).unwrap(); let sk = K256KeyPair::from_secret_key_bytes(&test_pvt).expect("Error creating signing key"); + + let jwk = sk.to_jwk().expect("Error converting key to JWK"); + let jwk = jwk.to_parts().expect("Error parsing JWK"); + assert_eq!(jwk.kty, "EC"); + assert_eq!(jwk.crv, JWK_CURVE); + assert_eq!(jwk.x, test_pub_b64.0); + assert_eq!(jwk.y, test_pub_b64.1); + assert_eq!(jwk.d, None); + let jwk = sk.to_jwk_secret().expect("Error converting key to JWK"); let jwk = jwk.to_parts().expect("Error parsing JWK"); assert_eq!(jwk.kty, "EC"); - assert_eq!(jwk.crv, "secp256k1"); - assert_eq!(jwk.x, "dWCvM4fTdeM0KmloF57zxtBPXTOythHPMm1HCLrdd3A"); - assert_eq!(jwk.y, "36uMVGM7hnw-N6GnjFcihWE3SkrhMLzzLCdPMXPEXlA"); + assert_eq!(jwk.crv, JWK_CURVE); + assert_eq!(jwk.x, test_pub_b64.0); + assert_eq!(jwk.y, test_pub_b64.1); assert_eq!(jwk.d, test_pvt_b64); } #[test] fn sign_verify_expected() { let test_msg = b"This is a dummy message for use with tests"; - let test_sig = hex!("a2a3affbe18cda8c5a7b6375f05b304c2303ab8beb21428709a43a519f8f946f6ffa7966afdb337e9b1f70bb575282e71d4fe5bbe6bfa97b229d6bd7e97df1e5"); + let test_sig = &hex!( + "a2a3affbe18cda8c5a7b6375f05b304c2303ab8beb21428709a43a519f8f946f + 6ffa7966afdb337e9b1f70bb575282e71d4fe5bbe6bfa97b229d6bd7e97df1e5" + ); let test_pvt = base64::decode_config( "jv_VrhPomm6_WOzb74xF4eMI0hu9p0W1Zlxi0nz8AFs", base64::URL_SAFE_NO_PAD, diff --git a/askar-crypto/src/alg/mod.rs b/askar-crypto/src/alg/mod.rs index 373414b7..49cbf9a5 100644 --- a/askar-crypto/src/alg/mod.rs +++ b/askar-crypto/src/alg/mod.rs @@ -5,4 +5,4 @@ pub mod x25519; pub mod k256; -// pub mod p256; +pub mod p256; diff --git a/askar-crypto/src/alg/p256.rs b/askar-crypto/src/alg/p256.rs index 5bc6da87..909ee370 100644 --- a/askar-crypto/src/alg/p256.rs +++ b/askar-crypto/src/alg/p256.rs @@ -1,65 +1,122 @@ -use std::convert::TryInto; +use alloc::boxed::Box; +use core::convert::{TryFrom, TryInto}; use p256::{ ecdsa::{ signature::{Signer, Verifier}, Signature, SigningKey, VerifyingKey, }, - elliptic_curve::{ - ecdh::diffie_hellman, - sec1::{Coordinates, FromEncodedPoint}, - }, + elliptic_curve::{ecdh::diffie_hellman, sec1::Coordinates}, EncodedPoint, PublicKey, SecretKey, }; use rand::rngs::OsRng; -use serde_json::json; use crate::{ buffer::{SecretBytes, WriteBuffer}, // any::{AnyPrivateKey, AnyPublicKey}, - caps::{EcCurves, KeyAlg, KeyCapSign, KeyCapVerify, SignatureType}, + caps::{KeyCapSign, KeyCapVerify, SignatureType}, error::Error, + jwk::{JwkEncoder, KeyToJwk, KeyToJwkSecret}, }; pub const ES256_SIGNATURE_LENGTH: usize = 64; +pub const PUBLIC_KEY_LENGTH: usize = 33; // compressed size +pub const SECRET_KEY_LENGTH: usize = 32; +pub const KEYPAIR_LENGTH: usize = SECRET_KEY_LENGTH + PUBLIC_KEY_LENGTH; + +pub static JWK_CURVE: &'static str = "P-256"; + #[derive(Clone, Debug)] -pub struct P256SigningKey(SecretKey); +pub struct P256KeyPair(Box); -impl P256SigningKey { +impl P256KeyPair { pub fn generate() -> Result { - Ok(Self(SecretKey::random(OsRng))) + Ok(Self::from_secret_key(SecretKey::random(OsRng))) } - pub fn from_bytes(pt: &[u8]) -> Result { - let kp = SecretKey::from_bytes(pt).map_err(|_| err_msg!("Invalid signing key bytes"))?; - Ok(Self(kp)) + pub fn from_keypair_bytes(kp: &[u8]) -> Result { + if kp.len() != KEYPAIR_LENGTH { + return Err(err_msg!("Invalid keypair bytes")); + } + let sk = SecretKey::from_bytes(&kp[..SECRET_KEY_LENGTH]) + .map_err(|_| err_msg!("Invalid p-256 secret key bytes"))?; + let pk = EncodedPoint::from_bytes(&kp[SECRET_KEY_LENGTH..]) + .and_then(|pt| pt.decode()) + .map_err(|_| err_msg!("Invalid p-256 public key bytes"))?; + // FIXME: derive pk from sk and check value? + + Ok(Self(Box::new(Keypair { + secret: Some(sk), + public: pk, + }))) } - pub fn to_bytes(&self) -> SecretBytes { - SecretBytes::from(self.0.to_bytes().to_vec()) + pub fn from_public_key_bytes(key: &[u8]) -> Result { + let pk = EncodedPoint::from_bytes(key) + .and_then(|pt| pt.decode()) + .map_err(|_| err_msg!("Invalid p-256 public key bytes"))?; + Ok(Self(Box::new(Keypair { + secret: None, + public: pk, + }))) } - pub fn public_key(&self) -> P256VerifyingKey { - P256VerifyingKey(self.0.public_key()) + pub fn from_secret_key_bytes(key: &[u8]) -> Result { + Ok(Self::from_secret_key( + SecretKey::from_bytes(key).map_err(|_| err_msg!("Invalid k-256 secret key bytes"))?, + )) } - pub fn sign(&self, message: &[u8]) -> [u8; ES256_SIGNATURE_LENGTH] { - let skey = SigningKey::from(self.0.clone()); - let sig = skey.sign(message); - let mut sigb = [0u8; ES256_SIGNATURE_LENGTH]; - sigb[0..32].copy_from_slice(&sig.r().to_bytes()); - sigb[32..].copy_from_slice(&sig.s().to_bytes()); - sigb + #[inline] + pub(crate) fn from_secret_key(sk: SecretKey) -> Self { + let pk = sk.public_key(); + Self(Box::new(Keypair { + secret: Some(sk), + public: pk, + })) } - pub fn verify(&self, message: &[u8], signature: &[u8; ES256_SIGNATURE_LENGTH]) -> bool { - let mut r = [0u8; 32]; - let mut s = [0u8; 32]; - r.copy_from_slice(&signature[..32]); - s.copy_from_slice(&signature[32..]); - if let Ok(sig) = Signature::from_scalars(r, s) { - let vk = VerifyingKey::from(self.0.public_key()); + pub fn key_exchange_with(&self, other: &Self) -> Option { + match self.0.secret.as_ref() { + Some(sk) => { + let xk = diffie_hellman(sk.secret_scalar(), other.0.public.as_affine()); + Some(SecretBytes::from(xk.as_bytes().to_vec())) + } + None => None, + } + } + + pub fn to_keypair_bytes(&self) -> Option { + if let Some(secret) = self.0.secret.as_ref() { + let encp = EncodedPoint::encode(self.0.public, true); + let output = SecretBytes::new_with(KEYPAIR_LENGTH, |buf| { + buf[..SECRET_KEY_LENGTH].copy_from_slice(&secret.to_bytes()[..]); + buf[SECRET_KEY_LENGTH..].copy_from_slice(encp.as_ref()); + }); + Some(output) + } else { + None + } + } + + pub(crate) fn to_signing_key(&self) -> Option { + self.0.secret.clone().map(SigningKey::from) + } + + pub fn sign(&self, message: &[u8]) -> Option<[u8; ES256_SIGNATURE_LENGTH]> { + if let Some(skey) = self.to_signing_key() { + let sig: Signature = skey.sign(message); + let sigb: [u8; 64] = sig.as_ref().try_into().unwrap(); + Some(sigb) + } else { + None + } + } + + pub fn verify_signature(&self, message: &[u8], signature: &[u8]) -> bool { + if let Ok(sig) = Signature::try_from(signature) { + let vk = VerifyingKey::from(&self.0.public); vk.verify(message, &sig).is_ok() } else { false @@ -67,18 +124,21 @@ impl P256SigningKey { } } -impl KeyCapSign for P256SigningKey { +impl KeyCapSign for P256KeyPair { fn key_sign( &self, - data: &[u8], + message: &[u8], sig_type: Option, out: &mut B, ) -> Result { match sig_type { - None | Some(SignatureType::ES256) => { - let sig = self.sign(data); - out.extend_from_slice(&sig[..]); - Ok(ES256_SIGNATURE_LENGTH) + None | Some(SignatureType::ES256K) => { + if let Some(sig) = self.sign(message) { + out.extend_from_slice(&sig[..])?; + Ok(ES256_SIGNATURE_LENGTH) + } else { + Err(err_msg!(Unsupported, "Undefined secret key")) + } } #[allow(unreachable_patterns)] _ => Err(err_msg!(Unsupported, "Unsupported signature type")), @@ -86,7 +146,7 @@ impl KeyCapSign for P256SigningKey { } } -impl KeyCapVerify for P256SigningKey { +impl KeyCapVerify for P256KeyPair { fn key_verify( &self, message: &[u8], @@ -94,264 +154,58 @@ impl KeyCapVerify for P256SigningKey { sig_type: Option, ) -> Result { match sig_type { - None | Some(SignatureType::ES256) => { - if let Ok(sig) = TryInto::<&[u8; ES256_SIGNATURE_LENGTH]>::try_into(signature) { - Ok(self.verify(message, sig)) - } else { - Ok(false) - } - } + None | Some(SignatureType::ES256) => Ok(self.verify_signature(message, signature)), #[allow(unreachable_patterns)] _ => Err(err_msg!(Unsupported, "Unsupported signature type")), } } } -// impl TryFrom<&AnyPrivateKey> for P256SigningKey { -// type Error = Error; - -// fn try_from(value: &AnyPrivateKey) -> Result { -// if value.alg == KeyAlg::Ecdsa(EcCurves::Secp256r1) { -// Self::from_bytes(value.data.as_ref()) -// } else { -// Err(err_msg!(Unsupported, "Expected p-256 key type")) -// } -// } -// } - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct P256VerifyingKey(PublicKey); - -impl P256VerifyingKey { - pub fn from_str(key: impl AsRef) -> Result { - let key = key.as_ref(); - let key = key.strip_suffix(":p256/ecdsa").unwrap_or(key); - let mut bval = [0u8; 32]; - bs58::decode(key) - .into(&mut bval) - .map_err(|_| err_msg!("Invalid base58 public key"))?; - Self::from_bytes(bval) - } +impl KeyToJwk for P256KeyPair { + const KTY: &'static str = "EC"; - pub fn from_bytes(pk: impl AsRef<[u8]>) -> Result { - let encp = EncodedPoint::from_bytes(pk.as_ref()) - .map_err(|_| err_msg!("Invalid public key bytes"))?; - if encp.is_identity() { - return Err(err_msg!("Invalid public key: identity point")); - } - Ok(Self( - PublicKey::from_encoded_point(&encp).ok_or_else(|| err_msg!("Invalid public key"))?, - )) - } - - pub fn to_base58(&self) -> String { - bs58::encode(self.to_bytes()).into_string() - } - - pub fn to_bytes(&self) -> [u8; 32] { - let mut k = [0u8; 32]; - let encp = EncodedPoint::from(&self.0); - k.copy_from_slice(encp.as_bytes()); - k - } - - pub fn to_string(&self) -> String { - let mut sval = String::with_capacity(64); - bs58::encode(self.to_bytes()).into(&mut sval).unwrap(); - sval.push_str(":p256/ecdsa"); - sval - } - - pub fn to_jwk(&self) -> Result { - let encp = EncodedPoint::encode(self.0, false); + fn to_jwk_buffer(&self, buffer: &mut JwkEncoder) -> Result<(), Error> { + let encp = EncodedPoint::encode(self.0.public, false); let (x, y) = match encp.coordinates() { Coordinates::Identity => return Err(err_msg!("Cannot convert identity point to JWK")), - Coordinates::Uncompressed { x, y } => ( - base64::encode_config(x, base64::URL_SAFE_NO_PAD), - base64::encode_config(y, base64::URL_SAFE_NO_PAD), - ), + Coordinates::Uncompressed { x, y } => (x, y), Coordinates::Compressed { .. } => unreachable!(), }; - Ok(json!({ - "kty": "EC", - "crv": "P-256", - "x": x, - "y": y, - "key_ops": ["verify"] - })) - } - pub fn verify(&self, message: &[u8], signature: &[u8; 64]) -> bool { - let mut r = [0u8; 32]; - let mut s = [0u8; 32]; - r.copy_from_slice(&signature[..32]); - s.copy_from_slice(&signature[32..]); - if let Ok(sig) = Signature::from_scalars(r, s) { - let vk = VerifyingKey::from(self.0.clone()); - vk.verify(message, &sig).is_ok() - } else { - false - } + buffer.add_str("crv", JWK_CURVE)?; + buffer.add_as_base64("x", &x[..])?; + buffer.add_as_base64("y", &y[..])?; + // buffer.add_str("use", "enc")?; + Ok(()) } } -impl KeyCapVerify for P256VerifyingKey { - fn key_verify( +impl KeyToJwkSecret for P256KeyPair { + fn to_jwk_buffer_secret( &self, - data: &[u8], - signature: &[u8], - sig_type: Option, - ) -> Result { - match sig_type { - None | Some(SignatureType::ES256) => { - if let Ok(sig) = TryInto::<&[u8; ES256_SIGNATURE_LENGTH]>::try_into(signature) { - Ok(self.verify(data, sig)) - } else { - Ok(false) - } - } - #[allow(unreachable_patterns)] - _ => Err(err_msg!(Unsupported, "Unsupported signature type")), + buffer: &mut JwkEncoder, + ) -> Result<(), Error> { + self.to_jwk_buffer(buffer)?; + if let Some(sk) = self.0.secret.as_ref() { + buffer.add_as_base64("d", &sk.to_bytes()[..])?; + Ok(()) + } else { + self.to_jwk_buffer(buffer) } } } -// impl TryFrom<&AnyPublicKey> for P256VerifyingKey { -// type Error = Error; - -// fn try_from(value: &AnyPublicKey) -> Result { -// if value.alg == KeyAlg::Ecdsa(EcCurves::Secp256r1) { -// Self::from_bytes(&value.data) -// } else { -// Err(err_msg!(Unsupported, "Expected p-256 key type")) -// } -// } -// } - #[derive(Clone, Debug)] -pub struct P256ExchPrivateKey(SecretKey); - -impl P256ExchPrivateKey { - pub fn generate() -> Result { - Ok(Self(SecretKey::random(OsRng))) - } - - pub fn from_bytes(pt: &[u8]) -> Result { - let kp = SecretKey::from_bytes(pt).map_err(|_| err_msg!("Invalid signing key bytes"))?; - Ok(Self(kp)) - } - - pub fn to_bytes(&self) -> SecretBytes { - SecretBytes::from(self.0.to_bytes().to_vec()) - } - - pub fn key_exchange_with(&self, pk: &P256ExchPublicKey) -> SecretBytes { - let xk = diffie_hellman(self.0.secret_scalar(), pk.0.as_affine()); - SecretBytes::from(xk.as_bytes().to_vec()) - } - - pub fn public_key(&self) -> P256ExchPublicKey { - P256ExchPublicKey(self.0.public_key()) - } +struct Keypair { + // SECURITY: SecretKey zeroizes on drop + secret: Option, + public: PublicKey, } -// impl TryFrom<&AnyPrivateKey> for P256ExchPrivateKey { -// type Error = Error; - -// fn try_from(value: &AnyPrivateKey) -> Result { -// if value.alg == KeyAlg::Ecdh(EcCurves::Secp256r1) { -// Self::from_bytes(value.data.as_ref()) -// } else { -// Err(err_msg!(Unsupported, "Expected p-256 key type")) -// } -// } -// } - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct P256ExchPublicKey(PublicKey); - -impl P256ExchPublicKey { - pub fn from_str(key: impl AsRef) -> Result { - let key = key.as_ref(); - let key = key.strip_suffix(":p256/ecdh").unwrap_or(key); - let mut bval = [0u8; 32]; - bs58::decode(key) - .into(&mut bval) - .map_err(|_| err_msg!("Invalid base58 public key"))?; - Self::from_bytes(bval) - } - - pub fn from_bytes(pk: impl AsRef<[u8]>) -> Result { - let encp = EncodedPoint::from_bytes(pk.as_ref()) - .map_err(|_| err_msg!("Invalid public key bytes"))?; - if encp.is_identity() { - return Err(err_msg!("Invalid public key: identity point")); - } - Ok(Self( - PublicKey::from_encoded_point(&encp).ok_or_else(|| err_msg!("Invalid public key"))?, - )) - } - - pub fn to_base58(&self) -> String { - bs58::encode(self.to_bytes()).into_string() - } - - pub fn to_bytes(&self) -> [u8; 32] { - let mut k = [0u8; 32]; - let encp = EncodedPoint::from(&self.0); - k.copy_from_slice(encp.as_bytes()); - k - } - - pub fn to_string(&self) -> String { - let mut sval = String::with_capacity(64); - bs58::encode(self.to_bytes()).into(&mut sval).unwrap(); - sval.push_str(":p256/ecdh"); - sval - } - - pub fn to_jwk(&self) -> Result { - let encp = EncodedPoint::encode(self.0, false); - let (x, y) = match encp.coordinates() { - Coordinates::Identity => return Err(err_msg!("Cannot convert identity point to JWK")), - Coordinates::Uncompressed { x, y } => ( - base64::encode_config(x, base64::URL_SAFE_NO_PAD), - base64::encode_config(y, base64::URL_SAFE_NO_PAD), - ), - Coordinates::Compressed { .. } => unreachable!(), - }; - Ok(json!({ - "kty": "EC", - "crv": "P-256", - "x": x, - "y": y, - "key_ops": ["deriveKey"] - })) - } -} - -// impl TryFrom<&AnyPublicKey> for P256ExchPublicKey { -// type Error = Error; - -// fn try_from(value: &AnyPublicKey) -> Result { -// if value.alg == KeyAlg::Ecdh(EcCurves::Secp256r1) { -// Self::from_bytes(&value.data) -// } else { -// Err(err_msg!(Unsupported, "Expected p-256 key type")) -// } -// } -// } - #[cfg(test)] mod tests { use super::*; - #[test] - fn create_keypair_random() { - let sk = P256SigningKey::generate().expect("Error creating signing key"); - let _vk = sk.public_key(); - } - #[test] fn jwk_expected() { // from JWS RFC https://tools.ietf.org/html/rfc7515 @@ -361,51 +215,72 @@ mod tests { // "y":"x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0", // "d":"jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI" // } - let test_pvt = base64::decode_config( - "jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI", - base64::URL_SAFE, - ) - .unwrap(); - let sk = P256SigningKey::from_bytes(&test_pvt).expect("Error creating signing key"); - let vk = sk.public_key(); - let jwk = vk.to_jwk().expect("Error converting public key to JWK"); - assert_eq!(jwk["kty"], "EC"); - assert_eq!(jwk["crv"], "P-256"); - assert_eq!(jwk["x"], "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU"); - assert_eq!(jwk["y"], "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0"); + let test_pvt_b64 = "jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI"; + let test_pub_b64 = ( + "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU", + "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0", + ); + let test_pvt = base64::decode_config(test_pvt_b64, base64::URL_SAFE).unwrap(); + let sk = P256KeyPair::from_secret_key_bytes(&test_pvt).expect("Error creating signing key"); + + let jwk = sk.to_jwk().expect("Error converting key to JWK"); + let jwk = jwk.to_parts().expect("Error parsing JWK"); + assert_eq!(jwk.kty, "EC"); + assert_eq!(jwk.crv, JWK_CURVE); + assert_eq!(jwk.x, test_pub_b64.0); + assert_eq!(jwk.y, test_pub_b64.1); + assert_eq!(jwk.d, None); + + let jwk = sk.to_jwk_secret().expect("Error converting key to JWK"); + let jwk = jwk.to_parts().expect("Error parsing JWK"); + assert_eq!(jwk.kty, "EC"); + assert_eq!(jwk.crv, JWK_CURVE); + assert_eq!(jwk.x, test_pub_b64.0); + assert_eq!(jwk.y, test_pub_b64.1); + assert_eq!(jwk.d, test_pvt_b64); } #[test] fn sign_verify_expected() { let test_msg = b"This is a dummy message for use with tests"; - let test_sig = hex::decode("241f765f19d4e6148452f2249d2fa69882244a6ad6e70aadb8848a6409d207124e85faf9587100247de7bdace13a3073b47ec8a531ca91c1375b2b6134344413").unwrap(); + let test_sig = &hex!( + "241f765f19d4e6148452f2249d2fa69882244a6ad6e70aadb8848a6409d20712 + 4e85faf9587100247de7bdace13a3073b47ec8a531ca91c1375b2b6134344413" + ); let test_pvt = base64::decode_config( "jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI", base64::URL_SAFE_NO_PAD, ) .unwrap(); - let kp = P256SigningKey::from_bytes(&&test_pvt).unwrap(); - let sig = kp.sign(&test_msg[..]); - assert_eq!(sig, test_sig.as_slice()); - assert_eq!(kp.public_key().verify(&test_msg[..], &sig), true); - assert_eq!(kp.public_key().verify(b"Not the message", &sig), false); - assert_eq!(kp.public_key().verify(&test_msg[..], &[0u8; 64]), false); + let kp = P256KeyPair::from_secret_key_bytes(&test_pvt).unwrap(); + let sig = kp.sign(&test_msg[..]).unwrap(); + assert_eq!(sig, &test_sig[..]); + assert_eq!(kp.verify_signature(&test_msg[..], &sig[..]), true); + assert_eq!(kp.verify_signature(b"Not the message", &sig[..]), false); + assert_eq!(kp.verify_signature(&test_msg[..], &[0u8; 64]), false); } #[test] fn key_exchange_random() { - let kp1 = P256ExchPrivateKey::generate().unwrap(); - let kp2 = P256ExchPrivateKey::generate().unwrap(); - assert_ne!(kp1.to_bytes(), kp2.to_bytes()); - - let xch1 = kp1.key_exchange_with(&kp2.public_key()); - let xch2 = kp2.key_exchange_with(&kp1.public_key()); + let kp1 = P256KeyPair::generate().unwrap(); + let kp2 = P256KeyPair::generate().unwrap(); + assert_ne!( + kp1.to_keypair_bytes().unwrap(), + kp2.to_keypair_bytes().unwrap() + ); + + let xch1 = kp1.key_exchange_with(&kp2); + let xch2 = kp2.key_exchange_with(&kp1); assert_eq!(xch1, xch2); + } - // test round trip - let xch3 = P256ExchPrivateKey::from_bytes(&kp1.to_bytes()) - .unwrap() - .key_exchange_with(&kp2.public_key()); - assert_eq!(xch3, xch1); + #[test] + fn round_trip_bytes() { + let kp = P256KeyPair::generate().unwrap(); + let cmp = P256KeyPair::from_keypair_bytes(&kp.to_keypair_bytes().unwrap()).unwrap(); + assert_eq!( + kp.to_keypair_bytes().unwrap(), + cmp.to_keypair_bytes().unwrap() + ); } } diff --git a/askar-crypto/src/alg/x25519.rs b/askar-crypto/src/alg/x25519.rs index 822c604e..c37e9d99 100644 --- a/askar-crypto/src/alg/x25519.rs +++ b/askar-crypto/src/alg/x25519.rs @@ -15,6 +15,8 @@ pub const PUBLIC_KEY_LENGTH: usize = 32; pub const SECRET_KEY_LENGTH: usize = 32; pub const KEYPAIR_LENGTH: usize = SECRET_KEY_LENGTH + PUBLIC_KEY_LENGTH; +pub static JWK_CURVE: &'static str = "X25519"; + #[derive(Clone)] // FIXME implement zeroize pub struct X25519KeyPair(Box); @@ -131,7 +133,7 @@ impl KeyToJwk for X25519KeyPair { const KTY: &'static str = "OKP"; fn to_jwk_buffer(&self, buffer: &mut JwkEncoder) -> Result<(), Error> { - buffer.add_str("crv", "X25519")?; + buffer.add_str("crv", JWK_CURVE)?; buffer.add_as_base64("x", &self.to_public_key_bytes()[..])?; buffer.add_str("use", "enc")?; Ok(()) @@ -145,7 +147,7 @@ impl KeyToJwkSecret for X25519KeyPair { ) -> Result<(), Error> { if let Some(sk) = self.0.secret.as_ref() { let mut sk = sk.to_bytes(); - buffer.add_str("crv", "X25519")?; + buffer.add_str("crv", JWK_CURVE)?; buffer.add_as_base64("x", &self.to_public_key_bytes()[..])?; buffer.add_as_base64("d", &sk[..])?; sk.zeroize(); @@ -194,7 +196,7 @@ mod tests { let jwk = kp.to_jwk().expect("Error converting public key to JWK"); let jwk = jwk.to_parts().expect("Error parsing JWK output"); assert_eq!(jwk.kty, "OKP"); - assert_eq!(jwk.crv, "X25519"); + assert_eq!(jwk.crv, JWK_CURVE); assert_eq!(jwk.x, "tGskN_ae61DP4DLY31_fjkbvnKqf-ze7kA6Cj2vyQxU"); assert_eq!(jwk.d, None); @@ -203,7 +205,7 @@ mod tests { .expect("Error converting private key to JWK"); let jwk = jwk.to_parts().expect("Error parsing JWK output"); assert_eq!(jwk.kty, "OKP"); - assert_eq!(jwk.crv, "X25519"); + assert_eq!(jwk.crv, JWK_CURVE); assert_eq!(jwk.x, "tGskN_ae61DP4DLY31_fjkbvnKqf-ze7kA6Cj2vyQxU"); assert_eq!(jwk.d, test_pvt_b64); } From 8ea5c2f320149946e0b4b36291acf7300608c3eb Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Sat, 10 Apr 2021 12:26:15 -0700 Subject: [PATCH 011/116] combine JWK traits Signed-off-by: Andrew Whitehead --- askar-crypto/src/alg/ed25519.rs | 45 ++++++++++++++++------------- askar-crypto/src/alg/k256.rs | 22 ++++----------- askar-crypto/src/alg/p256.rs | 22 ++++----------- askar-crypto/src/alg/x25519.rs | 50 +++++++++++++++++++-------------- askar-crypto/src/jwk/encode.rs | 9 ++++-- askar-crypto/src/jwk/mod.rs | 15 ++++------ 6 files changed, 79 insertions(+), 84 deletions(-) diff --git a/askar-crypto/src/alg/ed25519.rs b/askar-crypto/src/alg/ed25519.rs index b15fb829..36035a2d 100644 --- a/askar-crypto/src/alg/ed25519.rs +++ b/askar-crypto/src/alg/ed25519.rs @@ -1,5 +1,8 @@ use alloc::boxed::Box; -use core::convert::{TryFrom, TryInto}; +use core::{ + convert::{TryFrom, TryInto}, + fmt::{self, Debug, Formatter}, +}; use curve25519_dalek::edwards::CompressedEdwardsY; use ed25519_dalek::{ExpandedSecretKey, PublicKey, SecretKey, Signature}; @@ -13,7 +16,7 @@ use crate::{ buffer::{SecretBytes, WriteBuffer}, caps::{KeyCapSign, KeyCapVerify, SignatureType}, error::Error, - jwk::{JwkEncoder, KeyToJwk, KeyToJwkSecret}, + jwk::{JwkEncoder, KeyToJwk}, }; // FIXME - check for low-order points when loading public keys? @@ -27,7 +30,6 @@ pub const KEYPAIR_LENGTH: usize = SECRET_KEY_LENGTH + PUBLIC_KEY_LENGTH; pub static JWK_CURVE: &'static str = "Ed25519"; -// FIXME implement debug pub struct Ed25519KeyPair(Box); impl Ed25519KeyPair { @@ -157,6 +159,22 @@ impl Clone for Ed25519KeyPair { } } +impl Debug for Ed25519KeyPair { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("Ed25519KeyPair") + .field( + "secret", + if self.0.secret.is_some() { + &"" + } else { + &"None" + }, + ) + .field("public", &self.0.public) + .finish() + } +} + impl KeyCapSign for Ed25519KeyPair { fn key_sign( &self, @@ -201,27 +219,16 @@ impl KeyToJwk for Ed25519KeyPair { fn to_jwk_buffer(&self, buffer: &mut JwkEncoder) -> Result<(), Error> { buffer.add_str("crv", JWK_CURVE)?; buffer.add_as_base64("x", &self.to_public_key_bytes()[..])?; + if buffer.is_secret() { + if let Some(sk) = self.0.secret.as_ref() { + buffer.add_as_base64("d", sk.as_bytes())?; + } + } buffer.add_str("use", "sig")?; Ok(()) } } -impl KeyToJwkSecret for Ed25519KeyPair { - fn to_jwk_buffer_secret( - &self, - buffer: &mut JwkEncoder, - ) -> Result<(), Error> { - if let Some(sk) = self.0.secret.as_ref() { - buffer.add_str("crv", JWK_CURVE)?; - buffer.add_as_base64("x", &self.to_public_key_bytes()[..])?; - buffer.add_as_base64("d", sk.as_bytes())?; - Ok(()) - } else { - self.to_jwk_buffer(buffer) - } - } -} - struct Keypair { // SECURITY: SecretKey zeroizes on drop secret: Option, diff --git a/askar-crypto/src/alg/k256.rs b/askar-crypto/src/alg/k256.rs index fe7880c9..b5aba6bc 100644 --- a/askar-crypto/src/alg/k256.rs +++ b/askar-crypto/src/alg/k256.rs @@ -16,7 +16,7 @@ use crate::{ buffer::{SecretBytes, WriteBuffer}, caps::{KeyCapSign, KeyCapVerify, SignatureType}, error::Error, - jwk::{JwkEncoder, KeyToJwk, KeyToJwkSecret}, + jwk::{JwkEncoder, KeyToJwk}, }; pub const ES256K_SIGNATURE_LENGTH: usize = 64; @@ -175,26 +175,16 @@ impl KeyToJwk for K256KeyPair { buffer.add_str("crv", JWK_CURVE)?; buffer.add_as_base64("x", &x[..])?; buffer.add_as_base64("y", &y[..])?; + if buffer.is_secret() { + if let Some(sk) = self.0.secret.as_ref() { + buffer.add_as_base64("d", &sk.to_bytes()[..])?; + } + } // buffer.add_str("use", "enc")?; Ok(()) } } -impl KeyToJwkSecret for K256KeyPair { - fn to_jwk_buffer_secret( - &self, - buffer: &mut JwkEncoder, - ) -> Result<(), Error> { - self.to_jwk_buffer(buffer)?; - if let Some(sk) = self.0.secret.as_ref() { - buffer.add_as_base64("d", &sk.to_bytes()[..])?; - Ok(()) - } else { - self.to_jwk_buffer(buffer) - } - } -} - // impl TryFrom<&AnyPrivateKey> for K256SigningKey { // type Error = Error; diff --git a/askar-crypto/src/alg/p256.rs b/askar-crypto/src/alg/p256.rs index 909ee370..9c0214f7 100644 --- a/askar-crypto/src/alg/p256.rs +++ b/askar-crypto/src/alg/p256.rs @@ -16,7 +16,7 @@ use crate::{ // any::{AnyPrivateKey, AnyPublicKey}, caps::{KeyCapSign, KeyCapVerify, SignatureType}, error::Error, - jwk::{JwkEncoder, KeyToJwk, KeyToJwkSecret}, + jwk::{JwkEncoder, KeyToJwk}, }; pub const ES256_SIGNATURE_LENGTH: usize = 64; @@ -175,26 +175,16 @@ impl KeyToJwk for P256KeyPair { buffer.add_str("crv", JWK_CURVE)?; buffer.add_as_base64("x", &x[..])?; buffer.add_as_base64("y", &y[..])?; + if buffer.is_secret() { + if let Some(sk) = self.0.secret.as_ref() { + buffer.add_as_base64("d", &sk.to_bytes()[..])?; + } + } // buffer.add_str("use", "enc")?; Ok(()) } } -impl KeyToJwkSecret for P256KeyPair { - fn to_jwk_buffer_secret( - &self, - buffer: &mut JwkEncoder, - ) -> Result<(), Error> { - self.to_jwk_buffer(buffer)?; - if let Some(sk) = self.0.secret.as_ref() { - buffer.add_as_base64("d", &sk.to_bytes()[..])?; - Ok(()) - } else { - self.to_jwk_buffer(buffer) - } - } -} - #[derive(Clone, Debug)] struct Keypair { // SECURITY: SecretKey zeroizes on drop diff --git a/askar-crypto/src/alg/x25519.rs b/askar-crypto/src/alg/x25519.rs index c37e9d99..4d672151 100644 --- a/askar-crypto/src/alg/x25519.rs +++ b/askar-crypto/src/alg/x25519.rs @@ -1,5 +1,8 @@ use alloc::boxed::Box; -use core::convert::TryInto; +use core::{ + convert::TryInto, + fmt::{self, Debug, Formatter}, +}; use rand::rngs::OsRng; use x25519_dalek::{PublicKey, StaticSecret as SecretKey}; @@ -8,7 +11,7 @@ use zeroize::Zeroize; use crate::{ buffer::{SecretBytes, WriteBuffer}, error::Error, - jwk::{JwkEncoder, KeyToJwk, KeyToJwkSecret}, + jwk::{JwkEncoder, KeyToJwk}, }; pub const PUBLIC_KEY_LENGTH: usize = 32; @@ -18,7 +21,6 @@ pub const KEYPAIR_LENGTH: usize = SECRET_KEY_LENGTH + PUBLIC_KEY_LENGTH; pub static JWK_CURVE: &'static str = "X25519"; #[derive(Clone)] -// FIXME implement zeroize pub struct X25519KeyPair(Box); impl X25519KeyPair { @@ -129,37 +131,43 @@ impl X25519KeyPair { } } +impl Debug for X25519KeyPair { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("X25519KeyPair") + .field( + "secret", + if self.0.secret.is_some() { + &"" + } else { + &"None" + }, + ) + .field("public", &self.0.public) + .finish() + } +} + impl KeyToJwk for X25519KeyPair { const KTY: &'static str = "OKP"; fn to_jwk_buffer(&self, buffer: &mut JwkEncoder) -> Result<(), Error> { buffer.add_str("crv", JWK_CURVE)?; buffer.add_as_base64("x", &self.to_public_key_bytes()[..])?; + if buffer.is_secret() { + if let Some(sk) = self.0.secret.as_ref() { + let mut sk = sk.to_bytes(); + buffer.add_as_base64("d", &sk[..])?; + sk.zeroize(); + } + } buffer.add_str("use", "enc")?; Ok(()) } } -impl KeyToJwkSecret for X25519KeyPair { - fn to_jwk_buffer_secret( - &self, - buffer: &mut JwkEncoder, - ) -> Result<(), Error> { - if let Some(sk) = self.0.secret.as_ref() { - let mut sk = sk.to_bytes(); - buffer.add_str("crv", JWK_CURVE)?; - buffer.add_as_base64("x", &self.to_public_key_bytes()[..])?; - buffer.add_as_base64("d", &sk[..])?; - sk.zeroize(); - Ok(()) - } else { - self.to_jwk_buffer(buffer) - } - } -} - #[derive(Clone)] struct Keypair { + // SECURITY: SecretKey (StaticSecret) zeroizes on drop secret: Option, public: PublicKey, } diff --git a/askar-crypto/src/jwk/encode.rs b/askar-crypto/src/jwk/encode.rs index 79738d46..3c46c2e6 100644 --- a/askar-crypto/src/jwk/encode.rs +++ b/askar-crypto/src/jwk/encode.rs @@ -24,14 +24,15 @@ impl bs58::encode::EncodeTarget for JwkBuffer<'_, B> { pub struct JwkEncoder<'b, B: WriteBuffer> { buffer: &'b mut B, + secret: bool, } impl<'b, B: WriteBuffer> JwkEncoder<'b, B> { - pub fn new(buffer: &'b mut B, kty: &str) -> Result { + pub fn new(buffer: &'b mut B, kty: &str, secret: bool) -> Result { buffer.extend_from_slice(b"{\"kty\":\"")?; buffer.extend_from_slice(kty.as_bytes())?; buffer.extend_from_slice(b"\"")?; - Ok(Self { buffer }) + Ok(Self { buffer, secret }) } pub fn add_str(&mut self, key: &str, value: &str) -> Result<(), Error> { @@ -78,6 +79,10 @@ impl<'b, B: WriteBuffer> JwkEncoder<'b, B> { Ok(()) } + pub fn is_secret(&self) -> bool { + self.secret + } + pub fn finalize(self) -> Result<(), Error> { self.buffer.extend_from_slice(b"}")?; Ok(()) diff --git a/askar-crypto/src/jwk/mod.rs b/askar-crypto/src/jwk/mod.rs index a4a7f36a..91fe4422 100644 --- a/askar-crypto/src/jwk/mod.rs +++ b/askar-crypto/src/jwk/mod.rs @@ -50,28 +50,23 @@ impl Zeroize for Jwk<'_> { pub trait KeyToJwk { const KTY: &'static str; + fn to_jwk_buffer(&self, buffer: &mut JwkEncoder) -> Result<(), Error>; + fn to_jwk(&self) -> Result, Error> { let mut v = Vec::with_capacity(128); - let mut buf = JwkEncoder::new(&mut v, Self::KTY)?; + let mut buf = JwkEncoder::new(&mut v, Self::KTY, false)?; self.to_jwk_buffer(&mut buf)?; buf.finalize()?; Ok(Jwk::Encoded(Cow::Owned(String::from_utf8(v).unwrap()))) } - fn to_jwk_buffer(&self, buffer: &mut JwkEncoder) -> Result<(), Error>; -} - -pub trait KeyToJwkSecret: KeyToJwk { fn to_jwk_secret(&self) -> Result, Error> { let mut v = Vec::with_capacity(128); - let mut buf = JwkEncoder::new(&mut v, Self::KTY)?; - self.to_jwk_buffer_secret(&mut buf)?; + let mut buf = JwkEncoder::new(&mut v, Self::KTY, true)?; + self.to_jwk_buffer(&mut buf)?; buf.finalize()?; Ok(Jwk::Encoded(Cow::Owned(String::from_utf8(v).unwrap()))) } - - fn to_jwk_buffer_secret(&self, buffer: &mut JwkEncoder) - -> Result<(), Error>; } // pub trait JwkBuilder<'s> { From 37dbbb9b33ee469bf3a9ccad516d1920ea8045ec Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Sat, 10 Apr 2021 19:24:28 -0700 Subject: [PATCH 012/116] update key traits, buffer handling Signed-off-by: Andrew Whitehead --- askar-crypto/src/alg/chacha.rs | 102 +++++++++++++++++++ askar-crypto/src/alg/ed25519.rs | 119 +++++++++++----------- askar-crypto/src/alg/k256.rs | 90 +++++++++-------- askar-crypto/src/alg/mod.rs | 2 + askar-crypto/src/alg/p256.rs | 90 +++++++++-------- askar-crypto/src/alg/x25519.rs | 126 +++++++++++------------ askar-crypto/src/buffer.rs | 171 ++++++++++++++++++++++++++------ askar-crypto/src/caps.rs | 41 ++++++-- askar-crypto/src/error.rs | 4 + askar-crypto/src/jwk/encode.rs | 65 ++++++------ askar-crypto/src/lib.rs | 14 ++- askar-crypto/src/random.rs | 40 ++++---- 12 files changed, 563 insertions(+), 301 deletions(-) create mode 100644 askar-crypto/src/alg/chacha.rs diff --git a/askar-crypto/src/alg/chacha.rs b/askar-crypto/src/alg/chacha.rs new file mode 100644 index 00000000..482cd0be --- /dev/null +++ b/askar-crypto/src/alg/chacha.rs @@ -0,0 +1,102 @@ +use alloc::boxed::Box; + +use zeroize::Zeroize; + +use crate::generic_array::typenum::U32; + +use crate::{ + buffer::{ArrayKey, WriteBuffer}, + caps::{KeyGen, KeySecretBytes}, + // any::{AnyPrivateKey, AnyPublicKey}, + error::Error, + jwk::{JwkEncoder, KeyToJwk}, + random::{fill_random, fill_random_deterministic}, +}; + +pub type KeyType = ArrayKey; + +pub static JWK_ALG: &'static str = "C20P"; + +pub const KEY_LENGTH: usize = KeyType::SIZE; + +#[derive(Clone, Debug, Zeroize)] +// SECURITY: ArrayKey is zeroized on drop +pub struct Chacha20Key(KeyType); + +impl Chacha20Key { + // this is consistent with Indy's wallet wrapping key generation + // FIXME: move to a trait to allow custom impl for Box + pub fn from_seed(seed: &[u8]) -> Result { + let mut slf = KeyType::default(); + fill_random_deterministic(seed, &mut slf)?; + Ok(Self(slf)) + } +} + +impl KeyGen for Chacha20Key { + fn generate() -> Result { + Ok(Chacha20Key(KeyType::random())) + } +} + +impl KeySecretBytes for Chacha20Key { + fn from_key_secret_bytes(key: &[u8]) -> Result { + if key.len() != KEY_LENGTH { + return Err(err_msg!("Invalid length for chacha20 key")); + } + Ok(Self(KeyType::from_slice(key))) + } + + fn to_key_secret_buffer(&self, out: &mut B) -> Result<(), Error> { + out.write_slice(&self.0[..]) + } +} + +#[inline] +pub fn init_boxed(f: impl FnOnce(&mut [u8])) -> Box { + let mut slf = Box::new(Chacha20Key(KeyType::default())); + f(&mut slf.0); + slf +} + +#[inline] +pub fn try_init_boxed( + f: impl FnOnce(&mut [u8]) -> Result<(), Error>, +) -> Result, Error> { + let mut slf = Box::new(Chacha20Key(KeyType::default())); + f(&mut slf.0)?; + Ok(slf) +} + +impl KeyGen for Box { + fn generate() -> Result { + Ok(init_boxed(fill_random)) + } +} + +impl KeySecretBytes for Box { + fn from_key_secret_bytes(key: &[u8]) -> Result { + if key.len() != KEY_LENGTH { + return Err(err_msg!("Invalid length for chacha20 key")); + } + Ok(init_boxed(|buf| buf.copy_from_slice(key))) + } + + fn to_key_secret_buffer(&self, out: &mut B) -> Result<(), Error> { + out.write_slice(&self.0[..]) + } +} + +impl KeyToJwk for Chacha20Key { + const KTY: &'static str = "oct"; + + fn to_jwk_buffer(&self, buffer: &mut JwkEncoder) -> Result<(), Error> { + if !buffer.is_secret() { + return Err(err_msg!(Unsupported, "Cannot export as a public key")); + } + buffer.add_str("alg", JWK_ALG)?; + buffer.add_as_base64("k", &self.0[..])?; + buffer.add_str("use", "enc")?; + Ok(()) + } +} diff --git a/askar-crypto/src/alg/ed25519.rs b/askar-crypto/src/alg/ed25519.rs index 36035a2d..75b7cf09 100644 --- a/askar-crypto/src/alg/ed25519.rs +++ b/askar-crypto/src/alg/ed25519.rs @@ -1,4 +1,3 @@ -use alloc::boxed::Box; use core::{ convert::{TryFrom, TryInto}, fmt::{self, Debug, Formatter}, @@ -14,7 +13,7 @@ use super::x25519::X25519KeyPair; use crate::{ // any::{AnyPrivateKey, AnyPublicKey}, buffer::{SecretBytes, WriteBuffer}, - caps::{KeyCapSign, KeyCapVerify, SignatureType}, + caps::{KeyCapSign, KeyCapVerify, KeyGen, KeySecretBytes, SignatureType}, error::Error, jwk::{JwkEncoder, KeyToJwk}, }; @@ -30,17 +29,13 @@ pub const KEYPAIR_LENGTH: usize = SECRET_KEY_LENGTH + PUBLIC_KEY_LENGTH; pub static JWK_CURVE: &'static str = "Ed25519"; -pub struct Ed25519KeyPair(Box); +pub struct Ed25519KeyPair { + // SECURITY: SecretKey zeroizes on drop + secret: Option, + public: PublicKey, +} impl Ed25519KeyPair { - pub fn generate() -> Result { - Ok(Self::from_secret_key(SecretKey::generate(&mut OsRng))) - } - - pub fn from_seed(ikm: &[u8]) -> Result { - Self::from_secret_key_bytes(ikm) - } - pub fn from_keypair_bytes(kp: &[u8]) -> Result { if kp.len() != KEYPAIR_LENGTH { return Err(err_msg!("Invalid keypair bytes")); @@ -51,44 +46,36 @@ impl Ed25519KeyPair { .map_err(|_| err_msg!("Invalid ed25519 public key bytes"))?; // FIXME: derive pk from sk and check value? - Ok(Self(Box::new(Keypair { + Ok(Self { secret: Some(sk), public: pk, - }))) + }) } pub fn from_public_key_bytes(key: &[u8]) -> Result { if key.len() != PUBLIC_KEY_LENGTH { return Err(err_msg!("Invalid ed25519 public key length")); } - Ok(Self(Box::new(Keypair { + Ok(Self { secret: None, public: PublicKey::from_bytes(key).map_err(|_| err_msg!("Invalid keypair bytes"))?, - }))) - } - - pub fn from_secret_key_bytes(key: &[u8]) -> Result { - if key.len() != SECRET_KEY_LENGTH { - return Err(err_msg!("Invalid ed25519 secret key length")); - } - let sk = SecretKey::from_bytes(key).expect("Error loading ed25519 key"); - Ok(Self::from_secret_key(sk)) + }) } #[inline] pub(crate) fn from_secret_key(sk: SecretKey) -> Self { let public = PublicKey::from(&sk); - Self(Box::new(Keypair { + Self { secret: Some(sk), public, - })) + } } pub fn to_keypair_bytes(&self) -> Option { - if let Some(secret) = self.0.secret.as_ref() { + if let Some(secret) = self.secret.as_ref() { let output = SecretBytes::new_with(KEYPAIR_LENGTH, |buf| { buf[..SECRET_KEY_LENGTH].copy_from_slice(secret.as_bytes()); - buf[SECRET_KEY_LENGTH..].copy_from_slice(self.0.public.as_bytes()); + buf[SECRET_KEY_LENGTH..].copy_from_slice(self.public.as_bytes()); }); Some(output) } else { @@ -97,25 +84,17 @@ impl Ed25519KeyPair { } pub fn to_public_key_bytes(&self) -> [u8; PUBLIC_KEY_LENGTH] { - self.0.public.to_bytes() - } - - pub fn to_secret_key_bytes(&self) -> Option { - self.0 - .secret - .as_ref() - .map(|sk| SecretBytes::from_slice(sk.as_bytes())) + self.public.to_bytes() } pub fn to_signing_key(&self) -> Option> { - self.0 - .secret + self.secret .as_ref() - .map(|sk| Ed25519SigningKey(ExpandedSecretKey::from(sk), &self.0.public)) + .map(|sk| Ed25519SigningKey(ExpandedSecretKey::from(sk), &self.public)) } pub fn to_x25519_keypair(&self) -> X25519KeyPair { - if let Some(secret) = self.0.secret.as_ref() { + if let Some(secret) = self.secret.as_ref() { let hash = sha2::Sha512::digest(secret.as_bytes()); // clamp result let secret = XSecretKey::from(TryInto::<[u8; 32]>::try_into(&hash[..32]).unwrap()); @@ -123,7 +102,7 @@ impl Ed25519KeyPair { X25519KeyPair::new(Some(secret), public) } else { let public = XPublicKey::from( - CompressedEdwardsY(self.0.public.to_bytes()) + CompressedEdwardsY(self.public.to_bytes()) .decompress() .unwrap() .to_montgomery() @@ -139,7 +118,7 @@ impl Ed25519KeyPair { pub fn verify_signature(&self, message: &[u8], signature: &[u8]) -> bool { if let Ok(sig) = Signature::try_from(signature) { - self.0.public.verify_strict(message, &sig).is_ok() + self.public.verify_strict(message, &sig).is_ok() } else { false } @@ -148,14 +127,13 @@ impl Ed25519KeyPair { impl Clone for Ed25519KeyPair { fn clone(&self) -> Self { - Self(Box::new(Keypair { + Self { secret: self - .0 .secret .as_ref() .map(|sk| SecretKey::from_bytes(&sk.as_bytes()[..]).unwrap()), - public: self.0.public.clone(), - })) + public: self.public.clone(), + } } } @@ -164,32 +142,56 @@ impl Debug for Ed25519KeyPair { f.debug_struct("Ed25519KeyPair") .field( "secret", - if self.0.secret.is_some() { + if self.secret.is_some() { &"" } else { &"None" }, ) - .field("public", &self.0.public) + .field("public", &self.public) .finish() } } +impl KeyGen for Ed25519KeyPair { + fn generate() -> Result { + Ok(Self::from_secret_key(SecretKey::generate(&mut OsRng))) + } +} + +impl KeySecretBytes for Ed25519KeyPair { + fn from_key_secret_bytes(key: &[u8]) -> Result { + if key.len() != SECRET_KEY_LENGTH { + return Err(err_msg!("Invalid ed25519 secret key length")); + } + let sk = SecretKey::from_bytes(key).expect("Error loading ed25519 key"); + Ok(Self::from_secret_key(sk)) + } + + fn to_key_secret_buffer(&self, out: &mut B) -> Result<(), Error> { + if let Some(sk) = self.secret.as_ref() { + out.write_slice(sk.as_bytes()) + } else { + Err(err_msg!(MissingSecretKey)) + } + } +} + impl KeyCapSign for Ed25519KeyPair { - fn key_sign( + fn key_sign_buffer( &self, data: &[u8], sig_type: Option, out: &mut B, - ) -> Result { + ) -> Result<(), Error> { match sig_type { None | Some(SignatureType::EdDSA) => { if let Some(signer) = self.to_signing_key() { let sig = signer.sign(data); - out.extend_from_slice(&sig[..])?; - Ok(EDDSA_SIGNATURE_LENGTH) + out.write_slice(&sig[..])?; + Ok(()) } else { - Err(err_msg!(Unsupported, "Undefined secret key")) + Err(err_msg!(MissingSecretKey)) } } #[allow(unreachable_patterns)] @@ -220,7 +222,7 @@ impl KeyToJwk for Ed25519KeyPair { buffer.add_str("crv", JWK_CURVE)?; buffer.add_as_base64("x", &self.to_public_key_bytes()[..])?; if buffer.is_secret() { - if let Some(sk) = self.0.secret.as_ref() { + if let Some(sk) = self.secret.as_ref() { buffer.add_as_base64("d", sk.as_bytes())?; } } @@ -229,12 +231,6 @@ impl KeyToJwk for Ed25519KeyPair { } } -struct Keypair { - // SECURITY: SecretKey zeroizes on drop - secret: Option, - public: PublicKey, -} - /// FIXME implement debug // SECURITY: ExpandedSecretKey zeroizes on drop pub struct Ed25519SigningKey<'p>(ExpandedSecretKey, &'p PublicKey); @@ -266,8 +262,9 @@ mod tests { let seed = b"000000000000000000000000Trustee1"; let test_sk = &hex!("3030303030303030303030303030303030303030303030305472757374656531e33aaf381fffa6109ad591fdc38717945f8fabf7abf02086ae401c63e9913097"); - let kp = Ed25519KeyPair::from_seed(seed).unwrap(); + let kp = Ed25519KeyPair::from_key_secret_bytes(seed).unwrap(); assert_eq!(kp.to_keypair_bytes().unwrap(), &test_sk[..]); + assert_eq!(kp.to_key_secret_bytes().unwrap(), &seed[..]); // test round trip let cmp = Ed25519KeyPair::from_keypair_bytes(test_sk).unwrap(); @@ -303,7 +300,7 @@ mod tests { let test_pub_b64 = "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"; let test_pvt = base64::decode_config(test_pvt_b64, base64::URL_SAFE).unwrap(); let kp = - Ed25519KeyPair::from_secret_key_bytes(&test_pvt).expect("Error creating signing key"); + Ed25519KeyPair::from_key_secret_bytes(&test_pvt).expect("Error creating signing key"); let jwk = kp.to_jwk().expect("Error converting public key to JWK"); let jwk = jwk.to_parts().expect("Error parsing JWK output"); assert_eq!(jwk.kty, "OKP"); diff --git a/askar-crypto/src/alg/k256.rs b/askar-crypto/src/alg/k256.rs index b5aba6bc..49fcb827 100644 --- a/askar-crypto/src/alg/k256.rs +++ b/askar-crypto/src/alg/k256.rs @@ -1,4 +1,3 @@ -use alloc::boxed::Box; use core::convert::{TryFrom, TryInto}; use k256::{ @@ -9,14 +8,14 @@ use k256::{ elliptic_curve::{ecdh::diffie_hellman, sec1::Coordinates}, EncodedPoint, PublicKey, SecretKey, }; -use rand::rngs::OsRng; use crate::{ // any::{AnyPrivateKey, AnyPublicKey}, buffer::{SecretBytes, WriteBuffer}, - caps::{KeyCapSign, KeyCapVerify, SignatureType}, + caps::{KeyCapSign, KeyCapVerify, KeyGen, KeySecretBytes, SignatureType}, error::Error, jwk::{JwkEncoder, KeyToJwk}, + random::with_rng, }; pub const ES256K_SIGNATURE_LENGTH: usize = 64; @@ -28,13 +27,13 @@ pub const KEYPAIR_LENGTH: usize = SECRET_KEY_LENGTH + PUBLIC_KEY_LENGTH; pub static JWK_CURVE: &'static str = "secp256k1"; #[derive(Clone, Debug)] -pub struct K256KeyPair(Box); +pub struct K256KeyPair { + // SECURITY: SecretKey zeroizes on drop + secret: Option, + public: PublicKey, +} impl K256KeyPair { - pub fn generate() -> Result { - Ok(Self::from_secret_key(SecretKey::random(OsRng))) - } - pub fn from_keypair_bytes(kp: &[u8]) -> Result { if kp.len() != KEYPAIR_LENGTH { return Err(err_msg!("Invalid keypair bytes")); @@ -46,41 +45,35 @@ impl K256KeyPair { .map_err(|_| err_msg!("Invalid k-256 public key bytes"))?; // FIXME: derive pk from sk and check value? - Ok(Self(Box::new(Keypair { + Ok(Self { secret: Some(sk), public: pk, - }))) + }) } pub fn from_public_key_bytes(key: &[u8]) -> Result { let pk = EncodedPoint::from_bytes(key) .and_then(|pt| pt.decode()) .map_err(|_| err_msg!("Invalid k-256 public key bytes"))?; - Ok(Self(Box::new(Keypair { + Ok(Self { secret: None, public: pk, - }))) - } - - pub fn from_secret_key_bytes(key: &[u8]) -> Result { - Ok(Self::from_secret_key( - SecretKey::from_bytes(key).map_err(|_| err_msg!("Invalid k-256 secret key bytes"))?, - )) + }) } #[inline] pub(crate) fn from_secret_key(sk: SecretKey) -> Self { let pk = sk.public_key(); - Self(Box::new(Keypair { + Self { secret: Some(sk), public: pk, - })) + } } pub fn key_exchange_with(&self, other: &Self) -> Option { - match self.0.secret.as_ref() { + match self.secret.as_ref() { Some(sk) => { - let xk = diffie_hellman(sk.secret_scalar(), other.0.public.as_affine()); + let xk = diffie_hellman(sk.secret_scalar(), other.public.as_affine()); Some(SecretBytes::from(xk.as_bytes().to_vec())) } None => None, @@ -88,8 +81,8 @@ impl K256KeyPair { } pub fn to_keypair_bytes(&self) -> Option { - if let Some(secret) = self.0.secret.as_ref() { - let encp = EncodedPoint::encode(self.0.public, true); + if let Some(secret) = self.secret.as_ref() { + let encp = EncodedPoint::encode(self.public, true); let output = SecretBytes::new_with(KEYPAIR_LENGTH, |buf| { buf[..SECRET_KEY_LENGTH].copy_from_slice(&secret.to_bytes()[..]); buf[SECRET_KEY_LENGTH..].copy_from_slice(encp.as_ref()); @@ -101,7 +94,7 @@ impl K256KeyPair { } pub(crate) fn to_signing_key(&self) -> Option { - self.0.secret.as_ref().map(SigningKey::from) + self.secret.as_ref().map(SigningKey::from) } pub fn sign(&self, message: &[u8]) -> Option<[u8; ES256K_SIGNATURE_LENGTH]> { @@ -116,7 +109,7 @@ impl K256KeyPair { pub fn verify_signature(&self, message: &[u8], signature: &[u8]) -> bool { if let Ok(sig) = Signature::try_from(signature) { - let vk = VerifyingKey::from(self.0.public.as_affine()); + let vk = VerifyingKey::from(self.public.as_affine()); vk.verify(message, &sig).is_ok() } else { false @@ -124,18 +117,40 @@ impl K256KeyPair { } } +impl KeyGen for K256KeyPair { + fn generate() -> Result { + Ok(Self::from_secret_key(with_rng(|r| SecretKey::random(r)))) + } +} + +impl KeySecretBytes for K256KeyPair { + fn from_key_secret_bytes(key: &[u8]) -> Result { + Ok(Self::from_secret_key( + SecretKey::from_bytes(key).map_err(|_| err_msg!("Invalid k-256 secret key bytes"))?, + )) + } + + fn to_key_secret_buffer(&self, out: &mut B) -> Result<(), Error> { + if let Some(sk) = self.secret.as_ref() { + out.write_slice(&sk.to_bytes()[..]) + } else { + Err(err_msg!(MissingSecretKey)) + } + } +} + impl KeyCapSign for K256KeyPair { - fn key_sign( + fn key_sign_buffer( &self, message: &[u8], sig_type: Option, out: &mut B, - ) -> Result { + ) -> Result<(), Error> { match sig_type { None | Some(SignatureType::ES256K) => { if let Some(sig) = self.sign(message) { - out.extend_from_slice(&sig[..])?; - Ok(ES256K_SIGNATURE_LENGTH) + out.write_slice(&sig[..])?; + Ok(()) } else { Err(err_msg!(Unsupported, "Undefined secret key")) } @@ -165,7 +180,7 @@ impl KeyToJwk for K256KeyPair { const KTY: &'static str = "EC"; fn to_jwk_buffer(&self, buffer: &mut JwkEncoder) -> Result<(), Error> { - let encp = EncodedPoint::encode(self.0.public, false); + let encp = EncodedPoint::encode(self.public, false); let (x, y) = match encp.coordinates() { Coordinates::Identity => return Err(err_msg!("Cannot convert identity point to JWK")), Coordinates::Uncompressed { x, y } => (x, y), @@ -176,7 +191,7 @@ impl KeyToJwk for K256KeyPair { buffer.add_as_base64("x", &x[..])?; buffer.add_as_base64("y", &y[..])?; if buffer.is_secret() { - if let Some(sk) = self.0.secret.as_ref() { + if let Some(sk) = self.secret.as_ref() { buffer.add_as_base64("d", &sk.to_bytes()[..])?; } } @@ -197,13 +212,6 @@ impl KeyToJwk for K256KeyPair { // } // } -#[derive(Clone, Debug)] -struct Keypair { - // SECURITY: SecretKey zeroizes on drop - secret: Option, - public: PublicKey, -} - #[cfg(test)] mod tests { use super::*; @@ -225,7 +233,7 @@ mod tests { "36uMVGM7hnw-N6GnjFcihWE3SkrhMLzzLCdPMXPEXlA", ); let test_pvt = base64::decode_config(test_pvt_b64, base64::URL_SAFE).unwrap(); - let sk = K256KeyPair::from_secret_key_bytes(&test_pvt).expect("Error creating signing key"); + let sk = K256KeyPair::from_key_secret_bytes(&test_pvt).expect("Error creating signing key"); let jwk = sk.to_jwk().expect("Error converting key to JWK"); let jwk = jwk.to_parts().expect("Error parsing JWK"); @@ -256,7 +264,7 @@ mod tests { base64::URL_SAFE_NO_PAD, ) .unwrap(); - let kp = K256KeyPair::from_secret_key_bytes(&test_pvt).unwrap(); + let kp = K256KeyPair::from_key_secret_bytes(&test_pvt).unwrap(); let sig = kp.sign(&test_msg[..]).unwrap(); assert_eq!(sig, &test_sig[..]); assert_eq!(kp.verify_signature(&test_msg[..], &sig[..]), true); diff --git a/askar-crypto/src/alg/mod.rs b/askar-crypto/src/alg/mod.rs index 49cbf9a5..0f0aea6f 100644 --- a/askar-crypto/src/alg/mod.rs +++ b/askar-crypto/src/alg/mod.rs @@ -1,5 +1,7 @@ // pub mod bls; +pub mod chacha; + pub mod ed25519; pub mod x25519; diff --git a/askar-crypto/src/alg/p256.rs b/askar-crypto/src/alg/p256.rs index 9c0214f7..a75b5747 100644 --- a/askar-crypto/src/alg/p256.rs +++ b/askar-crypto/src/alg/p256.rs @@ -1,4 +1,3 @@ -use alloc::boxed::Box; use core::convert::{TryFrom, TryInto}; use p256::{ @@ -9,14 +8,14 @@ use p256::{ elliptic_curve::{ecdh::diffie_hellman, sec1::Coordinates}, EncodedPoint, PublicKey, SecretKey, }; -use rand::rngs::OsRng; use crate::{ buffer::{SecretBytes, WriteBuffer}, // any::{AnyPrivateKey, AnyPublicKey}, - caps::{KeyCapSign, KeyCapVerify, SignatureType}, + caps::{KeyCapSign, KeyCapVerify, KeyGen, KeySecretBytes, SignatureType}, error::Error, jwk::{JwkEncoder, KeyToJwk}, + random::with_rng, }; pub const ES256_SIGNATURE_LENGTH: usize = 64; @@ -28,13 +27,13 @@ pub const KEYPAIR_LENGTH: usize = SECRET_KEY_LENGTH + PUBLIC_KEY_LENGTH; pub static JWK_CURVE: &'static str = "P-256"; #[derive(Clone, Debug)] -pub struct P256KeyPair(Box); +pub struct P256KeyPair { + // SECURITY: SecretKey zeroizes on drop + secret: Option, + public: PublicKey, +} impl P256KeyPair { - pub fn generate() -> Result { - Ok(Self::from_secret_key(SecretKey::random(OsRng))) - } - pub fn from_keypair_bytes(kp: &[u8]) -> Result { if kp.len() != KEYPAIR_LENGTH { return Err(err_msg!("Invalid keypair bytes")); @@ -46,41 +45,35 @@ impl P256KeyPair { .map_err(|_| err_msg!("Invalid p-256 public key bytes"))?; // FIXME: derive pk from sk and check value? - Ok(Self(Box::new(Keypair { + Ok(Self { secret: Some(sk), public: pk, - }))) + }) } pub fn from_public_key_bytes(key: &[u8]) -> Result { let pk = EncodedPoint::from_bytes(key) .and_then(|pt| pt.decode()) .map_err(|_| err_msg!("Invalid p-256 public key bytes"))?; - Ok(Self(Box::new(Keypair { + Ok(Self { secret: None, public: pk, - }))) - } - - pub fn from_secret_key_bytes(key: &[u8]) -> Result { - Ok(Self::from_secret_key( - SecretKey::from_bytes(key).map_err(|_| err_msg!("Invalid k-256 secret key bytes"))?, - )) + }) } #[inline] pub(crate) fn from_secret_key(sk: SecretKey) -> Self { let pk = sk.public_key(); - Self(Box::new(Keypair { + Self { secret: Some(sk), public: pk, - })) + } } pub fn key_exchange_with(&self, other: &Self) -> Option { - match self.0.secret.as_ref() { + match self.secret.as_ref() { Some(sk) => { - let xk = diffie_hellman(sk.secret_scalar(), other.0.public.as_affine()); + let xk = diffie_hellman(sk.secret_scalar(), other.public.as_affine()); Some(SecretBytes::from(xk.as_bytes().to_vec())) } None => None, @@ -88,8 +81,8 @@ impl P256KeyPair { } pub fn to_keypair_bytes(&self) -> Option { - if let Some(secret) = self.0.secret.as_ref() { - let encp = EncodedPoint::encode(self.0.public, true); + if let Some(secret) = self.secret.as_ref() { + let encp = EncodedPoint::encode(self.public, true); let output = SecretBytes::new_with(KEYPAIR_LENGTH, |buf| { buf[..SECRET_KEY_LENGTH].copy_from_slice(&secret.to_bytes()[..]); buf[SECRET_KEY_LENGTH..].copy_from_slice(encp.as_ref()); @@ -101,7 +94,7 @@ impl P256KeyPair { } pub(crate) fn to_signing_key(&self) -> Option { - self.0.secret.clone().map(SigningKey::from) + self.secret.clone().map(SigningKey::from) } pub fn sign(&self, message: &[u8]) -> Option<[u8; ES256_SIGNATURE_LENGTH]> { @@ -116,7 +109,7 @@ impl P256KeyPair { pub fn verify_signature(&self, message: &[u8], signature: &[u8]) -> bool { if let Ok(sig) = Signature::try_from(signature) { - let vk = VerifyingKey::from(&self.0.public); + let vk = VerifyingKey::from(&self.public); vk.verify(message, &sig).is_ok() } else { false @@ -124,18 +117,40 @@ impl P256KeyPair { } } +impl KeyGen for P256KeyPair { + fn generate() -> Result { + Ok(Self::from_secret_key(with_rng(|r| SecretKey::random(r)))) + } +} + +impl KeySecretBytes for P256KeyPair { + fn from_key_secret_bytes(key: &[u8]) -> Result { + Ok(Self::from_secret_key( + SecretKey::from_bytes(key).map_err(|_| err_msg!("Invalid p-256 secret key bytes"))?, + )) + } + + fn to_key_secret_buffer(&self, out: &mut B) -> Result<(), Error> { + if let Some(sk) = self.secret.as_ref() { + out.write_slice(&sk.to_bytes()[..]) + } else { + Err(err_msg!(MissingSecretKey)) + } + } +} + impl KeyCapSign for P256KeyPair { - fn key_sign( + fn key_sign_buffer( &self, message: &[u8], sig_type: Option, out: &mut B, - ) -> Result { + ) -> Result<(), Error> { match sig_type { None | Some(SignatureType::ES256K) => { if let Some(sig) = self.sign(message) { - out.extend_from_slice(&sig[..])?; - Ok(ES256_SIGNATURE_LENGTH) + out.write_slice(&sig[..])?; + Ok(()) } else { Err(err_msg!(Unsupported, "Undefined secret key")) } @@ -165,7 +180,7 @@ impl KeyToJwk for P256KeyPair { const KTY: &'static str = "EC"; fn to_jwk_buffer(&self, buffer: &mut JwkEncoder) -> Result<(), Error> { - let encp = EncodedPoint::encode(self.0.public, false); + let encp = EncodedPoint::encode(self.public, false); let (x, y) = match encp.coordinates() { Coordinates::Identity => return Err(err_msg!("Cannot convert identity point to JWK")), Coordinates::Uncompressed { x, y } => (x, y), @@ -176,7 +191,7 @@ impl KeyToJwk for P256KeyPair { buffer.add_as_base64("x", &x[..])?; buffer.add_as_base64("y", &y[..])?; if buffer.is_secret() { - if let Some(sk) = self.0.secret.as_ref() { + if let Some(sk) = self.secret.as_ref() { buffer.add_as_base64("d", &sk.to_bytes()[..])?; } } @@ -185,13 +200,6 @@ impl KeyToJwk for P256KeyPair { } } -#[derive(Clone, Debug)] -struct Keypair { - // SECURITY: SecretKey zeroizes on drop - secret: Option, - public: PublicKey, -} - #[cfg(test)] mod tests { use super::*; @@ -211,7 +219,7 @@ mod tests { "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0", ); let test_pvt = base64::decode_config(test_pvt_b64, base64::URL_SAFE).unwrap(); - let sk = P256KeyPair::from_secret_key_bytes(&test_pvt).expect("Error creating signing key"); + let sk = P256KeyPair::from_key_secret_bytes(&test_pvt).expect("Error creating signing key"); let jwk = sk.to_jwk().expect("Error converting key to JWK"); let jwk = jwk.to_parts().expect("Error parsing JWK"); @@ -242,7 +250,7 @@ mod tests { base64::URL_SAFE_NO_PAD, ) .unwrap(); - let kp = P256KeyPair::from_secret_key_bytes(&test_pvt).unwrap(); + let kp = P256KeyPair::from_key_secret_bytes(&test_pvt).unwrap(); let sig = kp.sign(&test_msg[..]).unwrap(); assert_eq!(sig, &test_sig[..]); assert_eq!(kp.verify_signature(&test_msg[..], &sig[..]), true); diff --git a/askar-crypto/src/alg/x25519.rs b/askar-crypto/src/alg/x25519.rs index 4d672151..4ad140f4 100644 --- a/askar-crypto/src/alg/x25519.rs +++ b/askar-crypto/src/alg/x25519.rs @@ -1,4 +1,3 @@ -use alloc::boxed::Box; use core::{ convert::TryInto, fmt::{self, Debug, Formatter}, @@ -10,6 +9,7 @@ use zeroize::Zeroize; use crate::{ buffer::{SecretBytes, WriteBuffer}, + caps::{KeyGen, KeySecretBytes}, error::Error, jwk::{JwkEncoder, KeyToJwk}, }; @@ -21,30 +21,28 @@ pub const KEYPAIR_LENGTH: usize = SECRET_KEY_LENGTH + PUBLIC_KEY_LENGTH; pub static JWK_CURVE: &'static str = "X25519"; #[derive(Clone)] -pub struct X25519KeyPair(Box); +pub struct X25519KeyPair { + // SECURITY: SecretKey (StaticSecret) zeroizes on drop + secret: Option, + public: PublicKey, +} impl X25519KeyPair { #[inline(always)] pub(crate) fn new(sk: Option, pk: PublicKey) -> Self { - Self(Box::new(Keypair { + Self { secret: sk, public: pk, - })) - } - - pub fn generate() -> Result { - let sk = SecretKey::new(OsRng); - let pk = PublicKey::from(&sk); - Ok(Self::new(Some(sk), pk)) + } } - pub fn key_exchange_with(&self, other: &Self) -> Option { - match self.0.secret.as_ref() { + pub fn key_exchange_with(&self, other: &Self) -> Result { + match self.secret.as_ref() { Some(sk) => { - let xk = sk.diffie_hellman(&other.0.public); - Some(SecretBytes::from(xk.as_bytes().to_vec())) + let xk = sk.diffie_hellman(&other.public); + Ok(SecretBytes::from(xk.as_bytes().to_vec())) } - None => None, + None => Err(err_msg!(MissingSecretKey)), } } @@ -60,19 +58,19 @@ impl X25519KeyPair { ); // FIXME: derive pk from sk and check value? - Ok(Self(Box::new(Keypair { + Ok(Self { secret: Some(sk), public: pk, - }))) + }) } #[inline] pub(crate) fn from_secret_key(sk: SecretKey) -> Self { let public = PublicKey::from(&sk); - Self(Box::new(Keypair { + Self { secret: Some(sk), public, - })) + } } pub fn from_public_key_bytes(key: &[u8]) -> Result { @@ -85,44 +83,15 @@ impl X25519KeyPair { )) } - pub fn from_secret_key_bytes(key: &[u8]) -> Result { - if key.len() != SECRET_KEY_LENGTH { - return Err(err_msg!("Invalid x25519 key length")); - } - - // pre-check key to ensure that clamping has no effect - if key[0] & 7 != 0 || (key[31] & 127 | 64) != key[31] { - return Err(err_msg!("invalid x25519 secret key")); - } - - let sk = SecretKey::from(TryInto::<[u8; SECRET_KEY_LENGTH]>::try_into(key).unwrap()); - - // post-check key - // let mut check = sk.to_bytes(); - // if &check[..] != key { - // return Err(err_msg!("invalid x25519 secret key")); - // } - // check.zeroize(); - - Ok(Self::from_secret_key(sk)) - } - pub fn to_public_key_bytes(&self) -> [u8; PUBLIC_KEY_LENGTH] { - self.0.public.to_bytes() - } - - pub fn to_secret_key_bytes(&self) -> Option { - self.0 - .secret - .as_ref() - .map(|sk| SecretBytes::from_slice(&sk.to_bytes()[..])) + self.public.to_bytes() } pub fn to_keypair_bytes(&self) -> Option { - if let Some(secret) = self.0.secret.as_ref() { + if let Some(secret) = self.secret.as_ref() { let output = SecretBytes::new_with(KEYPAIR_LENGTH, |buf| { buf[..SECRET_KEY_LENGTH].copy_from_slice(&secret.to_bytes()[..]); - buf[SECRET_KEY_LENGTH..].copy_from_slice(self.0.public.as_bytes()); + buf[SECRET_KEY_LENGTH..].copy_from_slice(self.public.as_bytes()); }); Some(output) } else { @@ -136,17 +105,57 @@ impl Debug for X25519KeyPair { f.debug_struct("X25519KeyPair") .field( "secret", - if self.0.secret.is_some() { + if self.secret.is_some() { &"" } else { &"None" }, ) - .field("public", &self.0.public) + .field("public", &self.public) .finish() } } +impl KeyGen for X25519KeyPair { + fn generate() -> Result { + let sk = SecretKey::new(OsRng); + let pk = PublicKey::from(&sk); + Ok(Self::new(Some(sk), pk)) + } +} + +impl KeySecretBytes for X25519KeyPair { + fn from_key_secret_bytes(key: &[u8]) -> Result { + if key.len() != SECRET_KEY_LENGTH { + return Err(err_msg!("Invalid x25519 key length")); + } + + // pre-check key to ensure that clamping has no effect + if key[0] & 7 != 0 || (key[31] & 127 | 64) != key[31] { + return Err(err_msg!("invalid x25519 secret key")); + } + + let sk = SecretKey::from(TryInto::<[u8; SECRET_KEY_LENGTH]>::try_into(key).unwrap()); + + // post-check key + // let mut check = sk.to_bytes(); + // if &check[..] != key { + // return Err(err_msg!("invalid x25519 secret key")); + // } + // check.zeroize(); + + Ok(Self::from_secret_key(sk)) + } + + fn to_key_secret_buffer(&self, out: &mut B) -> Result<(), Error> { + if let Some(sk) = self.secret.as_ref() { + out.write_slice(&sk.to_bytes()[..]) + } else { + Err(err_msg!(MissingSecretKey)) + } + } +} + impl KeyToJwk for X25519KeyPair { const KTY: &'static str = "OKP"; @@ -154,7 +163,7 @@ impl KeyToJwk for X25519KeyPair { buffer.add_str("crv", JWK_CURVE)?; buffer.add_as_base64("x", &self.to_public_key_bytes()[..])?; if buffer.is_secret() { - if let Some(sk) = self.0.secret.as_ref() { + if let Some(sk) = self.secret.as_ref() { let mut sk = sk.to_bytes(); buffer.add_as_base64("d", &sk[..])?; sk.zeroize(); @@ -165,13 +174,6 @@ impl KeyToJwk for X25519KeyPair { } } -#[derive(Clone)] -struct Keypair { - // SECURITY: SecretKey (StaticSecret) zeroizes on drop - secret: Option, - public: PublicKey, -} - // impl TryFrom<&AnyPrivateKey> for X25519KeyPair { // type Error = Error; @@ -200,7 +202,7 @@ mod tests { let test_pvt_b64 = "qL25gw-HkNJC9m4EsRzCoUx1KntjwHPzxo6a2xUcyFQ"; let test_pvt = base64::decode_config(test_pvt_b64, base64::URL_SAFE).unwrap(); let kp = - X25519KeyPair::from_secret_key_bytes(&test_pvt).expect("Error creating x25519 keypair"); + X25519KeyPair::from_key_secret_bytes(&test_pvt).expect("Error creating x25519 keypair"); let jwk = kp.to_jwk().expect("Error converting public key to JWK"); let jwk = jwk.to_parts().expect("Error parsing JWK output"); assert_eq!(jwk.kty, "OKP"); diff --git a/askar-crypto/src/buffer.rs b/askar-crypto/src/buffer.rs index 0706e86d..28fb8efe 100644 --- a/askar-crypto/src/buffer.rs +++ b/askar-crypto/src/buffer.rs @@ -10,27 +10,23 @@ use core::{ mem::{self, ManuallyDrop}, ops::{Deref, DerefMut}, }; -use fmt::Write; -use aead::{ - generic_array::{typenum, ArrayLength, GenericArray}, - Buffer, -}; +use crate::generic_array::{typenum, ArrayLength, GenericArray}; use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; use zeroize::Zeroize; -use crate::{error::Error, random::random_array}; +use crate::{error::Error, random::fill_random}; /// A secure key representation for fixed-length keys -#[derive(Clone, Debug, Hash, Zeroize)] +#[derive(Clone, Hash, Zeroize)] pub struct ArrayKey>(GenericArray); impl> ArrayKey { pub const SIZE: usize = L::USIZE; #[inline] - pub fn from_slice>(data: D) -> Self { - Self(GenericArray::clone_from_slice(data.as_ref())) + pub fn copy_from_slice>(&mut self, data: D) { + self.0[..].copy_from_slice(data.as_ref()); } #[inline] @@ -38,9 +34,17 @@ impl> ArrayKey { self.0.clone() } + #[inline] + pub fn from_slice(data: &[u8]) -> Self { + // like <&GenericArray>::from_slice, panics if the length is incorrect + Self(GenericArray::from_slice(data).clone()) + } + #[inline] pub fn random() -> Self { - Self(random_array()) + let mut slf = GenericArray::default(); + fill_random(&mut slf); + Self(slf) } } @@ -57,6 +61,16 @@ impl> From> for ArrayKey { } } +impl> Debug for ArrayKey { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + if cfg!(test) { + f.debug_tuple("ArrayKey").field(&*self).finish() + } else { + f.debug_tuple("ArrayKey").field(&"").finish() + } + } +} + impl> Deref for ArrayKey { type Target = GenericArray; fn deref(&self) -> &Self::Target { @@ -367,7 +381,7 @@ impl SecretBytesMut<'_> { } pub fn resize(&mut self, new_len: usize) { - let len = self.len(); + let len = self.0.len(); if new_len > len { self.reserve(new_len - len); self.0.resize(new_len, 0u8); @@ -377,7 +391,7 @@ impl SecretBytesMut<'_> { } } -impl Buffer for SecretBytesMut<'_> { +impl aead::Buffer for SecretBytesMut<'_> { fn extend_from_slice(&mut self, other: &[u8]) -> Result<(), aead::Error> { self.extend_from_slice(other); Ok(()) @@ -400,6 +414,14 @@ impl AsMut<[u8]> for SecretBytesMut<'_> { } } +impl Deref for SecretBytesMut<'_> { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + /// A utility type for debug printing of byte strings struct MaybeStr<'a>(&'a [u8]); @@ -408,40 +430,97 @@ impl Debug for MaybeStr<'_> { if let Ok(sval) = core::str::from_utf8(self.0) { write!(f, "{:?}", sval) } else { - f.write_char('<')?; + fmt::Write::write_char(f, '<')?; for c in self.0 { f.write_fmt(format_args!("{:02x}", c))?; } - f.write_char('>')?; + fmt::Write::write_char(f, '>')?; Ok(()) } } } pub trait WriteBuffer { - fn extend_from_slice(&mut self, data: &[u8]) -> Result<(), Error> { - if let Some(ext) = self.extend_buffer(data.len()) { + fn write_slice(&mut self, data: &[u8]) -> Result<(), Error> { + let len = data.len(); + self.write_with(len, |ext| { ext.copy_from_slice(data); - Ok(()) - } else { - Err(err_msg!(Unexpected, "JWK buffer too small")) + Ok(len) + })?; + Ok(()) + } + + fn write_with( + &mut self, + max_len: usize, + f: impl FnOnce(&mut [u8]) -> Result, + ) -> Result; +} + +pub struct Writer { + inner: B, + pos: usize, +} + +impl<'w> Writer<&'w mut [u8]> { + pub fn from_slice(slice: &'w mut [u8]) -> Self { + Writer { + inner: slice, + pos: 0, } } - fn extend_buffer(&mut self, max_len: usize) -> Option<&mut [u8]>; + pub fn pos(&self) -> usize { + self.pos + } +} - fn truncate_by(&mut self, len: usize); +impl<'b> WriteBuffer for Writer<&'b mut [u8]> { + fn write_with( + &mut self, + max_len: usize, + f: impl FnOnce(&mut [u8]) -> Result, + ) -> Result { + let total = self.inner.len(); + let end = max_len + self.pos; + if end > total { + return Err(err_msg!("exceeded buffer size")); + } + let written = f(&mut self.inner[self.pos..end])?; + self.pos += written; + Ok(written) + } } impl WriteBuffer for Vec { - fn extend_buffer(&mut self, max_len: usize) -> Option<&mut [u8]> { + fn write_with( + &mut self, + max_len: usize, + f: impl FnOnce(&mut [u8]) -> Result, + ) -> Result { let len = self.len(); self.resize(len + max_len, 0u8); - Some(&mut self[len..(len + max_len)]) + let written = f(&mut self[len..(len + max_len)])?; + if written < max_len { + self.truncate(len + written); + } + Ok(written) } +} - fn truncate_by(&mut self, len: usize) { - self.truncate(self.len().saturating_sub(len)); +impl WriteBuffer for SecretBytes { + fn write_with( + &mut self, + max_len: usize, + f: impl FnOnce(&mut [u8]) -> Result, + ) -> Result { + let len = self.len(); + self.0.resize(len + max_len, 0u8); + let written = f(&mut self.0[len..(len + max_len)])?; + if written < max_len { + self.0.truncate(len + written); + } + Ok(written) } } @@ -450,13 +529,41 @@ mod tests { use super::*; #[test] - fn test_write_vec() { - let mut v = Vec::new(); - let b = &mut v as &mut dyn WriteBuffer; - b.extend_from_slice(b"hello").unwrap(); - b.truncate_by(3); - b.extend_from_slice(b"y").unwrap(); - assert_eq!(&v[..], b"hey"); + fn write_buffer_slice() { + let mut buf = [0u8; 10]; + let mut w = Writer::from_slice(&mut buf); + w.write_with(5, |buf| { + buf.copy_from_slice(b"hello"); + Ok(2) + }) + .unwrap(); + w.write_slice(b"y").unwrap(); + assert_eq!(w.pos(), 3); + assert_eq!(&buf[..3], b"hey"); + } + + #[test] + fn write_buffer_vec() { + let mut w = Vec::new(); + w.write_with(5, |buf| { + buf.copy_from_slice(b"hello"); + Ok(2) + }) + .unwrap(); + w.write_slice(b"y").unwrap(); + assert_eq!(&w[..], b"hey"); + } + + #[test] + fn write_buffer_secret() { + let mut w = SecretBytes::with_capacity(10); + w.write_with(5, |buf| { + buf.copy_from_slice(b"hello"); + Ok(2) + }) + .unwrap(); + w.write_slice(b"y").unwrap(); + assert_eq!(&w[..], b"hey"); } #[test] diff --git a/askar-crypto/src/caps.rs b/askar-crypto/src/caps.rs index aeccdcbc..446e496f 100644 --- a/askar-crypto/src/caps.rs +++ b/askar-crypto/src/caps.rs @@ -6,22 +6,51 @@ use core::{ use zeroize::Zeroize; -use crate::{buffer::WriteBuffer, error::Error}; +use crate::{ + buffer::{SecretBytes, WriteBuffer}, + error::Error, +}; // #[cfg(feature = "any")] // use crate::any::AnyKey; -// pub trait KeyCapGetPublic { -// fn key_get_public(&self, alg: Option) -> Result; -// } +/// Generate a new random key. +pub trait KeyGen: Sized { + fn generate() -> Result; +} + +/// Allows a key to be created uninitialized and populated later, +/// for instance when nested inside another struct. +pub trait KeyGenInPlace { + fn generate_in_place(&mut self) -> Result<(), Error>; +} + +/// Initialize a key from an array of bytes. +pub trait KeySecretBytes: Sized { + fn from_key_secret_bytes(key: &[u8]) -> Result; + + fn to_key_secret_buffer(&self, out: &mut B) -> Result<(), Error>; + + fn to_key_secret_bytes(&self) -> Result { + let mut buf = SecretBytes::with_capacity(128); + self.to_key_secret_buffer(&mut buf)?; + Ok(buf) + } +} pub trait KeyCapSign { - fn key_sign( + fn key_sign_buffer( &self, data: &[u8], sig_type: Option, out: &mut B, - ) -> Result; + ) -> Result<(), Error>; + + fn key_sign(&self, data: &[u8], sig_type: Option) -> Result { + let mut buf = SecretBytes::with_capacity(128); + self.key_sign_buffer(data, sig_type, &mut buf)?; + Ok(buf) + } } pub trait KeyCapVerify { diff --git a/askar-crypto/src/error.rs b/askar-crypto/src/error.rs index ddbc4ed4..6190870b 100644 --- a/askar-crypto/src/error.rs +++ b/askar-crypto/src/error.rs @@ -13,6 +13,9 @@ pub enum ErrorKind { /// The input parameters to the method were incorrect Input, + /// A secret key is required but not present + MissingSecretKey, + /// An unexpected error occurred Unexpected, @@ -26,6 +29,7 @@ impl ErrorKind { match self { Self::Encryption => "Encryption error", Self::Input => "Input error", + Self::MissingSecretKey => "Missing secret key", Self::Unexpected => "Unexpected error", Self::Unsupported => "Unsupported", } diff --git a/askar-crypto/src/jwk/encode.rs b/askar-crypto/src/jwk/encode.rs index 3c46c2e6..c59c45de 100644 --- a/askar-crypto/src/jwk/encode.rs +++ b/askar-crypto/src/jwk/encode.rs @@ -8,17 +8,14 @@ impl bs58::encode::EncodeTarget for JwkBuffer<'_, B> { fn encode_with( &mut self, max_len: usize, - f: impl for<'a> FnOnce(&'a mut [u8]) -> Result, + f: impl FnOnce(&mut [u8]) -> Result, ) -> Result { - if let Some(ext) = self.0.extend_buffer(max_len) { - let len = f(ext)?; - if len < max_len { - self.0.truncate_by(max_len - len); - } - Ok(len) - } else { - Err(bs58::encode::Error::BufferTooSmall) - } + self.0 + .write_with(max_len, |buf| { + // this cannot fail - there is enough space allocated + Ok(f(buf).unwrap()) + }) + .map_err(|_| bs58::encode::Error::BufferTooSmall) } } @@ -29,53 +26,49 @@ pub struct JwkEncoder<'b, B: WriteBuffer> { impl<'b, B: WriteBuffer> JwkEncoder<'b, B> { pub fn new(buffer: &'b mut B, kty: &str, secret: bool) -> Result { - buffer.extend_from_slice(b"{\"kty\":\"")?; - buffer.extend_from_slice(kty.as_bytes())?; - buffer.extend_from_slice(b"\"")?; + buffer.write_slice(b"{\"kty\":\"")?; + buffer.write_slice(kty.as_bytes())?; + buffer.write_slice(b"\"")?; Ok(Self { buffer, secret }) } pub fn add_str(&mut self, key: &str, value: &str) -> Result<(), Error> { let buffer = &mut *self.buffer; - buffer.extend_from_slice(b",\"")?; - buffer.extend_from_slice(key.as_bytes())?; - buffer.extend_from_slice(b"\":\"")?; - buffer.extend_from_slice(value.as_bytes())?; - buffer.extend_from_slice(b"\"")?; + buffer.write_slice(b",\"")?; + buffer.write_slice(key.as_bytes())?; + buffer.write_slice(b"\":\"")?; + buffer.write_slice(value.as_bytes())?; + buffer.write_slice(b"\"")?; Ok(()) } pub fn add_as_base64(&mut self, key: &str, value: &[u8]) -> Result<(), Error> { let buffer = &mut *self.buffer; - buffer.extend_from_slice(b",\"")?; - buffer.extend_from_slice(key.as_bytes())?; - buffer.extend_from_slice(b"\":\"")?; + buffer.write_slice(b",\"")?; + buffer.write_slice(key.as_bytes())?; + buffer.write_slice(b"\":\"")?; let enc_size = ((value.len() << 2) + 2) / 3; - if let Some(mbuf) = buffer.extend_buffer(enc_size) { + buffer.write_with(enc_size, |mbuf| { let len = base64::encode_config_slice(value, base64::URL_SAFE_NO_PAD, mbuf); - if len < enc_size { - buffer.truncate_by(enc_size - len); - } - } else { - return Err(err_msg!("buffer too small")); - } - buffer.extend_from_slice(b"\"")?; + Ok(len) + })?; + buffer.write_slice(b"\"")?; Ok(()) } pub fn add_key_ops(&mut self, ops: impl Into) -> Result<(), Error> { let buffer = &mut *self.buffer; - buffer.extend_from_slice(b",\"key_ops\":[")?; + buffer.write_slice(b",\"key_ops\":[")?; for (idx, op) in ops.into().into_iter().enumerate() { if idx > 0 { - buffer.extend_from_slice(b",\"")?; + buffer.write_slice(b",\"")?; } else { - buffer.extend_from_slice(b"\"")?; + buffer.write_slice(b"\"")?; } - buffer.extend_from_slice(op.as_str().as_bytes())?; - buffer.extend_from_slice(b"\"")?; + buffer.write_slice(op.as_str().as_bytes())?; + buffer.write_slice(b"\"")?; } - buffer.extend_from_slice(b"]")?; + buffer.write_slice(b"]")?; Ok(()) } @@ -84,7 +77,7 @@ impl<'b, B: WriteBuffer> JwkEncoder<'b, B> { } pub fn finalize(self) -> Result<(), Error> { - self.buffer.extend_from_slice(b"}")?; + self.buffer.write_slice(b"}")?; Ok(()) } } diff --git a/askar-crypto/src/lib.rs b/askar-crypto/src/lib.rs index 7ec7a83e..fdc784d1 100644 --- a/askar-crypto/src/lib.rs +++ b/askar-crypto/src/lib.rs @@ -35,11 +35,17 @@ pub use self::buffer::{PassKey, SecretBytes}; pub mod caps; pub use self::caps::{ - KeyAlg, /*KeyCapGetPublic,*/ KeyCapSign, KeyCapVerify, KeyCategory, SignatureFormat, - SignatureType, + KeyAlg, + KeyCapVerify, + KeyCategory, + KeyGen, + KeyGenInPlace, + KeySecretBytes, + SignatureFormat, + SignatureType, // KeyCapSign }; -pub mod encrypt; +// pub mod encrypt; pub mod jwk; @@ -48,5 +54,3 @@ pub mod kdf; pub mod pack; pub mod random; - -// pub mod wrap; diff --git a/askar-crypto/src/random.rs b/askar-crypto/src/random.rs index 43141b84..8a008c87 100644 --- a/askar-crypto/src/random.rs +++ b/askar-crypto/src/random.rs @@ -1,35 +1,41 @@ -use aead::generic_array::{ArrayLength, GenericArray}; +use aead::generic_array::{typenum::Unsigned, GenericArray}; use chacha20::{ cipher::{NewStreamCipher, SyncStreamCipher}, ChaCha20, }; use rand::{rngs::OsRng, RngCore}; -use crate::buffer::SecretBytes; +use crate::{buffer::SecretBytes, error::Error}; -pub type SeedSize = ::KeySize; +pub const SEED_LENGTH: usize = ::KeySize::USIZE; + +pub type StdRng = OsRng; -/// Fill a mutable slice with random data using the -/// system random number generator. #[inline(always)] -pub fn fill_random(value: &mut [u8]) { - OsRng.fill_bytes(value); +pub fn with_rng(f: impl FnOnce(&mut StdRng) -> O) -> O { + // may need to substitute another RNG depending on the platform + f(&mut OsRng) } -/// Create a new `GenericArray` instance with random data. +/// Fill a mutable slice with random data using the +/// system random number generator. #[inline(always)] -pub fn random_array>() -> GenericArray { - let mut buf = GenericArray::default(); - fill_random(buf.as_mut_slice()); - buf +pub fn fill_random(value: &mut [u8]) { + with_rng(|rng| rng.fill_bytes(value)); } /// Written to be compatible with randombytes_deterministic in libsodium, -/// used to generate a deterministic wallet raw key. -pub fn random_deterministic(seed: &GenericArray, len: usize) -> SecretBytes { - let nonce = GenericArray::from_slice(b"LibsodiumDRG"); - let mut cipher = ChaCha20::new(seed, &nonce); - SecretBytes::new_with(len, |buf| cipher.apply_keystream(buf)) +/// used to generate a deterministic symmetric encryption key +pub fn fill_random_deterministic(seed: &[u8], output: &mut [u8]) -> Result<(), Error> { + if seed.len() != SEED_LENGTH { + return Err(err_msg!("Invalid length for seed")); + } + let mut cipher = ChaCha20::new( + GenericArray::from_slice(seed), + GenericArray::from_slice(b"LibsodiumDRG"), + ); + cipher.apply_keystream(output); + Ok(()) } /// Create a new `SecretBytes` instance with random data. From ade2aee1baec51e070f86a9b9e07b6bb6e7b2a74 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Sun, 11 Apr 2021 00:53:21 -0700 Subject: [PATCH 013/116] implement chacha encryption Signed-off-by: Andrew Whitehead --- askar-crypto/src/alg/chacha.rs | 163 +++++++++++++++++---- askar-crypto/src/buffer.rs | 75 +++++++++- askar-crypto/src/encrypt.rs | 248 -------------------------------- askar-crypto/src/encrypt/mod.rs | 25 ++++ askar-crypto/src/lib.rs | 12 +- 5 files changed, 238 insertions(+), 285 deletions(-) delete mode 100644 askar-crypto/src/encrypt.rs create mode 100644 askar-crypto/src/encrypt/mod.rs diff --git a/askar-crypto/src/alg/chacha.rs b/askar-crypto/src/alg/chacha.rs index 482cd0be..71f27d5b 100644 --- a/askar-crypto/src/alg/chacha.rs +++ b/askar-crypto/src/alg/chacha.rs @@ -1,50 +1,78 @@ use alloc::boxed::Box; +use aead::{Aead, AeadInPlace, NewAead}; +use chacha20poly1305::{ChaCha20Poly1305, XChaCha20Poly1305}; use zeroize::Zeroize; -use crate::generic_array::typenum::U32; +use crate::generic_array::{typenum::Unsigned, GenericArray}; use crate::{ - buffer::{ArrayKey, WriteBuffer}, + buffer::{ArrayKey, ResizeBuffer, WriteBuffer}, caps::{KeyGen, KeySecretBytes}, - // any::{AnyPrivateKey, AnyPublicKey}, + encrypt::KeyAeadInPlace, error::Error, jwk::{JwkEncoder, KeyToJwk}, random::{fill_random, fill_random_deterministic}, }; -pub type KeyType = ArrayKey; +pub trait Chacha20Type { + type Aead: NewAead + Aead + AeadInPlace; -pub static JWK_ALG: &'static str = "C20P"; + const JWK_ALG: &'static str; -pub const KEY_LENGTH: usize = KeyType::SIZE; + fn key_size() -> usize { + ::KeySize::USIZE + } +} + +pub struct C20P; + +impl Chacha20Type for C20P { + type Aead = ChaCha20Poly1305; + + const JWK_ALG: &'static str = "C20P"; +} + +pub struct XC20P; + +impl Chacha20Type for XC20P { + type Aead = XChaCha20Poly1305; + + const JWK_ALG: &'static str = "XC20P"; +} + +type KeyType = ArrayKey<<::Aead as NewAead>::KeySize>; + +type NonceSize = <::Aead as Aead>::NonceSize; + +type TagSize = <::Aead as Aead>::TagSize; #[derive(Clone, Debug, Zeroize)] // SECURITY: ArrayKey is zeroized on drop -pub struct Chacha20Key(KeyType); +pub struct Chacha20Key(KeyType); -impl Chacha20Key { +impl Chacha20Key { // this is consistent with Indy's wallet wrapping key generation // FIXME: move to a trait to allow custom impl for Box pub fn from_seed(seed: &[u8]) -> Result { - let mut slf = KeyType::default(); + let mut slf = KeyType::::default(); fill_random_deterministic(seed, &mut slf)?; Ok(Self(slf)) } } -impl KeyGen for Chacha20Key { +impl KeyGen for Chacha20Key { fn generate() -> Result { - Ok(Chacha20Key(KeyType::random())) + Ok(Chacha20Key(KeyType::::random())) } } -impl KeySecretBytes for Chacha20Key { +impl KeySecretBytes for Chacha20Key { fn from_key_secret_bytes(key: &[u8]) -> Result { - if key.len() != KEY_LENGTH { + if key.len() != ::KeySize::USIZE { return Err(err_msg!("Invalid length for chacha20 key")); } - Ok(Self(KeyType::from_slice(key))) + Ok(Self(KeyType::::from_slice(key))) } fn to_key_secret_buffer(&self, out: &mut B) -> Result<(), Error> { @@ -53,30 +81,30 @@ impl KeySecretBytes for Chacha20Key { } #[inline] -pub fn init_boxed(f: impl FnOnce(&mut [u8])) -> Box { - let mut slf = Box::new(Chacha20Key(KeyType::default())); +pub fn init_boxed(f: impl FnOnce(&mut [u8])) -> Box> { + let mut slf = Box::new(Chacha20Key(KeyType::::default())); f(&mut slf.0); slf } #[inline] -pub fn try_init_boxed( +pub fn try_init_boxed( f: impl FnOnce(&mut [u8]) -> Result<(), Error>, -) -> Result, Error> { - let mut slf = Box::new(Chacha20Key(KeyType::default())); +) -> Result>, Error> { + let mut slf = Box::new(Chacha20Key(KeyType::::default())); f(&mut slf.0)?; Ok(slf) } -impl KeyGen for Box { +impl KeyGen for Box> { fn generate() -> Result { Ok(init_boxed(fill_random)) } } -impl KeySecretBytes for Box { +impl KeySecretBytes for Box> { fn from_key_secret_bytes(key: &[u8]) -> Result { - if key.len() != KEY_LENGTH { + if key.len() != ::KeySize::USIZE { return Err(err_msg!("Invalid length for chacha20 key")); } Ok(init_boxed(|buf| buf.copy_from_slice(key))) @@ -87,16 +115,103 @@ impl KeySecretBytes for Box { } } -impl KeyToJwk for Chacha20Key { +impl KeyAeadInPlace for Chacha20Key { + /// Encrypt a secret value in place, appending the verification tag + fn encrypt_in_place( + &self, + buffer: &mut B, + nonce: &[u8], + aad: &[u8], + ) -> Result<(), Error> { + if nonce.len() != NonceSize::::USIZE { + return Err(err_msg!( + "invalid size for nonce (expected {} bytes)", + NonceSize::::USIZE + )); + } + let nonce = GenericArray::from_slice(nonce); + let chacha = T::Aead::new(&self.0); + let tag = chacha + .encrypt_in_place_detached(nonce, aad, buffer.as_mut()) + .map_err(|e| err_msg!(Encryption, "{}", e))?; + buffer.write_slice(&tag[..])?; + Ok(()) + } + + /// Decrypt an encrypted (verification tag appended) value in place + fn decrypt_in_place( + &self, + buffer: &mut B, + nonce: &[u8], + aad: &[u8], + ) -> Result<(), Error> { + if nonce.len() != NonceSize::::USIZE { + return Err(err_msg!( + "invalid size for nonce (expected {} bytes)", + NonceSize::::USIZE + )); + } + let nonce = GenericArray::from_slice(nonce); + let buf_len = buffer.as_ref().len(); + if buf_len < TagSize::::USIZE { + return Err(err_msg!("invalid size for encrypted data")); + } + let tag_start = buf_len - TagSize::::USIZE; + let mut tag = GenericArray::default(); + tag.clone_from_slice(&buffer.as_ref()[tag_start..]); + let chacha = T::Aead::new(&self.0); + chacha + .decrypt_in_place_detached(nonce, aad, &mut buffer.as_mut()[..tag_start], &tag) + .map_err(|e| err_msg!(Encryption, "{}", e))?; + buffer.truncate(tag_start); + Ok(()) + } + + /// Get the required nonce size for encryption + fn nonce_size() -> usize { + NonceSize::::USIZE + } + + /// Get the size of the verification tag + fn tag_size() -> usize { + TagSize::::USIZE + } +} + +impl KeyToJwk for Chacha20Key { const KTY: &'static str = "oct"; fn to_jwk_buffer(&self, buffer: &mut JwkEncoder) -> Result<(), Error> { if !buffer.is_secret() { return Err(err_msg!(Unsupported, "Cannot export as a public key")); } - buffer.add_str("alg", JWK_ALG)?; + buffer.add_str("alg", T::JWK_ALG)?; buffer.add_as_base64("k", &self.0[..])?; buffer.add_str("use", "enc")?; Ok(()) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{buffer::SecretBytes, random::fill_random}; + + #[test] + fn encrypt_round_trip() { + fn test_encrypt() { + let input = b"hello"; + let key = Chacha20Key::::generate().unwrap(); + let mut buffer = SecretBytes::from_slice(input); + let mut nonce = GenericArray::>::default(); + fill_random(&mut nonce); + key.encrypt_in_place(&mut buffer, &nonce, &[]).unwrap(); + assert_eq!(buffer.len(), input.len() + Chacha20Key::::tag_size()); + assert_ne!(&buffer[..], input); + key.decrypt_in_place(&mut buffer, &nonce, &[]).unwrap(); + assert_eq!(&buffer[..], input); + } + test_encrypt::(); + test_encrypt::(); + } +} diff --git a/askar-crypto/src/buffer.rs b/askar-crypto/src/buffer.rs index 28fb8efe..c225005e 100644 --- a/askar-crypto/src/buffer.rs +++ b/askar-crypto/src/buffer.rs @@ -457,20 +457,44 @@ pub trait WriteBuffer { ) -> Result; } +pub trait ResizeBuffer: WriteBuffer { + fn as_ref(&self) -> &[u8]; + + fn as_mut(&mut self) -> &mut [u8]; + + fn reserve(&mut self, len: usize) -> Result<(), Error> { + self.write_with(len, |buf| { + for idx in 0..len { + buf[idx] = 0u8; + } + Ok(len) + })?; + Ok(()) + } + + fn truncate(&mut self, len: usize); +} + pub struct Writer { inner: B, pos: usize, } -impl<'w> Writer<&'w mut [u8]> { - pub fn from_slice(slice: &'w mut [u8]) -> Self { +impl<'b> Writer<&'b mut [u8]> { + #[inline] + pub fn from_slice(slice: &'b mut [u8]) -> Self { Writer { inner: slice, pos: 0, } } - pub fn pos(&self) -> usize { + #[inline] + pub fn from_slice_position(slice: &'b mut [u8], pos: usize) -> Self { + Writer { inner: slice, pos } + } + + pub fn position(&self) -> usize { self.pos } } @@ -492,6 +516,21 @@ impl<'b> WriteBuffer for Writer<&'b mut [u8]> { } } +impl<'b> ResizeBuffer for Writer<&'b mut [u8]> { + fn as_ref(&self) -> &[u8] { + &self.inner[..self.pos] + } + + fn as_mut(&mut self) -> &mut [u8] { + &mut self.inner[..self.pos] + } + + fn truncate(&mut self, len: usize) { + assert!(len <= self.pos); + self.pos = len; + } +} + impl WriteBuffer for Vec { fn write_with( &mut self, @@ -508,6 +547,20 @@ impl WriteBuffer for Vec { } } +impl ResizeBuffer for Vec { + fn as_ref(&self) -> &[u8] { + &self[..] + } + + fn as_mut(&mut self) -> &mut [u8] { + &mut self[..] + } + + fn truncate(&mut self, len: usize) { + self.truncate(len); + } +} + impl WriteBuffer for SecretBytes { fn write_with( &mut self, @@ -524,6 +577,20 @@ impl WriteBuffer for SecretBytes { } } +impl ResizeBuffer for SecretBytes { + fn as_ref(&self) -> &[u8] { + &self.0[..] + } + + fn as_mut(&mut self) -> &mut [u8] { + &mut self.0[..] + } + + fn truncate(&mut self, len: usize) { + self.0.truncate(len); + } +} + #[cfg(test)] mod tests { use super::*; @@ -538,7 +605,7 @@ mod tests { }) .unwrap(); w.write_slice(b"y").unwrap(); - assert_eq!(w.pos(), 3); + assert_eq!(w.position(), 3); assert_eq!(&buf[..3], b"hey"); } diff --git a/askar-crypto/src/encrypt.rs b/askar-crypto/src/encrypt.rs deleted file mode 100644 index fb842157..00000000 --- a/askar-crypto/src/encrypt.rs +++ /dev/null @@ -1,248 +0,0 @@ -use alloc::vec::Vec; -use core::fmt::Debug; - -use serde::{Deserialize, Serialize}; - -use crate::buffer::SecretBytes; -use crate::error::Error; - -pub trait SymEncryptKey: - Clone + Debug + Eq + Sized + Serialize + for<'de> Deserialize<'de> -{ - const SIZE: usize; - - fn as_bytes(&self) -> &[u8]; - - fn from_slice(bytes: &[u8]) -> Self; - - fn from_seed(seed: &[u8]) -> Result; - - fn random_key() -> Self; -} - -pub trait SymEncryptHashKey: - Clone + Debug + Eq + Sized + Serialize + for<'de> Deserialize<'de> -{ - const SIZE: usize; - - fn random_hash_key() -> Self; -} - -pub trait SymEncrypt: Debug { - type Key: SymEncryptKey; - type HashKey: SymEncryptHashKey; - type Nonce; - - /// Convert a referenced secret value to a secure buffer with sufficient - /// memory for in-place encryption, reusing the same buffer if possible - fn prepare_input(input: &[u8]) -> SecretBytes; - - /// Create a predictable nonce for an input, to allow searching - fn hashed_nonce(input: &SecretBytes, key: &Self::HashKey) -> Result; - - /// Encrypt a secret value and optional random nonce, producing a Vec containing the - /// nonce, ciphertext and tag - fn encrypt( - input: SecretBytes, - enc_key: &Self::Key, - nonce: Option, - ) -> Result, Error>; - - /// Get the expected size of an input value after encryption - fn encrypted_size(input_size: usize) -> usize; - - /// Decrypt a combined encrypted value - fn decrypt(enc: Vec, enc_key: &Self::Key) -> Result; -} - -pub(crate) mod aead { - use alloc::vec::Vec; - use core::{ - fmt::{self, Debug, Formatter}, - marker::PhantomData, - ptr, - }; - - use chacha20poly1305::{ - aead::{ - generic_array::{ - typenum::{Unsigned, U32}, - ArrayLength, - }, - AeadInPlace, NewAead, - }, - ChaCha20Poly1305, - }; - use hmac::{Hmac, Mac, NewMac}; - use sha2::Sha256; - - use super::{SymEncrypt, SymEncryptHashKey, SymEncryptKey}; - use crate::{ - buffer::{ArrayKey, SecretBytes}, - error::Error, - random::random_deterministic, - }; - - pub type ChaChaEncrypt = AeadEncrypt; - - const SEED_LENGTH: usize = 32; - - impl + Debug> SymEncryptKey for ArrayKey { - const SIZE: usize = L::USIZE; - - fn as_bytes(&self) -> &[u8] { - &**self - } - - fn from_slice(bytes: &[u8]) -> Self { - ArrayKey::from_slice(bytes) - } - - fn from_seed(seed: &[u8]) -> Result { - if seed.len() != SEED_LENGTH { - return Err(err_msg!(Encryption, "Invalid length for seed")); - } - let input = ArrayKey::from_slice(seed); - let raw_key = SecretBytes::from(random_deterministic(&input, L::USIZE)); - Ok(ArrayKey::from_slice(&raw_key)) - } - - fn random_key() -> Self { - ArrayKey::random() - } - } - - impl + Debug> SymEncryptHashKey for ArrayKey { - const SIZE: usize = L::USIZE; - - fn random_hash_key() -> Self { - ArrayKey::random() - } - } - - #[derive(PartialEq, Eq, PartialOrd, Ord)] - pub struct AeadEncrypt(PhantomData); - - impl AeadEncrypt { - const NONCE_SIZE: usize = E::NonceSize::USIZE; - const TAG_SIZE: usize = E::TagSize::USIZE; - } - - impl Debug for AeadEncrypt { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_struct("AeadEncrypt").finish() - } - } - - impl SymEncrypt for AeadEncrypt - where - E: NewAead + AeadInPlace, - E::KeySize: Debug, - { - type Key = ArrayKey; - type HashKey = ArrayKey; - type Nonce = ArrayKey; - - fn prepare_input(input: &[u8]) -> SecretBytes { - // if we must perform a heap allocation, try to make sure that the - // allocation is large enough to avoid reallocating later (for performance) - let size = input.len() + Self::NONCE_SIZE + Self::TAG_SIZE; - let mut buf = Vec::with_capacity(size); - buf.extend_from_slice(input); - SecretBytes::from(buf) - } - - fn hashed_nonce(input: &SecretBytes, key: &Self::HashKey) -> Result { - let mut nonce_hmac = - Hmac::::new_varkey(&**key).map_err(|e| err_msg!(Encryption, "{}", e))?; - nonce_hmac.update(&*input); - let nonce_long = nonce_hmac.finalize().into_bytes(); - Ok(ArrayKey::::from_slice( - &nonce_long[0..E::NonceSize::USIZE], - )) - } - - fn encrypt( - mut input: SecretBytes, - enc_key: &Self::Key, - nonce: Option, - ) -> Result, Error> { - let nonce = nonce.unwrap_or_else(|| Self::Nonce::random()); - let chacha = E::new(&enc_key); - let mut buf = input.as_buffer(); - // should be trivial if prepare_input was used - buf.reserve(Self::NONCE_SIZE + Self::TAG_SIZE); - // replace the input data with the ciphertext and tag - chacha - .encrypt_in_place(&*nonce, &[], &mut buf) - .map_err(|e| err_msg!(Encryption, "{}", e))?; - let mut buf = input.into_vec(); - // prepend the nonce to the current (ciphertext + tag) Vec contents. - // extra capacity has previously been reserved for this in order to avoid - // reallocation of the Vec buffer - buf.splice(0..0, nonce.as_slice().into_iter().cloned()); - Ok(buf) - } - - #[inline] - fn encrypted_size(input_size: usize) -> usize { - Self::NONCE_SIZE + Self::TAG_SIZE + input_size - } - - fn decrypt(mut enc: Vec, enc_key: &Self::Key) -> Result { - if enc.len() < Self::NONCE_SIZE + Self::TAG_SIZE { - return Err(err_msg!( - Encryption, - "Buffer is too short to represent an encrypted value" - )); - } - let nonce = Self::Nonce::from_slice(&enc[0..Self::NONCE_SIZE]); - let chacha = E::new(&enc_key); - unsafe { - let cipher_len = enc.len() - Self::NONCE_SIZE; - ptr::copy( - enc.as_mut_ptr().add(Self::NONCE_SIZE), - enc.as_mut_ptr(), - cipher_len, - ); - enc.set_len(cipher_len); - } - let mut result = SecretBytes::from(enc); - chacha - .decrypt_in_place(&nonce, &[], &mut result.as_buffer()) - .map_err(|e| err_msg!(Encryption, "Error decrypting record: {}", e))?; - Ok(result) - } - } - - #[cfg(test)] - mod tests { - use super::*; - - #[test] - fn chacha_key_round_trip() { - let input = b"hello"; - let key = ArrayKey::random(); - let enc = ChaChaEncrypt::encrypt(SecretBytes::from(&input[..]), &key, None).unwrap(); - assert_eq!( - enc.len(), - input.len() + ChaChaEncrypt::NONCE_SIZE + ChaChaEncrypt::TAG_SIZE - ); - let dec = ChaChaEncrypt::decrypt(enc, &key).unwrap(); - assert_eq!(dec, &input[..]); - } - - #[test] - fn chacha_encrypt_avoid_realloc() { - let input = ChaChaEncrypt::prepare_input(b"hello"); - let buffer_ptr = input.as_ptr() as usize; - let key = ArrayKey::random(); - let enc = ChaChaEncrypt::encrypt(input, &key, None).unwrap(); - assert_eq!( - enc.as_ptr() as usize, - buffer_ptr, - "Same buffer should be used" - ); - } - } -} diff --git a/askar-crypto/src/encrypt/mod.rs b/askar-crypto/src/encrypt/mod.rs new file mode 100644 index 00000000..93b79662 --- /dev/null +++ b/askar-crypto/src/encrypt/mod.rs @@ -0,0 +1,25 @@ +use crate::{buffer::ResizeBuffer, error::Error}; + +pub trait KeyAeadInPlace { + /// Encrypt a secret value in place, appending the verification tag + fn encrypt_in_place( + &self, + buffer: &mut B, + nonce: &[u8], + aad: &[u8], + ) -> Result<(), Error>; + + /// Decrypt an encrypted (verification tag appended) value in place + fn decrypt_in_place( + &self, + buffer: &mut B, + nonce: &[u8], + aad: &[u8], + ) -> Result<(), Error>; + + /// Get the required nonce size for encryption + fn nonce_size() -> usize; + + /// Get the size of the verification tag + fn tag_size() -> usize; +} diff --git a/askar-crypto/src/lib.rs b/askar-crypto/src/lib.rs index fdc784d1..86032d4c 100644 --- a/askar-crypto/src/lib.rs +++ b/askar-crypto/src/lib.rs @@ -35,17 +35,11 @@ pub use self::buffer::{PassKey, SecretBytes}; pub mod caps; pub use self::caps::{ - KeyAlg, - KeyCapVerify, - KeyCategory, - KeyGen, - KeyGenInPlace, - KeySecretBytes, - SignatureFormat, - SignatureType, // KeyCapSign + KeyAlg, KeyCapSign, KeyCapVerify, KeyCategory, KeyGen, KeyGenInPlace, KeySecretBytes, + SignatureFormat, SignatureType, }; -// pub mod encrypt; +pub mod encrypt; pub mod jwk; From e3ddc7e11481bbcf08ff61711dcf3476ba0b17ea Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Sun, 11 Apr 2021 09:51:26 -0700 Subject: [PATCH 014/116] add aes-gcm support Signed-off-by: Andrew Whitehead --- askar-crypto/Cargo.toml | 1 + askar-crypto/src/alg/aesgcm.rs | 169 ++++++++++++++++++ .../src/alg/{chacha.rs => chacha20.rs} | 52 ++---- askar-crypto/src/alg/mod.rs | 4 +- 4 files changed, 187 insertions(+), 39 deletions(-) create mode 100644 askar-crypto/src/alg/aesgcm.rs rename askar-crypto/src/alg/{chacha.rs => chacha20.rs} (82%) diff --git a/askar-crypto/Cargo.toml b/askar-crypto/Cargo.toml index 3e373a44..0c00a912 100644 --- a/askar-crypto/Cargo.toml +++ b/askar-crypto/Cargo.toml @@ -23,6 +23,7 @@ hex-literal = "0.3" [dependencies] aead = "0.3" +aes-gcm = { version = "0.8", default-features = false, features = ["aes", "alloc"] } argon2 = "0.1" base64 = { version = "0.13", default-features = false, features = ["alloc"] } blake2 = { version = "0.9", default-features = false } diff --git a/askar-crypto/src/alg/aesgcm.rs b/askar-crypto/src/alg/aesgcm.rs new file mode 100644 index 00000000..69cec750 --- /dev/null +++ b/askar-crypto/src/alg/aesgcm.rs @@ -0,0 +1,169 @@ +use aead::{Aead, AeadInPlace, NewAead}; +use aes_gcm::{Aes128Gcm, Aes256Gcm}; +use zeroize::Zeroize; + +use crate::generic_array::{typenum::Unsigned, GenericArray}; + +use crate::{ + buffer::{ArrayKey, ResizeBuffer, WriteBuffer}, + caps::{KeyGen, KeySecretBytes}, + encrypt::KeyAeadInPlace, + error::Error, + jwk::{JwkEncoder, KeyToJwk}, +}; + +pub trait AesGcmType { + type Aead: NewAead + Aead + AeadInPlace; + + const JWK_ALG: &'static str; + + fn key_size() -> usize { + ::KeySize::USIZE + } +} + +pub struct A128; + +impl AesGcmType for A128 { + type Aead = Aes128Gcm; + + const JWK_ALG: &'static str = "A128GCM"; +} + +pub struct A256; + +impl AesGcmType for A256 { + type Aead = Aes256Gcm; + + const JWK_ALG: &'static str = "A256GCM"; +} + +type KeyType = ArrayKey<<::Aead as NewAead>::KeySize>; + +type NonceSize = <::Aead as Aead>::NonceSize; + +type TagSize = <::Aead as Aead>::TagSize; + +#[derive(Clone, Debug, Zeroize)] +// SECURITY: ArrayKey is zeroized on drop +pub struct AesGcmKey(KeyType); + +impl KeyGen for AesGcmKey { + fn generate() -> Result { + Ok(AesGcmKey(KeyType::::random())) + } +} + +impl KeySecretBytes for AesGcmKey { + fn from_key_secret_bytes(key: &[u8]) -> Result { + if key.len() != ::KeySize::USIZE { + return Err(err_msg!("Invalid length for AES-GCM key")); + } + Ok(Self(KeyType::::from_slice(key))) + } + + fn to_key_secret_buffer(&self, out: &mut B) -> Result<(), Error> { + out.write_slice(&self.0[..]) + } +} + +impl KeyAeadInPlace for AesGcmKey { + /// Encrypt a secret value in place, appending the verification tag + fn encrypt_in_place( + &self, + buffer: &mut B, + nonce: &[u8], + aad: &[u8], + ) -> Result<(), Error> { + if nonce.len() != NonceSize::::USIZE { + return Err(err_msg!( + "invalid size for nonce (expected {} bytes)", + NonceSize::::USIZE + )); + } + let nonce = GenericArray::from_slice(nonce); + let chacha = T::Aead::new(&self.0); + let tag = chacha + .encrypt_in_place_detached(nonce, aad, buffer.as_mut()) + .map_err(|e| err_msg!(Encryption, "{}", e))?; + buffer.write_slice(&tag[..])?; + Ok(()) + } + + /// Decrypt an encrypted (verification tag appended) value in place + fn decrypt_in_place( + &self, + buffer: &mut B, + nonce: &[u8], + aad: &[u8], + ) -> Result<(), Error> { + if nonce.len() != NonceSize::::USIZE { + return Err(err_msg!( + "invalid size for nonce (expected {} bytes)", + NonceSize::::USIZE + )); + } + let nonce = GenericArray::from_slice(nonce); + let buf_len = buffer.as_ref().len(); + if buf_len < TagSize::::USIZE { + return Err(err_msg!("invalid size for encrypted data")); + } + let tag_start = buf_len - TagSize::::USIZE; + let mut tag = GenericArray::default(); + tag.clone_from_slice(&buffer.as_ref()[tag_start..]); + let chacha = T::Aead::new(&self.0); + chacha + .decrypt_in_place_detached(nonce, aad, &mut buffer.as_mut()[..tag_start], &tag) + .map_err(|e| err_msg!(Encryption, "{}", e))?; + buffer.truncate(tag_start); + Ok(()) + } + + /// Get the required nonce size for encryption + fn nonce_size() -> usize { + NonceSize::::USIZE + } + + /// Get the size of the verification tag + fn tag_size() -> usize { + TagSize::::USIZE + } +} + +impl KeyToJwk for AesGcmKey { + const KTY: &'static str = "oct"; + + fn to_jwk_buffer(&self, buffer: &mut JwkEncoder) -> Result<(), Error> { + if !buffer.is_secret() { + return Err(err_msg!(Unsupported, "Cannot export as a public key")); + } + buffer.add_str("alg", T::JWK_ALG)?; + buffer.add_as_base64("k", &self.0[..])?; + buffer.add_str("use", "enc")?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{buffer::SecretBytes, random::fill_random}; + + #[test] + fn encrypt_round_trip() { + fn test_encrypt() { + let input = b"hello"; + let key = AesGcmKey::::generate().unwrap(); + let mut buffer = SecretBytes::from_slice(input); + let mut nonce = GenericArray::>::default(); + fill_random(&mut nonce); + key.encrypt_in_place(&mut buffer, &nonce, &[]).unwrap(); + assert_eq!(buffer.len(), input.len() + AesGcmKey::::tag_size()); + assert_ne!(&buffer[..], input); + key.decrypt_in_place(&mut buffer, &nonce, &[]).unwrap(); + assert_eq!(&buffer[..], input); + } + test_encrypt::(); + test_encrypt::(); + } +} diff --git a/askar-crypto/src/alg/chacha.rs b/askar-crypto/src/alg/chacha20.rs similarity index 82% rename from askar-crypto/src/alg/chacha.rs rename to askar-crypto/src/alg/chacha20.rs index 71f27d5b..984981e9 100644 --- a/askar-crypto/src/alg/chacha.rs +++ b/askar-crypto/src/alg/chacha20.rs @@ -1,5 +1,3 @@ -use alloc::boxed::Box; - use aead::{Aead, AeadInPlace, NewAead}; use chacha20poly1305::{ChaCha20Poly1305, XChaCha20Poly1305}; use zeroize::Zeroize; @@ -12,7 +10,7 @@ use crate::{ encrypt::KeyAeadInPlace, error::Error, jwk::{JwkEncoder, KeyToJwk}, - random::{fill_random, fill_random_deterministic}, + random::fill_random_deterministic, }; pub trait Chacha20Type { @@ -53,7 +51,7 @@ pub struct Chacha20Key(KeyType); impl Chacha20Key { // this is consistent with Indy's wallet wrapping key generation - // FIXME: move to a trait to allow custom impl for Box + // FIXME - move to aries_askar, use from_key_secret_bytes pub fn from_seed(seed: &[u8]) -> Result { let mut slf = KeyType::::default(); fill_random_deterministic(seed, &mut slf)?; @@ -80,40 +78,18 @@ impl KeySecretBytes for Chacha20Key { } } -#[inline] -pub fn init_boxed(f: impl FnOnce(&mut [u8])) -> Box> { - let mut slf = Box::new(Chacha20Key(KeyType::::default())); - f(&mut slf.0); - slf -} - -#[inline] -pub fn try_init_boxed( - f: impl FnOnce(&mut [u8]) -> Result<(), Error>, -) -> Result>, Error> { - let mut slf = Box::new(Chacha20Key(KeyType::::default())); - f(&mut slf.0)?; - Ok(slf) -} - -impl KeyGen for Box> { - fn generate() -> Result { - Ok(init_boxed(fill_random)) - } -} - -impl KeySecretBytes for Box> { - fn from_key_secret_bytes(key: &[u8]) -> Result { - if key.len() != ::KeySize::USIZE { - return Err(err_msg!("Invalid length for chacha20 key")); - } - Ok(init_boxed(|buf| buf.copy_from_slice(key))) - } - - fn to_key_secret_buffer(&self, out: &mut B) -> Result<(), Error> { - out.write_slice(&self.0[..]) - } -} +// impl KeySecretBytes for Box> { +// fn from_key_secret_bytes(key: &[u8]) -> Result { +// if key.len() != ::KeySize::USIZE { +// return Err(err_msg!("Invalid length for chacha20 key")); +// } +// Ok(init_boxed(|buf| buf.copy_from_slice(key))) +// } + +// fn to_key_secret_buffer(&self, out: &mut B) -> Result<(), Error> { +// out.write_slice(&self.0[..]) +// } +// } impl KeyAeadInPlace for Chacha20Key { /// Encrypt a secret value in place, appending the verification tag diff --git a/askar-crypto/src/alg/mod.rs b/askar-crypto/src/alg/mod.rs index 0f0aea6f..9cd6bc93 100644 --- a/askar-crypto/src/alg/mod.rs +++ b/askar-crypto/src/alg/mod.rs @@ -1,6 +1,8 @@ // pub mod bls; -pub mod chacha; +pub mod aesgcm; + +pub mod chacha20; pub mod ed25519; pub mod x25519; From f077f876cce5b8d5fee56bdf4af186d1c2e646fd Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Sun, 11 Apr 2021 10:37:55 -0700 Subject: [PATCH 015/116] move key exchange to trait Signed-off-by: Andrew Whitehead --- askar-crypto/src/alg/k256.rs | 30 +++++++++++++++++------------- askar-crypto/src/alg/p256.rs | 29 +++++++++++++++++------------ askar-crypto/src/alg/x25519.rs | 29 +++++++++++++++++------------ askar-crypto/src/buffer.rs | 11 ----------- askar-crypto/src/encrypt/mod.rs | 19 ++++++++++++++++++- 5 files changed, 69 insertions(+), 49 deletions(-) diff --git a/askar-crypto/src/alg/k256.rs b/askar-crypto/src/alg/k256.rs index 49fcb827..0c5da1aa 100644 --- a/askar-crypto/src/alg/k256.rs +++ b/askar-crypto/src/alg/k256.rs @@ -10,9 +10,9 @@ use k256::{ }; use crate::{ - // any::{AnyPrivateKey, AnyPublicKey}, buffer::{SecretBytes, WriteBuffer}, caps::{KeyCapSign, KeyCapVerify, KeyGen, KeySecretBytes, SignatureType}, + encrypt::KeyExchange, error::Error, jwk::{JwkEncoder, KeyToJwk}, random::with_rng, @@ -70,16 +70,6 @@ impl K256KeyPair { } } - pub fn key_exchange_with(&self, other: &Self) -> Option { - match self.secret.as_ref() { - Some(sk) => { - let xk = diffie_hellman(sk.secret_scalar(), other.public.as_affine()); - Some(SecretBytes::from(xk.as_bytes().to_vec())) - } - None => None, - } - } - pub fn to_keypair_bytes(&self) -> Option { if let Some(secret) = self.secret.as_ref() { let encp = EncodedPoint::encode(self.public, true); @@ -200,6 +190,19 @@ impl KeyToJwk for K256KeyPair { } } +impl KeyExchange for K256KeyPair { + fn key_exchange_buffer(&self, other: &Self, out: &mut B) -> Result<(), Error> { + match self.secret.as_ref() { + Some(sk) => { + let xk = diffie_hellman(sk.secret_scalar(), other.public.as_affine()); + out.write_slice(xk.as_bytes())?; + Ok(()) + } + None => Err(err_msg!(MissingSecretKey)), + } + } +} + // impl TryFrom<&AnyPrivateKey> for K256SigningKey { // type Error = Error; @@ -281,8 +284,9 @@ mod tests { kp2.to_keypair_bytes().unwrap() ); - let xch1 = kp1.key_exchange_with(&kp2); - let xch2 = kp2.key_exchange_with(&kp1); + let xch1 = kp1.key_exchange_bytes(&kp2).unwrap(); + let xch2 = kp2.key_exchange_bytes(&kp1).unwrap(); + assert_eq!(xch1.len(), 32); assert_eq!(xch1, xch2); } diff --git a/askar-crypto/src/alg/p256.rs b/askar-crypto/src/alg/p256.rs index a75b5747..dbbcc9a2 100644 --- a/askar-crypto/src/alg/p256.rs +++ b/askar-crypto/src/alg/p256.rs @@ -13,6 +13,7 @@ use crate::{ buffer::{SecretBytes, WriteBuffer}, // any::{AnyPrivateKey, AnyPublicKey}, caps::{KeyCapSign, KeyCapVerify, KeyGen, KeySecretBytes, SignatureType}, + encrypt::KeyExchange, error::Error, jwk::{JwkEncoder, KeyToJwk}, random::with_rng, @@ -70,16 +71,6 @@ impl P256KeyPair { } } - pub fn key_exchange_with(&self, other: &Self) -> Option { - match self.secret.as_ref() { - Some(sk) => { - let xk = diffie_hellman(sk.secret_scalar(), other.public.as_affine()); - Some(SecretBytes::from(xk.as_bytes().to_vec())) - } - None => None, - } - } - pub fn to_keypair_bytes(&self) -> Option { if let Some(secret) = self.secret.as_ref() { let encp = EncodedPoint::encode(self.public, true); @@ -200,6 +191,19 @@ impl KeyToJwk for P256KeyPair { } } +impl KeyExchange for P256KeyPair { + fn key_exchange_buffer(&self, other: &Self, out: &mut B) -> Result<(), Error> { + match self.secret.as_ref() { + Some(sk) => { + let xk = diffie_hellman(sk.secret_scalar(), other.public.as_affine()); + out.write_slice(xk.as_bytes())?; + Ok(()) + } + None => Err(err_msg!(MissingSecretKey)), + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -267,8 +271,9 @@ mod tests { kp2.to_keypair_bytes().unwrap() ); - let xch1 = kp1.key_exchange_with(&kp2); - let xch2 = kp2.key_exchange_with(&kp1); + let xch1 = kp1.key_exchange_bytes(&kp2).unwrap(); + let xch2 = kp2.key_exchange_bytes(&kp1).unwrap(); + assert_eq!(xch1.len(), 32); assert_eq!(xch1, xch2); } diff --git a/askar-crypto/src/alg/x25519.rs b/askar-crypto/src/alg/x25519.rs index 4ad140f4..0281704a 100644 --- a/askar-crypto/src/alg/x25519.rs +++ b/askar-crypto/src/alg/x25519.rs @@ -10,6 +10,7 @@ use zeroize::Zeroize; use crate::{ buffer::{SecretBytes, WriteBuffer}, caps::{KeyGen, KeySecretBytes}, + encrypt::KeyExchange, error::Error, jwk::{JwkEncoder, KeyToJwk}, }; @@ -36,16 +37,6 @@ impl X25519KeyPair { } } - pub fn key_exchange_with(&self, other: &Self) -> Result { - match self.secret.as_ref() { - Some(sk) => { - let xk = sk.diffie_hellman(&other.public); - Ok(SecretBytes::from(xk.as_bytes().to_vec())) - } - None => Err(err_msg!(MissingSecretKey)), - } - } - pub fn from_keypair_bytes(kp: &[u8]) -> Result { if kp.len() != KEYPAIR_LENGTH { return Err(err_msg!("Invalid keypair bytes")); @@ -174,6 +165,19 @@ impl KeyToJwk for X25519KeyPair { } } +impl KeyExchange for X25519KeyPair { + fn key_exchange_buffer(&self, other: &Self, out: &mut B) -> Result<(), Error> { + match self.secret.as_ref() { + Some(sk) => { + let xk = sk.diffie_hellman(&other.public); + out.write_slice(xk.as_bytes())?; + Ok(()) + } + None => Err(err_msg!(MissingSecretKey)), + } + } +} + // impl TryFrom<&AnyPrivateKey> for X25519KeyPair { // type Error = Error; @@ -229,8 +233,9 @@ mod tests { kp2.to_keypair_bytes().unwrap() ); - let xch1 = kp1.key_exchange_with(&kp2); - let xch2 = kp2.key_exchange_with(&kp1); + let xch1 = kp1.key_exchange_bytes(&kp2).unwrap(); + let xch2 = kp2.key_exchange_bytes(&kp1).unwrap(); + assert_eq!(xch1.len(), 32); assert_eq!(xch1, xch2); } diff --git a/askar-crypto/src/buffer.rs b/askar-crypto/src/buffer.rs index c225005e..c64c86d4 100644 --- a/askar-crypto/src/buffer.rs +++ b/askar-crypto/src/buffer.rs @@ -391,17 +391,6 @@ impl SecretBytesMut<'_> { } } -impl aead::Buffer for SecretBytesMut<'_> { - fn extend_from_slice(&mut self, other: &[u8]) -> Result<(), aead::Error> { - self.extend_from_slice(other); - Ok(()) - } - - fn truncate(&mut self, len: usize) { - self.truncate(len); - } -} - impl AsRef<[u8]> for SecretBytesMut<'_> { fn as_ref(&self) -> &[u8] { self.0.as_slice() diff --git a/askar-crypto/src/encrypt/mod.rs b/askar-crypto/src/encrypt/mod.rs index 93b79662..47e28488 100644 --- a/askar-crypto/src/encrypt/mod.rs +++ b/askar-crypto/src/encrypt/mod.rs @@ -1,4 +1,7 @@ -use crate::{buffer::ResizeBuffer, error::Error}; +use crate::{ + buffer::{ResizeBuffer, SecretBytes, WriteBuffer}, + error::Error, +}; pub trait KeyAeadInPlace { /// Encrypt a secret value in place, appending the verification tag @@ -23,3 +26,17 @@ pub trait KeyAeadInPlace { /// Get the size of the verification tag fn tag_size() -> usize; } + +pub trait KeyExchange { + fn key_exchange_buffer(&self, other: &Rhs, out: &mut B) -> Result<(), Error>; + + fn key_exchange_bytes(&self, other: &Rhs) -> Result { + let mut buf = SecretBytes::with_capacity(128); + self.key_exchange_buffer(other, &mut buf)?; + Ok(buf) + } +} + +pub trait KeyFromExchange: Sized { + fn key_from_exchange(lhs: &Lhs, other: &Rhs) -> Result; +} From 92535427e7e89955ca4708a10c4a0f810caaf0e5 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Sun, 11 Apr 2021 14:26:00 -0700 Subject: [PATCH 016/116] implement JWK loading for p-256, k-256; adjust JWK traits and encoder Signed-off-by: Andrew Whitehead --- askar-crypto/src/alg/aesgcm.rs | 50 +++++++++++++++++++----- askar-crypto/src/alg/chacha20.rs | 51 +++++++++++++++++------- askar-crypto/src/alg/ed25519.rs | 12 +++--- askar-crypto/src/alg/k256.rs | 60 +++++++++++++++++++++++++---- askar-crypto/src/alg/p256.rs | 61 +++++++++++++++++++++++++---- askar-crypto/src/alg/x25519.rs | 12 +++--- askar-crypto/src/buffer.rs | 41 +++++++++++--------- askar-crypto/src/encrypt/mod.rs | 4 +- askar-crypto/src/jwk/encode.rs | 64 +++++++++++++++++++++++-------- askar-crypto/src/jwk/mod.rs | 21 ++++++---- askar-crypto/src/jwk/parts.rs | 66 ++++++++++++++++++++------------ 11 files changed, 327 insertions(+), 115 deletions(-) diff --git a/askar-crypto/src/alg/aesgcm.rs b/askar-crypto/src/alg/aesgcm.rs index 69cec750..37c8c320 100644 --- a/askar-crypto/src/alg/aesgcm.rs +++ b/askar-crypto/src/alg/aesgcm.rs @@ -2,16 +2,21 @@ use aead::{Aead, AeadInPlace, NewAead}; use aes_gcm::{Aes128Gcm, Aes256Gcm}; use zeroize::Zeroize; -use crate::generic_array::{typenum::Unsigned, GenericArray}; +use crate::{ + buffer::Writer, + generic_array::{typenum::Unsigned, GenericArray}, +}; use crate::{ buffer::{ArrayKey, ResizeBuffer, WriteBuffer}, caps::{KeyGen, KeySecretBytes}, - encrypt::KeyAeadInPlace, + encrypt::{FromKeyExchange, KeyAeadInPlace, KeyExchange}, error::Error, - jwk::{JwkEncoder, KeyToJwk}, + jwk::{JwkEncoder, ToJwk}, }; +pub static JWK_KEY_TYPE: &'static str = "oct"; + pub trait AesGcmType { type Aead: NewAead + Aead + AeadInPlace; @@ -48,6 +53,13 @@ type TagSize = <::Aead as Aead>::TagSize; // SECURITY: ArrayKey is zeroized on drop pub struct AesGcmKey(KeyType); +impl AesGcmKey { + #[inline] + pub(crate) fn uninit() -> Self { + Self(KeyType::::default()) + } +} + impl KeyGen for AesGcmKey { fn generate() -> Result { Ok(AesGcmKey(KeyType::::random())) @@ -63,7 +75,7 @@ impl KeySecretBytes for AesGcmKey { } fn to_key_secret_buffer(&self, out: &mut B) -> Result<(), Error> { - out.write_slice(&self.0[..]) + out.write_slice(self.0.as_ref()) } } @@ -82,7 +94,7 @@ impl KeyAeadInPlace for AesGcmKey { )); } let nonce = GenericArray::from_slice(nonce); - let chacha = T::Aead::new(&self.0); + let chacha = T::Aead::new(self.0.as_ref()); let tag = chacha .encrypt_in_place_detached(nonce, aad, buffer.as_mut()) .map_err(|e| err_msg!(Encryption, "{}", e))?; @@ -111,7 +123,7 @@ impl KeyAeadInPlace for AesGcmKey { let tag_start = buf_len - TagSize::::USIZE; let mut tag = GenericArray::default(); tag.clone_from_slice(&buffer.as_ref()[tag_start..]); - let chacha = T::Aead::new(&self.0); + let chacha = T::Aead::new(self.0.as_ref()); chacha .decrypt_in_place_detached(nonce, aad, &mut buffer.as_mut()[..tag_start], &tag) .map_err(|e| err_msg!(Encryption, "{}", e))?; @@ -130,20 +142,38 @@ impl KeyAeadInPlace for AesGcmKey { } } -impl KeyToJwk for AesGcmKey { - const KTY: &'static str = "oct"; - +impl ToJwk for AesGcmKey { fn to_jwk_buffer(&self, buffer: &mut JwkEncoder) -> Result<(), Error> { + buffer.add_str("kty", JWK_KEY_TYPE)?; if !buffer.is_secret() { return Err(err_msg!(Unsupported, "Cannot export as a public key")); } buffer.add_str("alg", T::JWK_ALG)?; - buffer.add_as_base64("k", &self.0[..])?; + buffer.add_as_base64("k", self.0.as_ref())?; buffer.add_str("use", "enc")?; Ok(()) } } +// for direct key agreement (not used currently) +impl FromKeyExchange for AesGcmKey +where + Lhs: KeyExchange, + T: AesGcmType, +{ + fn from_key_exchange(lhs: &Lhs, rhs: &Rhs) -> Result { + // NOTE: currently requires the exchange to produce a key of the same length, + // while it may be acceptable to just use the prefix if the output is longer? + let mut key = Self::uninit(); + let mut buf = Writer::from_slice(key.0.as_mut()); + lhs.key_exchange_buffer(rhs, &mut buf)?; + if buf.position() != T::key_size() { + return Err(err_msg!("invalid length for key exchange output")); + } + Ok(key) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/askar-crypto/src/alg/chacha20.rs b/askar-crypto/src/alg/chacha20.rs index 984981e9..26a69410 100644 --- a/askar-crypto/src/alg/chacha20.rs +++ b/askar-crypto/src/alg/chacha20.rs @@ -5,14 +5,16 @@ use zeroize::Zeroize; use crate::generic_array::{typenum::Unsigned, GenericArray}; use crate::{ - buffer::{ArrayKey, ResizeBuffer, WriteBuffer}, + buffer::{ArrayKey, ResizeBuffer, WriteBuffer, Writer}, caps::{KeyGen, KeySecretBytes}, - encrypt::KeyAeadInPlace, + encrypt::{FromKeyExchange, KeyAeadInPlace, KeyExchange}, error::Error, - jwk::{JwkEncoder, KeyToJwk}, + jwk::{JwkEncoder, ToJwk}, random::fill_random_deterministic, }; +pub static JWK_KEY_TYPE: &'static str = "oct"; + pub trait Chacha20Type { type Aead: NewAead + Aead + AeadInPlace; @@ -50,12 +52,17 @@ type TagSize = <::Aead as Aead>::TagSize; pub struct Chacha20Key(KeyType); impl Chacha20Key { + #[inline] + pub(crate) fn uninit() -> Self { + Self(KeyType::::default()) + } + // this is consistent with Indy's wallet wrapping key generation // FIXME - move to aries_askar, use from_key_secret_bytes pub fn from_seed(seed: &[u8]) -> Result { - let mut slf = KeyType::::default(); - fill_random_deterministic(seed, &mut slf)?; - Ok(Self(slf)) + let mut key = KeyType::::default(); + fill_random_deterministic(seed, key.as_mut())?; + Ok(Self(key)) } } @@ -74,7 +81,7 @@ impl KeySecretBytes for Chacha20Key { } fn to_key_secret_buffer(&self, out: &mut B) -> Result<(), Error> { - out.write_slice(&self.0[..]) + out.write_slice(self.0.as_ref()) } } @@ -106,7 +113,7 @@ impl KeyAeadInPlace for Chacha20Key { )); } let nonce = GenericArray::from_slice(nonce); - let chacha = T::Aead::new(&self.0); + let chacha = T::Aead::new(self.0.as_ref()); let tag = chacha .encrypt_in_place_detached(nonce, aad, buffer.as_mut()) .map_err(|e| err_msg!(Encryption, "{}", e))?; @@ -135,7 +142,7 @@ impl KeyAeadInPlace for Chacha20Key { let tag_start = buf_len - TagSize::::USIZE; let mut tag = GenericArray::default(); tag.clone_from_slice(&buffer.as_ref()[tag_start..]); - let chacha = T::Aead::new(&self.0); + let chacha = T::Aead::new(self.0.as_ref()); chacha .decrypt_in_place_detached(nonce, aad, &mut buffer.as_mut()[..tag_start], &tag) .map_err(|e| err_msg!(Encryption, "{}", e))?; @@ -154,20 +161,38 @@ impl KeyAeadInPlace for Chacha20Key { } } -impl KeyToJwk for Chacha20Key { - const KTY: &'static str = "oct"; - +impl ToJwk for Chacha20Key { fn to_jwk_buffer(&self, buffer: &mut JwkEncoder) -> Result<(), Error> { + buffer.add_str("kty", JWK_KEY_TYPE)?; if !buffer.is_secret() { return Err(err_msg!(Unsupported, "Cannot export as a public key")); } buffer.add_str("alg", T::JWK_ALG)?; - buffer.add_as_base64("k", &self.0[..])?; + buffer.add_as_base64("k", self.0.as_ref())?; buffer.add_str("use", "enc")?; Ok(()) } } +// for direct key agreement (not used currently) +impl FromKeyExchange for Chacha20Key +where + Lhs: KeyExchange, + T: Chacha20Type, +{ + fn from_key_exchange(lhs: &Lhs, rhs: &Rhs) -> Result { + // NOTE: currently requires the exchange to produce a key of the same length, + // while it may be acceptable to just use the prefix if the output is longer? + let mut key = Self::uninit(); + let mut buf = Writer::from_slice(key.0.as_mut()); + lhs.key_exchange_buffer(rhs, &mut buf)?; + if buf.position() != T::key_size() { + return Err(err_msg!("invalid length for key exchange output")); + } + Ok(key) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/askar-crypto/src/alg/ed25519.rs b/askar-crypto/src/alg/ed25519.rs index 75b7cf09..5b0d439e 100644 --- a/askar-crypto/src/alg/ed25519.rs +++ b/askar-crypto/src/alg/ed25519.rs @@ -15,7 +15,7 @@ use crate::{ buffer::{SecretBytes, WriteBuffer}, caps::{KeyCapSign, KeyCapVerify, KeyGen, KeySecretBytes, SignatureType}, error::Error, - jwk::{JwkEncoder, KeyToJwk}, + jwk::{JwkEncoder, ToJwk}, }; // FIXME - check for low-order points when loading public keys? @@ -27,6 +27,7 @@ pub const PUBLIC_KEY_LENGTH: usize = 32; pub const SECRET_KEY_LENGTH: usize = 32; pub const KEYPAIR_LENGTH: usize = SECRET_KEY_LENGTH + PUBLIC_KEY_LENGTH; +pub static JWK_KEY_TYPE: &'static str = "OKP"; pub static JWK_CURVE: &'static str = "Ed25519"; pub struct Ed25519KeyPair { @@ -215,10 +216,9 @@ impl KeyCapVerify for Ed25519KeyPair { } } -impl KeyToJwk for Ed25519KeyPair { - const KTY: &'static str = "OKP"; - +impl ToJwk for Ed25519KeyPair { fn to_jwk_buffer(&self, buffer: &mut JwkEncoder) -> Result<(), Error> { + buffer.add_str("kty", JWK_KEY_TYPE)?; buffer.add_str("crv", JWK_CURVE)?; buffer.add_as_base64("x", &self.to_public_key_bytes()[..])?; if buffer.is_secret() { @@ -301,7 +301,9 @@ mod tests { let test_pvt = base64::decode_config(test_pvt_b64, base64::URL_SAFE).unwrap(); let kp = Ed25519KeyPair::from_key_secret_bytes(&test_pvt).expect("Error creating signing key"); - let jwk = kp.to_jwk().expect("Error converting public key to JWK"); + let jwk = kp + .to_jwk_public() + .expect("Error converting public key to JWK"); let jwk = jwk.to_parts().expect("Error parsing JWK output"); assert_eq!(jwk.kty, "OKP"); assert_eq!(jwk.crv, JWK_CURVE); diff --git a/askar-crypto/src/alg/k256.rs b/askar-crypto/src/alg/k256.rs index 0c5da1aa..68fe14f2 100644 --- a/askar-crypto/src/alg/k256.rs +++ b/askar-crypto/src/alg/k256.rs @@ -5,16 +5,16 @@ use k256::{ signature::{Signer, Verifier}, Signature, SigningKey, VerifyingKey, }, - elliptic_curve::{ecdh::diffie_hellman, sec1::Coordinates}, + elliptic_curve::{ecdh::diffie_hellman, sec1::Coordinates, Curve}, EncodedPoint, PublicKey, SecretKey, }; use crate::{ - buffer::{SecretBytes, WriteBuffer}, + buffer::{ArrayKey, SecretBytes, WriteBuffer}, caps::{KeyCapSign, KeyCapVerify, KeyGen, KeySecretBytes, SignatureType}, encrypt::KeyExchange, error::Error, - jwk::{JwkEncoder, KeyToJwk}, + jwk::{FromJwk, JwkEncoder, JwkParts, ToJwk}, random::with_rng, }; @@ -24,8 +24,11 @@ pub const PUBLIC_KEY_LENGTH: usize = 33; // compressed size pub const SECRET_KEY_LENGTH: usize = 32; pub const KEYPAIR_LENGTH: usize = SECRET_KEY_LENGTH + PUBLIC_KEY_LENGTH; +pub static JWK_KEY_TYPE: &'static str = "EC"; pub static JWK_CURVE: &'static str = "secp256k1"; +type FieldSize = ::FieldSize; + #[derive(Clone, Debug)] pub struct K256KeyPair { // SECURITY: SecretKey zeroizes on drop @@ -83,6 +86,10 @@ impl K256KeyPair { } } + pub fn to_public_key_bytes(&self) -> SecretBytes { + SecretBytes::from(EncodedPoint::encode(self.public, true).as_ref().to_vec()) + } + pub(crate) fn to_signing_key(&self) -> Option { self.secret.as_ref().map(SigningKey::from) } @@ -166,9 +173,7 @@ impl KeyCapVerify for K256KeyPair { } } -impl KeyToJwk for K256KeyPair { - const KTY: &'static str = "EC"; - +impl ToJwk for K256KeyPair { fn to_jwk_buffer(&self, buffer: &mut JwkEncoder) -> Result<(), Error> { let encp = EncodedPoint::encode(self.public, false); let (x, y) = match encp.coordinates() { @@ -177,6 +182,7 @@ impl KeyToJwk for K256KeyPair { Coordinates::Compressed { .. } => unreachable!(), }; + buffer.add_str("kty", JWK_KEY_TYPE)?; buffer.add_str("crv", JWK_CURVE)?; buffer.add_as_base64("x", &x[..])?; buffer.add_as_base64("y", &y[..])?; @@ -190,6 +196,39 @@ impl KeyToJwk for K256KeyPair { } } +impl FromJwk for K256KeyPair { + fn from_jwk_parts(jwk: JwkParts<'_>) -> Result { + // SECURITY: ArrayKey zeroizes on drop + let mut pk_x = ArrayKey::::default(); + let mut pk_y = ArrayKey::::default(); + if jwk.x.decode_base64(pk_x.as_mut())? != pk_x.as_ref().len() { + return Err(err_msg!("invalid length for p-256 attribute 'x'")); + } + if jwk.y.decode_base64(pk_y.as_mut())? != pk_y.as_ref().len() { + return Err(err_msg!("invalid length for p-256 attribute 'y'")); + } + let pk = EncodedPoint::from_affine_coordinates(pk_x.as_ref(), pk_y.as_ref(), false) + .decode() + .map_err(|_| err_msg!("error decoding p-256 public key"))?; + let sk = if jwk.d.is_some() { + let mut sk = ArrayKey::::default(); + if jwk.d.decode_base64(sk.as_mut())? != sk.as_ref().len() { + return Err(err_msg!("invalid length for p-256 attribute 'd'")); + } + Some( + SecretKey::from_bytes(sk.as_ref()) + .map_err(|_| err_msg!("Invalid p-256 secret key bytes"))?, + ) + } else { + None + }; + Ok(Self { + secret: sk, + public: pk, + }) + } +} + impl KeyExchange for K256KeyPair { fn key_exchange_buffer(&self, other: &Self, out: &mut B) -> Result<(), Error> { match self.secret.as_ref() { @@ -238,13 +277,15 @@ mod tests { let test_pvt = base64::decode_config(test_pvt_b64, base64::URL_SAFE).unwrap(); let sk = K256KeyPair::from_key_secret_bytes(&test_pvt).expect("Error creating signing key"); - let jwk = sk.to_jwk().expect("Error converting key to JWK"); + let jwk = sk.to_jwk_public().expect("Error converting key to JWK"); let jwk = jwk.to_parts().expect("Error parsing JWK"); assert_eq!(jwk.kty, "EC"); assert_eq!(jwk.crv, JWK_CURVE); assert_eq!(jwk.x, test_pub_b64.0); assert_eq!(jwk.y, test_pub_b64.1); assert_eq!(jwk.d, None); + let pk_load = K256KeyPair::from_jwk_parts(jwk).unwrap(); + assert_eq!(sk.to_public_key_bytes(), pk_load.to_public_key_bytes()); let jwk = sk.to_jwk_secret().expect("Error converting key to JWK"); let jwk = jwk.to_parts().expect("Error parsing JWK"); @@ -253,6 +294,11 @@ mod tests { assert_eq!(jwk.x, test_pub_b64.0); assert_eq!(jwk.y, test_pub_b64.1); assert_eq!(jwk.d, test_pvt_b64); + let sk_load = K256KeyPair::from_jwk_parts(jwk).unwrap(); + assert_eq!( + sk.to_keypair_bytes().unwrap(), + sk_load.to_keypair_bytes().unwrap() + ); } #[test] diff --git a/askar-crypto/src/alg/p256.rs b/askar-crypto/src/alg/p256.rs index dbbcc9a2..41eb59a5 100644 --- a/askar-crypto/src/alg/p256.rs +++ b/askar-crypto/src/alg/p256.rs @@ -5,17 +5,16 @@ use p256::{ signature::{Signer, Verifier}, Signature, SigningKey, VerifyingKey, }, - elliptic_curve::{ecdh::diffie_hellman, sec1::Coordinates}, + elliptic_curve::{ecdh::diffie_hellman, sec1::Coordinates, Curve}, EncodedPoint, PublicKey, SecretKey, }; use crate::{ - buffer::{SecretBytes, WriteBuffer}, - // any::{AnyPrivateKey, AnyPublicKey}, + buffer::{ArrayKey, SecretBytes, WriteBuffer}, caps::{KeyCapSign, KeyCapVerify, KeyGen, KeySecretBytes, SignatureType}, encrypt::KeyExchange, error::Error, - jwk::{JwkEncoder, KeyToJwk}, + jwk::{FromJwk, JwkEncoder, JwkParts, ToJwk}, random::with_rng, }; @@ -25,8 +24,11 @@ pub const PUBLIC_KEY_LENGTH: usize = 33; // compressed size pub const SECRET_KEY_LENGTH: usize = 32; pub const KEYPAIR_LENGTH: usize = SECRET_KEY_LENGTH + PUBLIC_KEY_LENGTH; +pub static JWK_KEY_TYPE: &'static str = "EC"; pub static JWK_CURVE: &'static str = "P-256"; +type FieldSize = ::FieldSize; + #[derive(Clone, Debug)] pub struct P256KeyPair { // SECURITY: SecretKey zeroizes on drop @@ -84,6 +86,10 @@ impl P256KeyPair { } } + pub fn to_public_key_bytes(&self) -> SecretBytes { + SecretBytes::from(EncodedPoint::encode(self.public, true).as_ref().to_vec()) + } + pub(crate) fn to_signing_key(&self) -> Option { self.secret.clone().map(SigningKey::from) } @@ -167,9 +173,7 @@ impl KeyCapVerify for P256KeyPair { } } -impl KeyToJwk for P256KeyPair { - const KTY: &'static str = "EC"; - +impl ToJwk for P256KeyPair { fn to_jwk_buffer(&self, buffer: &mut JwkEncoder) -> Result<(), Error> { let encp = EncodedPoint::encode(self.public, false); let (x, y) = match encp.coordinates() { @@ -178,6 +182,7 @@ impl KeyToJwk for P256KeyPair { Coordinates::Compressed { .. } => unreachable!(), }; + buffer.add_str("kty", JWK_KEY_TYPE)?; buffer.add_str("crv", JWK_CURVE)?; buffer.add_as_base64("x", &x[..])?; buffer.add_as_base64("y", &y[..])?; @@ -191,6 +196,39 @@ impl KeyToJwk for P256KeyPair { } } +impl FromJwk for P256KeyPair { + fn from_jwk_parts(jwk: JwkParts<'_>) -> Result { + // SECURITY: ArrayKey zeroizes on drop + let mut pk_x = ArrayKey::::default(); + let mut pk_y = ArrayKey::::default(); + if jwk.x.decode_base64(pk_x.as_mut())? != pk_x.as_ref().len() { + return Err(err_msg!("invalid length for p-256 attribute 'x'")); + } + if jwk.y.decode_base64(pk_y.as_mut())? != pk_y.as_ref().len() { + return Err(err_msg!("invalid length for p-256 attribute 'y'")); + } + let pk = EncodedPoint::from_affine_coordinates(pk_x.as_ref(), pk_y.as_ref(), false) + .decode() + .map_err(|_| err_msg!("error decoding p-256 public key"))?; + let sk = if jwk.d.is_some() { + let mut sk = ArrayKey::::default(); + if jwk.d.decode_base64(sk.as_mut())? != sk.as_ref().len() { + return Err(err_msg!("invalid length for p-256 attribute 'd'")); + } + Some( + SecretKey::from_bytes(sk.as_ref()) + .map_err(|_| err_msg!("Invalid p-256 secret key bytes"))?, + ) + } else { + None + }; + Ok(Self { + secret: sk, + public: pk, + }) + } +} + impl KeyExchange for P256KeyPair { fn key_exchange_buffer(&self, other: &Self, out: &mut B) -> Result<(), Error> { match self.secret.as_ref() { @@ -225,13 +263,15 @@ mod tests { let test_pvt = base64::decode_config(test_pvt_b64, base64::URL_SAFE).unwrap(); let sk = P256KeyPair::from_key_secret_bytes(&test_pvt).expect("Error creating signing key"); - let jwk = sk.to_jwk().expect("Error converting key to JWK"); + let jwk = sk.to_jwk_public().expect("Error converting key to JWK"); let jwk = jwk.to_parts().expect("Error parsing JWK"); assert_eq!(jwk.kty, "EC"); assert_eq!(jwk.crv, JWK_CURVE); assert_eq!(jwk.x, test_pub_b64.0); assert_eq!(jwk.y, test_pub_b64.1); assert_eq!(jwk.d, None); + let pk_load = P256KeyPair::from_jwk_parts(jwk).unwrap(); + assert_eq!(sk.to_public_key_bytes(), pk_load.to_public_key_bytes()); let jwk = sk.to_jwk_secret().expect("Error converting key to JWK"); let jwk = jwk.to_parts().expect("Error parsing JWK"); @@ -240,6 +280,11 @@ mod tests { assert_eq!(jwk.x, test_pub_b64.0); assert_eq!(jwk.y, test_pub_b64.1); assert_eq!(jwk.d, test_pvt_b64); + let sk_load = P256KeyPair::from_jwk_parts(jwk).unwrap(); + assert_eq!( + sk.to_keypair_bytes().unwrap(), + sk_load.to_keypair_bytes().unwrap() + ); } #[test] diff --git a/askar-crypto/src/alg/x25519.rs b/askar-crypto/src/alg/x25519.rs index 0281704a..26544b0d 100644 --- a/askar-crypto/src/alg/x25519.rs +++ b/askar-crypto/src/alg/x25519.rs @@ -12,13 +12,14 @@ use crate::{ caps::{KeyGen, KeySecretBytes}, encrypt::KeyExchange, error::Error, - jwk::{JwkEncoder, KeyToJwk}, + jwk::{JwkEncoder, ToJwk}, }; pub const PUBLIC_KEY_LENGTH: usize = 32; pub const SECRET_KEY_LENGTH: usize = 32; pub const KEYPAIR_LENGTH: usize = SECRET_KEY_LENGTH + PUBLIC_KEY_LENGTH; +pub static JWK_KEY_TYPE: &'static str = "OKP"; pub static JWK_CURVE: &'static str = "X25519"; #[derive(Clone)] @@ -147,10 +148,9 @@ impl KeySecretBytes for X25519KeyPair { } } -impl KeyToJwk for X25519KeyPair { - const KTY: &'static str = "OKP"; - +impl ToJwk for X25519KeyPair { fn to_jwk_buffer(&self, buffer: &mut JwkEncoder) -> Result<(), Error> { + buffer.add_str("kty", JWK_KEY_TYPE)?; buffer.add_str("crv", JWK_CURVE)?; buffer.add_as_base64("x", &self.to_public_key_bytes()[..])?; if buffer.is_secret() { @@ -207,7 +207,9 @@ mod tests { let test_pvt = base64::decode_config(test_pvt_b64, base64::URL_SAFE).unwrap(); let kp = X25519KeyPair::from_key_secret_bytes(&test_pvt).expect("Error creating x25519 keypair"); - let jwk = kp.to_jwk().expect("Error converting public key to JWK"); + let jwk = kp + .to_jwk_public() + .expect("Error converting public key to JWK"); let jwk = jwk.to_parts().expect("Error parsing JWK output"); assert_eq!(jwk.kty, "OKP"); assert_eq!(jwk.crv, JWK_CURVE); diff --git a/askar-crypto/src/buffer.rs b/askar-crypto/src/buffer.rs index c64c86d4..222592b7 100644 --- a/askar-crypto/src/buffer.rs +++ b/askar-crypto/src/buffer.rs @@ -8,7 +8,7 @@ use core::{ fmt::{self, Debug, Formatter}, marker::PhantomData, mem::{self, ManuallyDrop}, - ops::{Deref, DerefMut}, + ops::Deref, }; use crate::generic_array::{typenum, ArrayLength, GenericArray}; @@ -18,7 +18,7 @@ use zeroize::Zeroize; use crate::{error::Error, random::fill_random}; /// A secure key representation for fixed-length keys -#[derive(Clone, Hash, Zeroize)] +#[derive(Clone, Hash)] pub struct ArrayKey>(GenericArray); impl> ArrayKey { @@ -48,6 +48,18 @@ impl> ArrayKey { } } +impl> AsRef> for ArrayKey { + fn as_ref(&self) -> &GenericArray { + &self.0 + } +} + +impl> AsMut> for ArrayKey { + fn as_mut(&mut self) -> &mut GenericArray { + &mut self.0 + } +} + impl> Default for ArrayKey { #[inline] fn default() -> Self { @@ -71,34 +83,21 @@ impl> Debug for ArrayKey { } } -impl> Deref for ArrayKey { - type Target = GenericArray; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl> DerefMut for ArrayKey { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - impl> PartialEq for ArrayKey { fn eq(&self, other: &Self) -> bool { - **self == **other + self.as_ref() == other.as_ref() } } impl> Eq for ArrayKey {} impl> PartialOrd for ArrayKey { fn partial_cmp(&self, other: &Self) -> Option { - self.0.partial_cmp(&*other) + self.0.partial_cmp(other.as_ref()) } } impl> Ord for ArrayKey { fn cmp(&self, other: &Self) -> Ordering { - self.0.cmp(&*other) + self.0.cmp(other.as_ref()) } } @@ -123,6 +122,12 @@ impl<'a, L: ArrayLength> Deserialize<'a> for ArrayKey { } } +impl> Zeroize for ArrayKey { + fn zeroize(&mut self) { + self.0.zeroize(); + } +} + impl> Drop for ArrayKey { fn drop(&mut self) { self.zeroize(); diff --git a/askar-crypto/src/encrypt/mod.rs b/askar-crypto/src/encrypt/mod.rs index 47e28488..096a5259 100644 --- a/askar-crypto/src/encrypt/mod.rs +++ b/askar-crypto/src/encrypt/mod.rs @@ -37,6 +37,6 @@ pub trait KeyExchange { } } -pub trait KeyFromExchange: Sized { - fn key_from_exchange(lhs: &Lhs, other: &Rhs) -> Result; +pub trait FromKeyExchange: Sized { + fn from_key_exchange(lhs: &Lhs, rhs: &Rhs) -> Result; } diff --git a/askar-crypto/src/jwk/encode.rs b/askar-crypto/src/jwk/encode.rs index c59c45de..4a65063d 100644 --- a/askar-crypto/src/jwk/encode.rs +++ b/askar-crypto/src/jwk/encode.rs @@ -19,35 +19,55 @@ impl bs58::encode::EncodeTarget for JwkBuffer<'_, B> { } } +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum JwkEncoderMode { + PublicKey, + SecretKey, + Thumbprint, +} + pub struct JwkEncoder<'b, B: WriteBuffer> { buffer: &'b mut B, - secret: bool, + empty: bool, + mode: JwkEncoderMode, } impl<'b, B: WriteBuffer> JwkEncoder<'b, B> { - pub fn new(buffer: &'b mut B, kty: &str, secret: bool) -> Result { - buffer.write_slice(b"{\"kty\":\"")?; - buffer.write_slice(kty.as_bytes())?; - buffer.write_slice(b"\"")?; - Ok(Self { buffer, secret }) + pub fn new(buffer: &'b mut B, mode: JwkEncoderMode) -> Result { + Ok(Self { + buffer, + empty: true, + mode, + }) } - pub fn add_str(&mut self, key: &str, value: &str) -> Result<(), Error> { + fn start_attr(&mut self, key: &str) -> Result<(), Error> { let buffer = &mut *self.buffer; - buffer.write_slice(b",\"")?; + if self.empty { + buffer.write_slice(b"{\"")?; + self.empty = false; + } else { + buffer.write_slice(b",\"")?; + } buffer.write_slice(key.as_bytes())?; - buffer.write_slice(b"\":\"")?; + buffer.write_slice(b"\":")?; + Ok(()) + } + + pub fn add_str(&mut self, key: &str, value: &str) -> Result<(), Error> { + self.start_attr(key)?; + let buffer = &mut *self.buffer; + buffer.write_slice(b"\"")?; buffer.write_slice(value.as_bytes())?; buffer.write_slice(b"\"")?; Ok(()) } pub fn add_as_base64(&mut self, key: &str, value: &[u8]) -> Result<(), Error> { + self.start_attr(key)?; let buffer = &mut *self.buffer; - buffer.write_slice(b",\"")?; - buffer.write_slice(key.as_bytes())?; - buffer.write_slice(b"\":\"")?; let enc_size = ((value.len() << 2) + 2) / 3; + buffer.write_slice(b"\"")?; buffer.write_with(enc_size, |mbuf| { let len = base64::encode_config_slice(value, base64::URL_SAFE_NO_PAD, mbuf); Ok(len) @@ -57,8 +77,8 @@ impl<'b, B: WriteBuffer> JwkEncoder<'b, B> { } pub fn add_key_ops(&mut self, ops: impl Into) -> Result<(), Error> { + self.start_attr("key_ops")?; let buffer = &mut *self.buffer; - buffer.write_slice(b",\"key_ops\":[")?; for (idx, op) in ops.into().into_iter().enumerate() { if idx > 0 { buffer.write_slice(b",\"")?; @@ -72,12 +92,26 @@ impl<'b, B: WriteBuffer> JwkEncoder<'b, B> { Ok(()) } + pub fn mode(&self) -> JwkEncoderMode { + self.mode + } + + pub fn is_public(&self) -> bool { + matches!(self.mode, JwkEncoderMode::PublicKey) + } + pub fn is_secret(&self) -> bool { - self.secret + matches!(self.mode, JwkEncoderMode::SecretKey) + } + + pub fn is_thumbprint(&self) -> bool { + matches!(self.mode, JwkEncoderMode::Thumbprint) } pub fn finalize(self) -> Result<(), Error> { - self.buffer.write_slice(b"}")?; + if !self.empty { + self.buffer.write_slice(b"}")?; + } Ok(()) } } diff --git a/askar-crypto/src/jwk/mod.rs b/askar-crypto/src/jwk/mod.rs index 91fe4422..09837a89 100644 --- a/askar-crypto/src/jwk/mod.rs +++ b/askar-crypto/src/jwk/mod.rs @@ -5,7 +5,7 @@ use zeroize::Zeroize; use crate::{buffer::WriteBuffer, error::Error}; mod encode; -pub use encode::JwkEncoder; +pub use encode::{JwkEncoder, JwkEncoderMode}; mod ops; pub use self::ops::{KeyOps, KeyOpsSet}; @@ -47,14 +47,12 @@ impl Zeroize for Jwk<'_> { } } -pub trait KeyToJwk { - const KTY: &'static str; - +pub trait ToJwk { fn to_jwk_buffer(&self, buffer: &mut JwkEncoder) -> Result<(), Error>; - fn to_jwk(&self) -> Result, Error> { + fn to_jwk_public(&self) -> Result, Error> { let mut v = Vec::with_capacity(128); - let mut buf = JwkEncoder::new(&mut v, Self::KTY, false)?; + let mut buf = JwkEncoder::new(&mut v, JwkEncoderMode::PublicKey)?; self.to_jwk_buffer(&mut buf)?; buf.finalize()?; Ok(Jwk::Encoded(Cow::Owned(String::from_utf8(v).unwrap()))) @@ -62,13 +60,22 @@ pub trait KeyToJwk { fn to_jwk_secret(&self) -> Result, Error> { let mut v = Vec::with_capacity(128); - let mut buf = JwkEncoder::new(&mut v, Self::KTY, true)?; + let mut buf = JwkEncoder::new(&mut v, JwkEncoderMode::SecretKey)?; self.to_jwk_buffer(&mut buf)?; buf.finalize()?; Ok(Jwk::Encoded(Cow::Owned(String::from_utf8(v).unwrap()))) } } +pub trait FromJwk: Sized { + fn from_jwk(jwk: Jwk<'_>) -> Result { + let parts = jwk.to_parts()?; + Self::from_jwk_parts(parts) + } + + fn from_jwk_parts(jwk: JwkParts<'_>) -> Result; +} + // pub trait JwkBuilder<'s> { // // key type // kty: &'a str, diff --git a/askar-crypto/src/jwk/parts.rs b/askar-crypto/src/jwk/parts.rs index 82b60365..fadb1fe5 100644 --- a/askar-crypto/src/jwk/parts.rs +++ b/askar-crypto/src/jwk/parts.rs @@ -1,54 +1,72 @@ use core::{ fmt::{self, Debug, Formatter}, marker::PhantomData, - ops::Deref, }; use serde::de::{Deserialize, Deserializer, MapAccess, SeqAccess, Visitor}; use super::ops::{KeyOps, KeyOpsSet}; +use crate::error::Error; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct JwkParts<'a> { // key type pub kty: &'a str, // key ID - pub kid: OptStr<'a>, + pub kid: OptAttr<'a>, // curve type - pub crv: OptStr<'a>, + pub crv: OptAttr<'a>, // curve key public y coordinate - pub x: OptStr<'a>, + pub x: OptAttr<'a>, // curve key public y coordinate - pub y: OptStr<'a>, + pub y: OptAttr<'a>, // curve key private key bytes - pub d: OptStr<'a>, + pub d: OptAttr<'a>, // used by symmetric keys like AES - pub k: OptStr<'a>, + pub k: OptAttr<'a>, // recognized key operations pub key_ops: Option, } #[derive(Copy, Clone, Default, PartialEq, Eq)] #[repr(transparent)] -pub struct OptStr<'a>(Option<&'a str>); +pub struct OptAttr<'a>(Option<&'a str>); -impl OptStr<'_> { +impl OptAttr<'_> { pub fn is_none(&self) -> bool { self.0.is_none() } + pub fn is_some(&self) -> bool { + self.0.is_some() + } + pub fn to_option(&self) -> Option<&str> { self.0 } + + pub fn decode_base64(&self, output: &mut [u8]) -> Result { + if let Some(s) = self.0 { + let max_input = (output.len() * 4 + 2) / 3; // ceil(4*n/3) + if s.len() > max_input { + Err(err_msg!("base64 length exceeds max")) + } else { + base64::decode_config_slice(s, base64::URL_SAFE_NO_PAD, output) + .map_err(|e| err_msg!("base64 decode error: {}", e)) + } + } else { + Err(err_msg!("empty attribute")) + } + } } -impl AsRef<[u8]> for OptStr<'_> { +impl AsRef<[u8]> for OptAttr<'_> { fn as_ref(&self) -> &[u8] { self.0.unwrap_or_default().as_bytes() } } -impl Debug for OptStr<'_> { +impl Debug for OptAttr<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self.0 { None => f.write_str("None"), @@ -57,33 +75,31 @@ impl Debug for OptStr<'_> { } } -impl Deref for OptStr<'_> { - type Target = str; - - fn deref(&self) -> &Self::Target { +impl AsRef for OptAttr<'_> { + fn as_ref(&self) -> &str { self.0.unwrap_or_default() } } -impl<'o> From<&'o str> for OptStr<'o> { +impl<'o> From<&'o str> for OptAttr<'o> { fn from(s: &'o str) -> Self { Self(Some(s)) } } -impl<'o> From> for OptStr<'o> { +impl<'o> From> for OptAttr<'o> { fn from(s: Option<&'o str>) -> Self { Self(s) } } -impl PartialEq> for OptStr<'_> { +impl PartialEq> for OptAttr<'_> { fn eq(&self, other: &Option<&str>) -> bool { self.0 == *other } } -impl PartialEq<&str> for OptStr<'_> { +impl PartialEq<&str> for OptAttr<'_> { fn eq(&self, other: &&str) -> bool { match self.0 { None => false, @@ -143,12 +159,12 @@ impl<'de> Visitor<'de> for JwkMapVisitor<'de> { if let Some(kty) = kty { Ok(JwkParts { kty, - kid: OptStr::from(kid), - crv: OptStr::from(crv), - x: OptStr::from(x), - y: OptStr::from(y), - d: OptStr::from(d), - k: OptStr::from(k), + kid: kid.into(), + crv: crv.into(), + x: x.into(), + y: y.into(), + d: d.into(), + k: k.into(), key_ops, }) } else { From fe0c0881f567499202a6062d61ab49dca1ac8b98 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Sun, 11 Apr 2021 14:26:28 -0700 Subject: [PATCH 017/116] add single-pass concat-KDF Signed-off-by: Andrew Whitehead --- askar-crypto/src/kdf/concat.rs | 104 +++++++++++++++++++++++++-------- 1 file changed, 81 insertions(+), 23 deletions(-) diff --git a/askar-crypto/src/kdf/concat.rs b/askar-crypto/src/kdf/concat.rs index 4f6ba68d..6ec58d18 100644 --- a/askar-crypto/src/kdf/concat.rs +++ b/askar-crypto/src/kdf/concat.rs @@ -2,26 +2,28 @@ use core::marker::PhantomData; -use digest::{Digest, FixedOutput}; +use digest::Digest; -use crate::error::Error; +use crate::generic_array::GenericArray; + +use crate::{buffer::WriteBuffer, error::Error}; pub struct ConcatKDF(PhantomData); -#[derive(Default)] -pub struct Params<'p> { - alg: &'p [u8], - apu: &'p [u8], - apv: &'p [u8], +#[derive(Clone, Copy, Default)] +pub struct ConcatKDFParams<'p> { + pub alg: &'p [u8], + pub apu: &'p [u8], + pub apv: &'p [u8], } impl ConcatKDF where - H: Digest + FixedOutput, + H: Digest, { pub fn derive_key( message: &[u8], - params: Params<'_>, + params: ConcatKDFParams<'_>, mut output: &mut [u8], ) -> Result<(), Error> { let output_len = output.len(); @@ -29,36 +31,92 @@ where // output_len is used as SuppPubInfo later return Err(err_msg!("exceeded max output size for concat KDF")); } - let mut counter = 1u32; + let mut hasher = ConcatKDFHash::::new(); let mut remain = output_len; - let mut hash = H::new(); while remain > 0 { - hash.update(counter.to_be_bytes()); - hash.update(message); - hash.update((params.alg.len() as u32).to_be_bytes()); - hash.update(params.alg); - hash.update((params.apu.len() as u32).to_be_bytes()); - hash.update(params.apu); - hash.update((params.apv.len() as u32).to_be_bytes()); - hash.update(params.apv); - hash.update((output_len as u32 * 8).to_be_bytes()); - let hashed = hash.finalize_reset(); + hasher.start_pass(); + hasher.hash_message(message); + let hashed = hasher.finish_pass(params, output_len); let cp_size = hashed.len().min(remain); &output[..cp_size].copy_from_slice(&hashed[..cp_size]); output = &mut output[cp_size..]; remain -= cp_size; - counter += 1; } Ok(()) } } +pub(crate) struct ConcatKDFHash { + hasher: H, + counter: u32, +} + +impl ConcatKDFHash { + pub fn new() -> Self { + Self { + hasher: H::new(), + counter: 1, + } + } + + pub fn start_pass(&mut self) { + self.hasher.update(self.counter.to_be_bytes()); + self.counter += 1; + } + + pub fn hash_message(&mut self, message: &[u8]) { + self.hasher.update(message); + } + + pub fn finish_pass( + &mut self, + params: ConcatKDFParams<'_>, + output_len: usize, + ) -> GenericArray { + let hash = &mut self.hasher; + hash.update((params.alg.len() as u32).to_be_bytes()); + hash.update(params.alg); + hash.update((params.apu.len() as u32).to_be_bytes()); + hash.update(params.apu); + hash.update((params.apv.len() as u32).to_be_bytes()); + hash.update(params.apv); + hash.update((output_len as u32 * 8).to_be_bytes()); + hash.finalize_reset() + } +} + +const HASH_BUFFER_SIZE: usize = 128; + +impl WriteBuffer for ConcatKDFHash { + fn write_slice(&mut self, data: &[u8]) -> Result<(), Error> { + self.hasher.update(data); + Ok(()) + } + + fn write_with( + &mut self, + max_len: usize, + f: impl FnOnce(&mut [u8]) -> Result, + ) -> Result { + // this could use a Vec to support larger inputs + // but for current purposes a small fixed buffer is fine + if max_len > HASH_BUFFER_SIZE { + return Err(err_msg!("exceeded hash buffer size")); + } + let mut buf = [0u8; HASH_BUFFER_SIZE]; + let written = f(&mut buf[..max_len])?; + self.write_slice(&buf[..written])?; + Ok(written) + } +} + #[cfg(test)] mod tests { use super::*; use sha2::Sha256; #[test] + // testing with ConcatKDF - single pass via ConcatKDFHash is tested elsewhere fn expected_1pu_output() { let z = hex!( "9e56d91d817135d372834283bf84269cfb316ea3da806a48f6daa7798cfe90c4 @@ -67,7 +125,7 @@ mod tests { let mut output = [0u8; 32]; ConcatKDF::::derive_key( &z, - Params { + ConcatKDFParams { alg: b"A256GCM", apu: b"Alice", apv: b"Bob", From d30ddfe75eaed19d42f2fc4a04e7811ba238f303 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Sun, 11 Apr 2021 14:40:05 -0700 Subject: [PATCH 018/116] add ecdh-1pu key derivation Signed-off-by: Andrew Whitehead --- askar-crypto/src/encrypt/ecdh_1pu.rs | 119 +++++++++++++++++++++++++++ askar-crypto/src/encrypt/mod.rs | 3 + askar-crypto/src/jwk/mod.rs | 24 ++++++ 3 files changed, 146 insertions(+) create mode 100644 askar-crypto/src/encrypt/ecdh_1pu.rs diff --git a/askar-crypto/src/encrypt/ecdh_1pu.rs b/askar-crypto/src/encrypt/ecdh_1pu.rs new file mode 100644 index 00000000..aee635c9 --- /dev/null +++ b/askar-crypto/src/encrypt/ecdh_1pu.rs @@ -0,0 +1,119 @@ +use sha2::Sha256; +use zeroize::Zeroize; + +use crate::{ + buffer::WriteBuffer, + caps::KeyGen, + encrypt::KeyExchange, + error::Error, + jwk::{JwkEncoder, ToJwk}, + kdf::concat::{ConcatKDFHash, ConcatKDFParams}, +}; + +fn ecdh_1pu_derive_shared( + send_key: &Key, + recip_key: &Key, + ephem_key: &Key, + params: ConcatKDFParams, + key_output: &mut [u8], +) -> Result<(), Error> +where + Key: KeyExchange, +{ + let output_len = key_output.len(); + // one-pass KDF only produces 256 bits of output + assert!(output_len <= 32); + let mut kdf = ConcatKDFHash::::new(); + kdf.start_pass(); + + // hash Zs and Ze directly into the KDF + ephem_key.key_exchange_buffer(recip_key, &mut kdf)?; + send_key.key_exchange_buffer(recip_key, &mut kdf)?; + + let mut key = kdf.finish_pass(params, output_len); + key_output.copy_from_slice(&key[..output_len]); + key.zeroize(); + + Ok(()) +} + +pub fn ecdh_1pu_direct( + send_key: &Key, + recip_key: &Key, + alg: &[u8], + apu: &[u8], + apv: &[u8], + key_output: &mut [u8], + jwk_output: &mut JwkEncoder, +) -> Result<(), Error> +where + Key: KeyGen + KeyExchange + ToJwk, +{ + let ephem_key = Key::generate()?; + ephem_key.to_jwk_buffer(jwk_output)?; + + let params = ConcatKDFParams { alg, apu, apv }; + ecdh_1pu_derive_shared(send_key, recip_key, &ephem_key, params, key_output)?; + + // SECURITY: keys must zeroize themselves on drop + drop(ephem_key); + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + // from RFC: https://tools.ietf.org/html/draft-madden-jose-ecdh-1pu-03#appendix-A + fn expected_1pu_direct_output() { + use crate::alg::p256::P256KeyPair; + use crate::jwk::{FromJwk, Jwk}; + + let alice_sk = P256KeyPair::from_jwk(Jwk::from( + r#"{"kty":"EC", + "crv":"P-256", + "x":"WKn-ZIGevcwGIyyrzFoZNBdaq9_TsqzGl96oc0CWuis", + "y":"y77t-RvAHRKTsSGdIYUfweuOvwrvDD-Q3Hv5J0fSKbE", + "d":"Hndv7ZZjs_ke8o9zXYo3iq-Yr8SewI5vrqd0pAvEPqg"}"#, + )) + .unwrap(); + let bob_sk = P256KeyPair::from_jwk(Jwk::from( + r#"{"kty":"EC", + "crv":"P-256", + "x":"weNJy2HscCSM6AEDTDg04biOvhFhyyWvOHQfeF_PxMQ", + "y":"e8lnCO-AlStT-NJVX-crhB7QRYhiix03illJOVAOyck", + "d":"VEmDZpDXXK8p8N0Cndsxs924q6nS1RXFASRl6BfUqdw"}"#, + )) + .unwrap(); + let ephem_sk = P256KeyPair::from_jwk(Jwk::from( + r#"{"kty":"EC", + "crv":"P-256", + "x":"gI0GAILBdu7T53akrFmMyGcsF3n5dO7MmwNBHKW5SV0", + "y":"SLW_xSffzlPWrHEVI30DHM_4egVwt3NQqeUD7nMFpps", + "d":"0_NxaRPUMQoAJt50Gz8YiTr8gRTwyEaCumd-MToTmIo"}"#, + )) + .unwrap(); + + let mut key_output = [0u8; 32]; + + ecdh_1pu_derive_shared( + &alice_sk, + &bob_sk, + &ephem_sk, + ConcatKDFParams { + alg: b"A256GCM", + apu: b"Alice", + apv: b"Bob", + }, + &mut key_output, + ) + .unwrap(); + + assert_eq!( + key_output, + hex!("6caf13723d14850ad4b42cd6dde935bffd2fff00a9ba70de05c203a5e1722ca7") + ); + } +} diff --git a/askar-crypto/src/encrypt/mod.rs b/askar-crypto/src/encrypt/mod.rs index 096a5259..06ac6b6d 100644 --- a/askar-crypto/src/encrypt/mod.rs +++ b/askar-crypto/src/encrypt/mod.rs @@ -3,6 +3,9 @@ use crate::{ error::Error, }; +mod ecdh_1pu; +pub use ecdh_1pu::ecdh_1pu_direct; + pub trait KeyAeadInPlace { /// Encrypt a secret value in place, appending the verification tag fn encrypt_in_place( diff --git a/askar-crypto/src/jwk/mod.rs b/askar-crypto/src/jwk/mod.rs index 09837a89..c4581320 100644 --- a/askar-crypto/src/jwk/mod.rs +++ b/askar-crypto/src/jwk/mod.rs @@ -37,6 +37,30 @@ impl Jwk<'_> { } } +impl<'a> From> for Jwk<'a> { + fn from(jwk: Cow<'a, str>) -> Self { + Jwk::Encoded(jwk) + } +} + +impl<'a> From<&'a str> for Jwk<'a> { + fn from(jwk: &'a str) -> Self { + Jwk::Encoded(Cow::Borrowed(jwk)) + } +} + +impl<'a> From for Jwk<'a> { + fn from(jwk: String) -> Self { + Jwk::Encoded(Cow::Owned(jwk)) + } +} + +impl<'a> From> for Jwk<'a> { + fn from(jwk: JwkParts<'a>) -> Self { + Jwk::Parts(jwk) + } +} + impl Zeroize for Jwk<'_> { fn zeroize(&mut self) { match self { From bc32f4757170fa4a5f472a623b5a305a4f397f50 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Sun, 11 Apr 2021 16:42:19 -0700 Subject: [PATCH 019/116] add ecdh-es key derivation; add JWK loading for ed25519 and x25519 Signed-off-by: Andrew Whitehead --- askar-crypto/src/alg/ed25519.rs | 53 +++++++++---- askar-crypto/src/alg/k256.rs | 12 --- askar-crypto/src/alg/x25519.rs | 52 +++++++++---- askar-crypto/src/encrypt/ecdh_1pu.rs | 11 +-- askar-crypto/src/encrypt/ecdh_es.rs | 111 +++++++++++++++++++++++++++ askar-crypto/src/encrypt/mod.rs | 5 +- 6 files changed, 197 insertions(+), 47 deletions(-) create mode 100644 askar-crypto/src/encrypt/ecdh_es.rs diff --git a/askar-crypto/src/alg/ed25519.rs b/askar-crypto/src/alg/ed25519.rs index 5b0d439e..7fe975fa 100644 --- a/askar-crypto/src/alg/ed25519.rs +++ b/askar-crypto/src/alg/ed25519.rs @@ -11,11 +11,11 @@ use x25519_dalek::{PublicKey as XPublicKey, StaticSecret as XSecretKey}; use super::x25519::X25519KeyPair; use crate::{ - // any::{AnyPrivateKey, AnyPublicKey}, - buffer::{SecretBytes, WriteBuffer}, + buffer::{ArrayKey, SecretBytes, WriteBuffer}, caps::{KeyCapSign, KeyCapVerify, KeyGen, KeySecretBytes, SignatureType}, error::Error, - jwk::{JwkEncoder, ToJwk}, + generic_array::typenum::U32, + jwk::{FromJwk, JwkEncoder, JwkParts, ToJwk}, }; // FIXME - check for low-order points when loading public keys? @@ -231,6 +231,34 @@ impl ToJwk for Ed25519KeyPair { } } +impl FromJwk for Ed25519KeyPair { + fn from_jwk_parts(jwk: JwkParts<'_>) -> Result { + // SECURITY: ArrayKey zeroizes on drop + let mut pk = ArrayKey::::default(); + if jwk.x.decode_base64(pk.as_mut())? != pk.as_ref().len() { + return Err(err_msg!("invalid length for ed25519 attribute 'x'")); + } + let pk = PublicKey::from_bytes(&pk.as_ref()[..]) + .map_err(|_| err_msg!("Invalid ed25519 public key bytes"))?; + let sk = if jwk.d.is_some() { + let mut sk = ArrayKey::::default(); + if jwk.d.decode_base64(sk.as_mut())? != sk.as_ref().len() { + return Err(err_msg!("invalid length for ed25519 attribute 'd'")); + } + Some( + SecretKey::from_bytes(&sk.as_ref()[..]) + .map_err(|_| err_msg!("Invalid ed25519 secret key bytes"))?, + ) + } else { + None + }; + Ok(Self { + secret: sk, + public: pk, + }) + } +} + /// FIXME implement debug // SECURITY: ExpandedSecretKey zeroizes on drop pub struct Ed25519SigningKey<'p>(ExpandedSecretKey, &'p PublicKey); @@ -241,18 +269,6 @@ impl Ed25519SigningKey<'_> { } } -// impl TryFrom<&AnyPrivateKey> for Ed25519KeyPair { -// type Error = Error; - -// fn try_from(value: &AnyPrivateKey) -> Result { -// if value.alg == KeyAlg::Ed25519 { -// Self::from_bytes(value.data.as_ref()) -// } else { -// Err(err_msg!(Unsupported, "Expected ed25519 key type")) -// } -// } -// } - #[cfg(test)] mod tests { use super::*; @@ -308,6 +324,8 @@ mod tests { assert_eq!(jwk.kty, "OKP"); assert_eq!(jwk.crv, JWK_CURVE); assert_eq!(jwk.x, "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"); + let pk_load = Ed25519KeyPair::from_jwk_parts(jwk).unwrap(); + assert_eq!(kp.to_public_key_bytes(), pk_load.to_public_key_bytes()); let jwk = kp .to_jwk_secret() @@ -317,6 +335,11 @@ mod tests { assert_eq!(jwk.crv, JWK_CURVE); assert_eq!(jwk.x, test_pub_b64); assert_eq!(jwk.d, test_pvt_b64); + let sk_load = Ed25519KeyPair::from_jwk_parts(jwk).unwrap(); + assert_eq!( + kp.to_keypair_bytes().unwrap(), + sk_load.to_keypair_bytes().unwrap() + ); } #[test] diff --git a/askar-crypto/src/alg/k256.rs b/askar-crypto/src/alg/k256.rs index 68fe14f2..ae3171cc 100644 --- a/askar-crypto/src/alg/k256.rs +++ b/askar-crypto/src/alg/k256.rs @@ -242,18 +242,6 @@ impl KeyExchange for K256KeyPair { } } -// impl TryFrom<&AnyPrivateKey> for K256SigningKey { -// type Error = Error; - -// fn try_from(value: &AnyPrivateKey) -> Result { -// if value.alg == KeyAlg::Ecdsa(EcCurves::Secp256k1) { -// Self::from_bytes(value.data.as_ref()) -// } else { -// Err(err_msg!(Unsupported, "Expected k-256 key type")) -// } -// } -// } - #[cfg(test)] mod tests { use super::*; diff --git a/askar-crypto/src/alg/x25519.rs b/askar-crypto/src/alg/x25519.rs index 26544b0d..e9385ed2 100644 --- a/askar-crypto/src/alg/x25519.rs +++ b/askar-crypto/src/alg/x25519.rs @@ -3,16 +3,17 @@ use core::{ fmt::{self, Debug, Formatter}, }; +use crate::generic_array::typenum::U32; use rand::rngs::OsRng; use x25519_dalek::{PublicKey, StaticSecret as SecretKey}; use zeroize::Zeroize; use crate::{ - buffer::{SecretBytes, WriteBuffer}, + buffer::{ArrayKey, SecretBytes, WriteBuffer}, caps::{KeyGen, KeySecretBytes}, encrypt::KeyExchange, error::Error, - jwk::{JwkEncoder, ToJwk}, + jwk::{FromJwk, JwkEncoder, JwkParts, ToJwk}, }; pub const PUBLIC_KEY_LENGTH: usize = 32; @@ -165,6 +166,34 @@ impl ToJwk for X25519KeyPair { } } +impl FromJwk for X25519KeyPair { + fn from_jwk_parts(jwk: JwkParts<'_>) -> Result { + // SECURITY: ArrayKey zeroizes on drop + let mut pk = ArrayKey::::default(); + if jwk.x.decode_base64(pk.as_mut())? != pk.as_ref().len() { + return Err(err_msg!("invalid length for x25519 attribute 'x'")); + } + let pk = PublicKey::from( + TryInto::<[u8; PUBLIC_KEY_LENGTH]>::try_into(&pk.as_ref()[..]).unwrap(), + ); + let sk = if jwk.d.is_some() { + let mut sk = ArrayKey::::default(); + if jwk.d.decode_base64(sk.as_mut())? != sk.as_ref().len() { + return Err(err_msg!("invalid length for x25519 attribute 'd'")); + } + Some(SecretKey::from( + TryInto::<[u8; SECRET_KEY_LENGTH]>::try_into(&sk.as_ref()[..]).unwrap(), + )) + } else { + None + }; + Ok(Self { + secret: sk, + public: pk, + }) + } +} + impl KeyExchange for X25519KeyPair { fn key_exchange_buffer(&self, other: &Self, out: &mut B) -> Result<(), Error> { match self.secret.as_ref() { @@ -178,18 +207,6 @@ impl KeyExchange for X25519KeyPair { } } -// impl TryFrom<&AnyPrivateKey> for X25519KeyPair { -// type Error = Error; - -// fn try_from(value: &AnyPrivateKey) -> Result { -// if value.alg == KeyAlg::X25519 { -// Self::from_bytes(value.data.as_ref()) -// } else { -// Err(err_msg!(Unsupported, "Expected x25519 key type")) -// } -// } -// } - #[cfg(test)] mod tests { use super::*; @@ -215,6 +232,8 @@ mod tests { assert_eq!(jwk.crv, JWK_CURVE); assert_eq!(jwk.x, "tGskN_ae61DP4DLY31_fjkbvnKqf-ze7kA6Cj2vyQxU"); assert_eq!(jwk.d, None); + let pk_load = X25519KeyPair::from_jwk_parts(jwk).unwrap(); + assert_eq!(kp.to_public_key_bytes(), pk_load.to_public_key_bytes()); let jwk = kp .to_jwk_secret() @@ -224,6 +243,11 @@ mod tests { assert_eq!(jwk.crv, JWK_CURVE); assert_eq!(jwk.x, "tGskN_ae61DP4DLY31_fjkbvnKqf-ze7kA6Cj2vyQxU"); assert_eq!(jwk.d, test_pvt_b64); + let sk_load = X25519KeyPair::from_jwk_parts(jwk).unwrap(); + assert_eq!( + kp.to_keypair_bytes().unwrap(), + sk_load.to_keypair_bytes().unwrap() + ); } #[test] diff --git a/askar-crypto/src/encrypt/ecdh_1pu.rs b/askar-crypto/src/encrypt/ecdh_1pu.rs index aee635c9..86486730 100644 --- a/askar-crypto/src/encrypt/ecdh_1pu.rs +++ b/askar-crypto/src/encrypt/ecdh_1pu.rs @@ -6,14 +6,14 @@ use crate::{ caps::KeyGen, encrypt::KeyExchange, error::Error, - jwk::{JwkEncoder, ToJwk}, + jwk::{JwkEncoder, JwkEncoderMode, ToJwk}, kdf::concat::{ConcatKDFHash, ConcatKDFParams}, }; fn ecdh_1pu_derive_shared( + ephem_key: &Key, send_key: &Key, recip_key: &Key, - ephem_key: &Key, params: ConcatKDFParams, key_output: &mut [u8], ) -> Result<(), Error> @@ -44,16 +44,17 @@ pub fn ecdh_1pu_direct( apu: &[u8], apv: &[u8], key_output: &mut [u8], - jwk_output: &mut JwkEncoder, + jwk_output: &mut B, ) -> Result<(), Error> where Key: KeyGen + KeyExchange + ToJwk, { let ephem_key = Key::generate()?; - ephem_key.to_jwk_buffer(jwk_output)?; + let mut encoder = JwkEncoder::new(jwk_output, JwkEncoderMode::PublicKey)?; + ephem_key.to_jwk_buffer(&mut encoder)?; let params = ConcatKDFParams { alg, apu, apv }; - ecdh_1pu_derive_shared(send_key, recip_key, &ephem_key, params, key_output)?; + ecdh_1pu_derive_shared(&ephem_key, send_key, recip_key, params, key_output)?; // SECURITY: keys must zeroize themselves on drop drop(ephem_key); diff --git a/askar-crypto/src/encrypt/ecdh_es.rs b/askar-crypto/src/encrypt/ecdh_es.rs new file mode 100644 index 00000000..62440bdf --- /dev/null +++ b/askar-crypto/src/encrypt/ecdh_es.rs @@ -0,0 +1,111 @@ +use sha2::Sha256; +use zeroize::Zeroize; + +use crate::{ + buffer::WriteBuffer, + caps::KeyGen, + encrypt::KeyExchange, + error::Error, + jwk::{JwkEncoder, JwkEncoderMode, ToJwk}, + kdf::concat::{ConcatKDFHash, ConcatKDFParams}, +}; + +fn ecdh_es_derive_shared( + ephem_key: &Key, + recip_key: &Key, + params: ConcatKDFParams, + key_output: &mut [u8], +) -> Result<(), Error> +where + Key: KeyExchange, +{ + let output_len = key_output.len(); + // one-pass KDF only produces 256 bits of output + assert!(output_len <= 32); + let mut kdf = ConcatKDFHash::::new(); + kdf.start_pass(); + + // hash Z directly into the KDF + ephem_key.key_exchange_buffer(recip_key, &mut kdf)?; + + let mut key = kdf.finish_pass(params, output_len); + key_output.copy_from_slice(&key[..output_len]); + key.zeroize(); + + Ok(()) +} + +pub fn ecdh_es_direct( + recip_key: &Key, + alg: &[u8], + apu: &[u8], + apv: &[u8], + key_output: &mut [u8], + jwk_output: &mut B, +) -> Result<(), Error> +where + Key: KeyGen + KeyExchange + ToJwk, +{ + let ephem_key = Key::generate()?; + let mut encoder = JwkEncoder::new(jwk_output, JwkEncoderMode::PublicKey)?; + ephem_key.to_jwk_buffer(&mut encoder)?; + + let params = ConcatKDFParams { alg, apu, apv }; + ecdh_es_derive_shared(&ephem_key, recip_key, params, key_output)?; + + // SECURITY: keys must zeroize themselves on drop + drop(ephem_key); + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + // based on RFC sample keys + // https://tools.ietf.org/html/rfc8037#appendix-A.6 + fn expected_es_direct_output() { + use crate::alg::x25519::X25519KeyPair; + use crate::jwk::{FromJwk, Jwk}; + + let bob_pk = X25519KeyPair::from_jwk(Jwk::from( + r#"{"kty":"OKP","crv":"X25519","kid":"Bob", + "x":"3p7bfXt9wbTTW2HC7OQ1Nz-DQ8hbeGdNrfx-FG-IK08"}"#, + )) + .unwrap(); + let ephem_sk = X25519KeyPair::from_jwk(Jwk::from( + r#"{"kty":"OKP","crv":"X25519", + "d":"dwdtCnMYpX08FsFyUbJmRd9ML4frwJkqsXf7pR25LCo", + "x":"hSDwCYkwp1R0i33ctD73Wg2_Og0mOBr066SpjqqbTmo"} + "#, + )) + .unwrap(); + + let xk = ephem_sk.key_exchange_bytes(&bob_pk).unwrap(); + assert_eq!( + xk, + &hex!("4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742")[..] + ); + + let mut key_output = [0u8; 32]; + + ecdh_es_derive_shared( + &ephem_sk, + &bob_pk, + ConcatKDFParams { + alg: b"A256GCM", + apu: b"Alice", + apv: b"Bob", + }, + &mut key_output, + ) + .unwrap(); + + assert_eq!( + key_output, + hex!("2f3636918ddb57fe0b3569113f19c4b6c518c2843f8930f05db25cd55dee53c1") + ); + } +} diff --git a/askar-crypto/src/encrypt/mod.rs b/askar-crypto/src/encrypt/mod.rs index 06ac6b6d..9a8e4065 100644 --- a/askar-crypto/src/encrypt/mod.rs +++ b/askar-crypto/src/encrypt/mod.rs @@ -4,7 +4,10 @@ use crate::{ }; mod ecdh_1pu; -pub use ecdh_1pu::ecdh_1pu_direct; +pub use self::ecdh_1pu::ecdh_1pu_direct; + +mod ecdh_es; +pub use self::ecdh_es::ecdh_es_direct; pub trait KeyAeadInPlace { /// Encrypt a secret value in place, appending the verification tag From 7e3aecee2aebb13fb892d38230daab1299f61dbb Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Mon, 12 Apr 2021 11:09:11 -0700 Subject: [PATCH 020/116] move ecdh-es, ecdh-1pu into kdf module Signed-off-by: Andrew Whitehead --- askar-crypto/src/alg/aesgcm.rs | 3 +- askar-crypto/src/alg/chacha20.rs | 3 +- askar-crypto/src/alg/k256.rs | 2 +- askar-crypto/src/alg/p256.rs | 2 +- askar-crypto/src/alg/x25519.rs | 2 +- askar-crypto/src/encrypt/mod.rs | 25 +--- askar-crypto/src/kdf/argon2.rs | 6 +- askar-crypto/src/{encrypt => kdf}/ecdh_1pu.rs | 111 +++++++++--------- askar-crypto/src/{encrypt => kdf}/ecdh_es.rs | 103 ++++++++-------- askar-crypto/src/kdf/mod.rs | 23 ++++ 10 files changed, 146 insertions(+), 134 deletions(-) rename askar-crypto/src/{encrypt => kdf}/ecdh_1pu.rs (53%) rename askar-crypto/src/{encrypt => kdf}/ecdh_es.rs (50%) diff --git a/askar-crypto/src/alg/aesgcm.rs b/askar-crypto/src/alg/aesgcm.rs index 37c8c320..2da32094 100644 --- a/askar-crypto/src/alg/aesgcm.rs +++ b/askar-crypto/src/alg/aesgcm.rs @@ -10,9 +10,10 @@ use crate::{ use crate::{ buffer::{ArrayKey, ResizeBuffer, WriteBuffer}, caps::{KeyGen, KeySecretBytes}, - encrypt::{FromKeyExchange, KeyAeadInPlace, KeyExchange}, + encrypt::KeyAeadInPlace, error::Error, jwk::{JwkEncoder, ToJwk}, + kdf::{FromKeyExchange, KeyExchange}, }; pub static JWK_KEY_TYPE: &'static str = "oct"; diff --git a/askar-crypto/src/alg/chacha20.rs b/askar-crypto/src/alg/chacha20.rs index 26a69410..aa7dc3b8 100644 --- a/askar-crypto/src/alg/chacha20.rs +++ b/askar-crypto/src/alg/chacha20.rs @@ -7,9 +7,10 @@ use crate::generic_array::{typenum::Unsigned, GenericArray}; use crate::{ buffer::{ArrayKey, ResizeBuffer, WriteBuffer, Writer}, caps::{KeyGen, KeySecretBytes}, - encrypt::{FromKeyExchange, KeyAeadInPlace, KeyExchange}, + encrypt::KeyAeadInPlace, error::Error, jwk::{JwkEncoder, ToJwk}, + kdf::{FromKeyExchange, KeyExchange}, random::fill_random_deterministic, }; diff --git a/askar-crypto/src/alg/k256.rs b/askar-crypto/src/alg/k256.rs index ae3171cc..2dc2ad9f 100644 --- a/askar-crypto/src/alg/k256.rs +++ b/askar-crypto/src/alg/k256.rs @@ -12,9 +12,9 @@ use k256::{ use crate::{ buffer::{ArrayKey, SecretBytes, WriteBuffer}, caps::{KeyCapSign, KeyCapVerify, KeyGen, KeySecretBytes, SignatureType}, - encrypt::KeyExchange, error::Error, jwk::{FromJwk, JwkEncoder, JwkParts, ToJwk}, + kdf::KeyExchange, random::with_rng, }; diff --git a/askar-crypto/src/alg/p256.rs b/askar-crypto/src/alg/p256.rs index 41eb59a5..67a2828c 100644 --- a/askar-crypto/src/alg/p256.rs +++ b/askar-crypto/src/alg/p256.rs @@ -12,9 +12,9 @@ use p256::{ use crate::{ buffer::{ArrayKey, SecretBytes, WriteBuffer}, caps::{KeyCapSign, KeyCapVerify, KeyGen, KeySecretBytes, SignatureType}, - encrypt::KeyExchange, error::Error, jwk::{FromJwk, JwkEncoder, JwkParts, ToJwk}, + kdf::KeyExchange, random::with_rng, }; diff --git a/askar-crypto/src/alg/x25519.rs b/askar-crypto/src/alg/x25519.rs index e9385ed2..475f8d9d 100644 --- a/askar-crypto/src/alg/x25519.rs +++ b/askar-crypto/src/alg/x25519.rs @@ -11,9 +11,9 @@ use zeroize::Zeroize; use crate::{ buffer::{ArrayKey, SecretBytes, WriteBuffer}, caps::{KeyGen, KeySecretBytes}, - encrypt::KeyExchange, error::Error, jwk::{FromJwk, JwkEncoder, JwkParts, ToJwk}, + kdf::KeyExchange, }; pub const PUBLIC_KEY_LENGTH: usize = 32; diff --git a/askar-crypto/src/encrypt/mod.rs b/askar-crypto/src/encrypt/mod.rs index 9a8e4065..93b79662 100644 --- a/askar-crypto/src/encrypt/mod.rs +++ b/askar-crypto/src/encrypt/mod.rs @@ -1,13 +1,4 @@ -use crate::{ - buffer::{ResizeBuffer, SecretBytes, WriteBuffer}, - error::Error, -}; - -mod ecdh_1pu; -pub use self::ecdh_1pu::ecdh_1pu_direct; - -mod ecdh_es; -pub use self::ecdh_es::ecdh_es_direct; +use crate::{buffer::ResizeBuffer, error::Error}; pub trait KeyAeadInPlace { /// Encrypt a secret value in place, appending the verification tag @@ -32,17 +23,3 @@ pub trait KeyAeadInPlace { /// Get the size of the verification tag fn tag_size() -> usize; } - -pub trait KeyExchange { - fn key_exchange_buffer(&self, other: &Rhs, out: &mut B) -> Result<(), Error>; - - fn key_exchange_bytes(&self, other: &Rhs) -> Result { - let mut buf = SecretBytes::with_capacity(128); - self.key_exchange_buffer(other, &mut buf)?; - Ok(buf) - } -} - -pub trait FromKeyExchange: Sized { - fn from_key_exchange(lhs: &Lhs, rhs: &Rhs) -> Result; -} diff --git a/askar-crypto/src/kdf/argon2.rs b/askar-crypto/src/kdf/argon2.rs index b32d1f16..855415d2 100644 --- a/askar-crypto/src/kdf/argon2.rs +++ b/askar-crypto/src/kdf/argon2.rs @@ -2,9 +2,6 @@ use crate::{buffer::SecretBytes, error::Error, random::random_secret}; pub use argon2::{Algorithm, Version}; -pub const LEVEL_INTERACTIVE: &'static str = "13:int"; -pub const LEVEL_MODERATE: &'static str = "13:mod"; - pub const HASH_SIZE: usize = 32; pub const SALT_SIZE: usize = 16; @@ -28,6 +25,9 @@ pub struct Params { time_cost: u32, } +// pub const LEVEL_INTERACTIVE: &'static str = "13:int"; +// pub const LEVEL_MODERATE: &'static str = "13:mod"; + // #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] // pub enum Level { // Interactive, diff --git a/askar-crypto/src/encrypt/ecdh_1pu.rs b/askar-crypto/src/kdf/ecdh_1pu.rs similarity index 53% rename from askar-crypto/src/encrypt/ecdh_1pu.rs rename to askar-crypto/src/kdf/ecdh_1pu.rs index 86486730..5b6e0e35 100644 --- a/askar-crypto/src/encrypt/ecdh_1pu.rs +++ b/askar-crypto/src/kdf/ecdh_1pu.rs @@ -1,65 +1,70 @@ +use core::marker::PhantomData; + use sha2::Sha256; use zeroize::Zeroize; +use super::{ + concat::{ConcatKDFHash, ConcatKDFParams}, + KeyExchange, +}; use crate::{ buffer::WriteBuffer, caps::KeyGen, - encrypt::KeyExchange, error::Error, jwk::{JwkEncoder, JwkEncoderMode, ToJwk}, - kdf::concat::{ConcatKDFHash, ConcatKDFParams}, }; -fn ecdh_1pu_derive_shared( - ephem_key: &Key, - send_key: &Key, - recip_key: &Key, - params: ConcatKDFParams, - key_output: &mut [u8], -) -> Result<(), Error> -where - Key: KeyExchange, -{ - let output_len = key_output.len(); - // one-pass KDF only produces 256 bits of output - assert!(output_len <= 32); - let mut kdf = ConcatKDFHash::::new(); - kdf.start_pass(); - - // hash Zs and Ze directly into the KDF - ephem_key.key_exchange_buffer(recip_key, &mut kdf)?; - send_key.key_exchange_buffer(recip_key, &mut kdf)?; - - let mut key = kdf.finish_pass(params, output_len); - key_output.copy_from_slice(&key[..output_len]); - key.zeroize(); - - Ok(()) -} +pub struct Ecdh1PU(PhantomData); + +impl Ecdh1PU { + fn derive_key_config( + ephem_key: &Key, + send_key: &Key, + recip_key: &Key, + params: ConcatKDFParams, + key_output: &mut [u8], + ) -> Result<(), Error> { + let output_len = key_output.len(); + // one-pass KDF only produces 256 bits of output + assert!(output_len <= 32); + let mut kdf = ConcatKDFHash::::new(); + kdf.start_pass(); + + // hash Zs and Ze directly into the KDF + ephem_key.key_exchange_buffer(recip_key, &mut kdf)?; + send_key.key_exchange_buffer(recip_key, &mut kdf)?; + + let mut key = kdf.finish_pass(params, output_len); + key_output.copy_from_slice(&key[..output_len]); + key.zeroize(); + + Ok(()) + } + + pub fn derive_key( + send_key: &Key, + recip_key: &Key, + alg: &[u8], + apu: &[u8], + apv: &[u8], + key_output: &mut [u8], + jwk_output: &mut B, + ) -> Result<(), Error> + where + Key: KeyGen + ToJwk, + { + let ephem_key = Key::generate()?; + let mut encoder = JwkEncoder::new(jwk_output, JwkEncoderMode::PublicKey)?; + ephem_key.to_jwk_buffer(&mut encoder)?; + + let params = ConcatKDFParams { alg, apu, apv }; + Self::derive_key_config(&ephem_key, send_key, recip_key, params, key_output)?; + + // SECURITY: keys must zeroize themselves on drop + drop(ephem_key); -pub fn ecdh_1pu_direct( - send_key: &Key, - recip_key: &Key, - alg: &[u8], - apu: &[u8], - apv: &[u8], - key_output: &mut [u8], - jwk_output: &mut B, -) -> Result<(), Error> -where - Key: KeyGen + KeyExchange + ToJwk, -{ - let ephem_key = Key::generate()?; - let mut encoder = JwkEncoder::new(jwk_output, JwkEncoderMode::PublicKey)?; - ephem_key.to_jwk_buffer(&mut encoder)?; - - let params = ConcatKDFParams { alg, apu, apv }; - ecdh_1pu_derive_shared(&ephem_key, send_key, recip_key, params, key_output)?; - - // SECURITY: keys must zeroize themselves on drop - drop(ephem_key); - - Ok(()) + Ok(()) + } } #[cfg(test)] @@ -99,10 +104,10 @@ mod tests { let mut key_output = [0u8; 32]; - ecdh_1pu_derive_shared( + Ecdh1PU::derive_key_config( + &ephem_sk, &alice_sk, &bob_sk, - &ephem_sk, ConcatKDFParams { alg: b"A256GCM", apu: b"Alice", diff --git a/askar-crypto/src/encrypt/ecdh_es.rs b/askar-crypto/src/kdf/ecdh_es.rs similarity index 50% rename from askar-crypto/src/encrypt/ecdh_es.rs rename to askar-crypto/src/kdf/ecdh_es.rs index 62440bdf..d8003a72 100644 --- a/askar-crypto/src/encrypt/ecdh_es.rs +++ b/askar-crypto/src/kdf/ecdh_es.rs @@ -1,62 +1,67 @@ +use core::marker::PhantomData; + use sha2::Sha256; use zeroize::Zeroize; +use super::{ + concat::{ConcatKDFHash, ConcatKDFParams}, + KeyExchange, +}; use crate::{ buffer::WriteBuffer, caps::KeyGen, - encrypt::KeyExchange, error::Error, jwk::{JwkEncoder, JwkEncoderMode, ToJwk}, - kdf::concat::{ConcatKDFHash, ConcatKDFParams}, }; -fn ecdh_es_derive_shared( - ephem_key: &Key, - recip_key: &Key, - params: ConcatKDFParams, - key_output: &mut [u8], -) -> Result<(), Error> -where - Key: KeyExchange, -{ - let output_len = key_output.len(); - // one-pass KDF only produces 256 bits of output - assert!(output_len <= 32); - let mut kdf = ConcatKDFHash::::new(); - kdf.start_pass(); - - // hash Z directly into the KDF - ephem_key.key_exchange_buffer(recip_key, &mut kdf)?; - - let mut key = kdf.finish_pass(params, output_len); - key_output.copy_from_slice(&key[..output_len]); - key.zeroize(); - - Ok(()) -} +pub struct EcdhEs(PhantomData); + +impl EcdhEs { + fn derive_key_config( + ephem_key: &Key, + recip_key: &Key, + params: ConcatKDFParams, + key_output: &mut [u8], + ) -> Result<(), Error> { + let output_len = key_output.len(); + // one-pass KDF only produces 256 bits of output + assert!(output_len <= 32); + let mut kdf = ConcatKDFHash::::new(); + kdf.start_pass(); + + // hash Z directly into the KDF + ephem_key.key_exchange_buffer(recip_key, &mut kdf)?; + + let mut key = kdf.finish_pass(params, output_len); + key_output.copy_from_slice(&key[..output_len]); + key.zeroize(); + + Ok(()) + } -pub fn ecdh_es_direct( - recip_key: &Key, - alg: &[u8], - apu: &[u8], - apv: &[u8], - key_output: &mut [u8], - jwk_output: &mut B, -) -> Result<(), Error> -where - Key: KeyGen + KeyExchange + ToJwk, -{ - let ephem_key = Key::generate()?; - let mut encoder = JwkEncoder::new(jwk_output, JwkEncoderMode::PublicKey)?; - ephem_key.to_jwk_buffer(&mut encoder)?; - - let params = ConcatKDFParams { alg, apu, apv }; - ecdh_es_derive_shared(&ephem_key, recip_key, params, key_output)?; - - // SECURITY: keys must zeroize themselves on drop - drop(ephem_key); - - Ok(()) + pub fn derive_key( + recip_key: &Key, + alg: &[u8], + apu: &[u8], + apv: &[u8], + key_output: &mut [u8], + jwk_output: &mut B, + ) -> Result<(), Error> + where + Key: KeyGen + ToJwk, + { + let ephem_key = Key::generate()?; + let mut encoder = JwkEncoder::new(jwk_output, JwkEncoderMode::PublicKey)?; + ephem_key.to_jwk_buffer(&mut encoder)?; + + let params = ConcatKDFParams { alg, apu, apv }; + Self::derive_key_config(&ephem_key, recip_key, params, key_output)?; + + // SECURITY: keys must zeroize themselves on drop + drop(ephem_key); + + Ok(()) + } } #[cfg(test)] @@ -91,7 +96,7 @@ mod tests { let mut key_output = [0u8; 32]; - ecdh_es_derive_shared( + EcdhEs::derive_key_config( &ephem_sk, &bob_pk, ConcatKDFParams { diff --git a/askar-crypto/src/kdf/mod.rs b/askar-crypto/src/kdf/mod.rs index 00c77c25..30d540c3 100644 --- a/askar-crypto/src/kdf/mod.rs +++ b/askar-crypto/src/kdf/mod.rs @@ -1,3 +1,26 @@ +use crate::{ + buffer::{SecretBytes, WriteBuffer}, + error::Error, +}; + pub mod argon2; pub mod concat; + +pub mod ecdh_1pu; + +pub mod ecdh_es; + +pub trait KeyExchange { + fn key_exchange_buffer(&self, other: &Rhs, out: &mut B) -> Result<(), Error>; + + fn key_exchange_bytes(&self, other: &Rhs) -> Result { + let mut buf = SecretBytes::with_capacity(128); + self.key_exchange_buffer(other, &mut buf)?; + Ok(buf) + } +} + +pub trait FromKeyExchange: Sized { + fn from_key_exchange(lhs: &Lhs, rhs: &Rhs) -> Result; +} From 514af3bf795587ebd54ab1ebd673845633a8e7e4 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Tue, 13 Apr 2021 13:06:35 -0700 Subject: [PATCH 021/116] update crypto_box support; clean up ResizeBuffer and WriteBuffer Signed-off-by: Andrew Whitehead --- askar-crypto/src/alg/aesgcm.rs | 2 +- askar-crypto/src/alg/chacha20.rs | 2 +- askar-crypto/src/alg/x25519.rs | 4 +- askar-crypto/src/{buffer.rs => buffer/mod.rs} | 348 +++--------------- askar-crypto/src/buffer/string.rs | 75 ++++ askar-crypto/src/buffer/writer.rs | 129 +++++++ askar-crypto/src/caps.rs | 2 +- askar-crypto/src/encrypt/mod.rs | 2 + askar-crypto/src/encrypt/nacl_box.rs | 162 ++++++++ askar-crypto/src/error.rs | 4 + askar-crypto/src/lib.rs | 2 +- askar-crypto/src/pack/nacl_box.rs | 139 ------- 12 files changed, 422 insertions(+), 449 deletions(-) rename askar-crypto/src/{buffer.rs => buffer/mod.rs} (51%) create mode 100644 askar-crypto/src/buffer/string.rs create mode 100644 askar-crypto/src/buffer/writer.rs create mode 100644 askar-crypto/src/encrypt/nacl_box.rs delete mode 100644 askar-crypto/src/pack/nacl_box.rs diff --git a/askar-crypto/src/alg/aesgcm.rs b/askar-crypto/src/alg/aesgcm.rs index 2da32094..964fab4a 100644 --- a/askar-crypto/src/alg/aesgcm.rs +++ b/askar-crypto/src/alg/aesgcm.rs @@ -128,7 +128,7 @@ impl KeyAeadInPlace for AesGcmKey { chacha .decrypt_in_place_detached(nonce, aad, &mut buffer.as_mut()[..tag_start], &tag) .map_err(|e| err_msg!(Encryption, "{}", e))?; - buffer.truncate(tag_start); + buffer.buffer_resize(tag_start)?; Ok(()) } diff --git a/askar-crypto/src/alg/chacha20.rs b/askar-crypto/src/alg/chacha20.rs index aa7dc3b8..fb00aeaa 100644 --- a/askar-crypto/src/alg/chacha20.rs +++ b/askar-crypto/src/alg/chacha20.rs @@ -147,7 +147,7 @@ impl KeyAeadInPlace for Chacha20Key { chacha .decrypt_in_place_detached(nonce, aad, &mut buffer.as_mut()[..tag_start], &tag) .map_err(|e| err_msg!(Encryption, "{}", e))?; - buffer.truncate(tag_start); + buffer.buffer_resize(tag_start)?; Ok(()) } diff --git a/askar-crypto/src/alg/x25519.rs b/askar-crypto/src/alg/x25519.rs index 475f8d9d..ba6d2f90 100644 --- a/askar-crypto/src/alg/x25519.rs +++ b/askar-crypto/src/alg/x25519.rs @@ -26,8 +26,8 @@ pub static JWK_CURVE: &'static str = "X25519"; #[derive(Clone)] pub struct X25519KeyPair { // SECURITY: SecretKey (StaticSecret) zeroizes on drop - secret: Option, - public: PublicKey, + pub(crate) secret: Option, + pub(crate) public: PublicKey, } impl X25519KeyPair { diff --git a/askar-crypto/src/buffer.rs b/askar-crypto/src/buffer/mod.rs similarity index 51% rename from askar-crypto/src/buffer.rs rename to askar-crypto/src/buffer/mod.rs index 222592b7..4c768fa9 100644 --- a/askar-crypto/src/buffer.rs +++ b/askar-crypto/src/buffer/mod.rs @@ -1,22 +1,24 @@ -use alloc::{ - borrow::Cow, - string::{String, ToString}, - vec::Vec, -}; +use alloc::{string::String, vec::Vec}; use core::{ cmp::Ordering, fmt::{self, Debug, Formatter}, marker::PhantomData, - mem::{self, ManuallyDrop}, + mem, ops::Deref, }; -use crate::generic_array::{typenum, ArrayLength, GenericArray}; +use crate::generic_array::{ArrayLength, GenericArray}; use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; use zeroize::Zeroize; use crate::{error::Error, random::fill_random}; +mod string; +pub(crate) use self::string::{HexRepr, MaybeStr}; + +mod writer; +pub use self::writer::Writer; + /// A secure key representation for fixed-length keys #[derive(Clone, Hash)] pub struct ArrayKey>(GenericArray); @@ -106,10 +108,7 @@ impl> Serialize for ArrayKey { where S: Serializer, { - // create an array twice the size of L on the stack (could be made clearer with const generics) - let mut hex_str = GenericArray::>::default(); - hex::encode_to_slice(&self.0.as_slice(), &mut hex_str).unwrap(); - serializer.serialize_str(core::str::from_utf8(&hex_str[..]).unwrap()) + serializer.collect_str(&HexRepr(&self.0)) } } @@ -155,100 +154,6 @@ impl<'a, L: ArrayLength> Visitor<'a> for KeyVisitor { } } -/// A possibly-empty password or key used to derive a store wrap key -#[derive(Clone)] -pub struct PassKey<'a>(Option>); - -impl PassKey<'_> { - /// Create a scoped reference to the passkey - pub fn as_ref(&self) -> PassKey<'_> { - PassKey(Some(Cow::Borrowed(&**self))) - } - - pub(crate) fn is_none(&self) -> bool { - self.0.is_none() - } - - pub(crate) fn into_owned(self) -> PassKey<'static> { - let mut slf = ManuallyDrop::new(self); - let val = slf.0.take(); - PassKey(match val { - None => None, - Some(Cow::Borrowed(s)) => Some(Cow::Owned(s.to_string())), - Some(Cow::Owned(s)) => Some(Cow::Owned(s)), - }) - } -} - -impl Debug for PassKey<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - if cfg!(test) { - f.debug_tuple("PassKey").field(&*self).finish() - } else { - f.debug_tuple("PassKey").field(&"").finish() - } - } -} - -impl Default for PassKey<'_> { - fn default() -> Self { - Self(None) - } -} - -impl Deref for PassKey<'_> { - type Target = str; - - fn deref(&self) -> &str { - match self.0.as_ref() { - None => "", - Some(s) => s.as_ref(), - } - } -} - -impl Drop for PassKey<'_> { - fn drop(&mut self) { - self.zeroize(); - } -} - -impl<'a> From<&'a str> for PassKey<'a> { - fn from(inner: &'a str) -> Self { - Self(Some(Cow::Borrowed(inner))) - } -} - -impl From for PassKey<'_> { - fn from(inner: String) -> Self { - Self(Some(Cow::Owned(inner))) - } -} - -impl<'a> From> for PassKey<'a> { - fn from(inner: Option<&'a str>) -> Self { - Self(inner.map(Cow::Borrowed)) - } -} - -impl<'a, 'b> PartialEq> for PassKey<'a> { - fn eq(&self, other: &PassKey<'b>) -> bool { - &**self == &**other - } -} -impl Eq for PassKey<'_> {} - -impl Zeroize for PassKey<'_> { - fn zeroize(&mut self) { - match self.0.take() { - Some(Cow::Owned(mut s)) => { - s.zeroize(); - } - _ => (), - } - } -} - /// A heap-allocated, zeroized byte buffer #[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Zeroize)] pub struct SecretBytes(Vec); @@ -256,9 +161,8 @@ pub struct SecretBytes(Vec); impl SecretBytes { pub fn new_with(len: usize, f: impl FnOnce(&mut [u8])) -> Self { let mut slf = Self::with_capacity(len); - let mut buf = slf.as_buffer(); - buf.resize(len); - f(buf.as_mut()); + slf.0.resize(len, 0u8); + f(slf.0.as_mut()); slf } @@ -266,10 +170,6 @@ impl SecretBytes { Self(Vec::with_capacity(max_len)) } - pub(crate) fn as_buffer(&mut self) -> SecretBytesMut<'_> { - SecretBytesMut(&mut self.0) - } - pub fn from_slice(data: &[u8]) -> Self { let mut v = Vec::with_capacity(data.len()); v.extend_from_slice(data); @@ -281,13 +181,17 @@ impl SecretBytes { core::str::from_utf8(self.0.as_slice()).ok() } - pub(crate) fn into_vec(mut self) -> Vec { + pub fn into_vec(mut self) -> Vec { // FIXME zeroize extra capacity? let mut v = Vec::new(); // note: no heap allocation for empty vec mem::swap(&mut v, &mut self.0); mem::forget(self); v } + + pub(crate) fn as_vec_mut(&mut self) -> &mut Vec { + &mut self.0 + } } impl Debug for SecretBytes { @@ -308,6 +212,12 @@ impl AsRef<[u8]> for SecretBytes { } } +impl AsMut<[u8]> for SecretBytes { + fn as_mut(&mut self) -> &mut [u8] { + self.0.as_mut_slice() + } +} + impl Deref for SecretBytes { type Target = [u8]; @@ -358,82 +268,6 @@ impl PartialEq> for SecretBytes { } } -pub(crate) struct SecretBytesMut<'m>(&'m mut Vec); - -impl SecretBytesMut<'_> { - /// Securely extend the buffer without leaving copies - #[inline] - pub fn extend_from_slice(&mut self, data: &[u8]) { - self.reserve(data.len()); - self.0.extend_from_slice(data); - } - - fn truncate(&mut self, len: usize) { - self.0.truncate(len); - } - - /// Obtain a large-enough SecretBytes without creating unsafe copies of - /// the contained data - pub fn reserve(&mut self, extra: usize) { - let len = self.0.len(); - if extra + len > self.0.capacity() { - // allocate a new buffer and copy the secure data over - let mut buf = Vec::with_capacity(extra + len); - buf.extend_from_slice(&self.0[..]); - mem::swap(&mut buf, &mut self.0); - buf.zeroize() - } - } - - pub fn resize(&mut self, new_len: usize) { - let len = self.0.len(); - if new_len > len { - self.reserve(new_len - len); - self.0.resize(new_len, 0u8); - } else { - self.0.truncate(new_len); - } - } -} - -impl AsRef<[u8]> for SecretBytesMut<'_> { - fn as_ref(&self) -> &[u8] { - self.0.as_slice() - } -} - -impl AsMut<[u8]> for SecretBytesMut<'_> { - fn as_mut(&mut self) -> &mut [u8] { - self.0.as_mut_slice() - } -} - -impl Deref for SecretBytesMut<'_> { - type Target = Vec; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -/// A utility type for debug printing of byte strings -struct MaybeStr<'a>(&'a [u8]); - -impl Debug for MaybeStr<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - if let Ok(sval) = core::str::from_utf8(self.0) { - write!(f, "{:?}", sval) - } else { - fmt::Write::write_char(f, '<')?; - for c in self.0 { - f.write_fmt(format_args!("{:02x}", c))?; - } - fmt::Write::write_char(f, '>')?; - Ok(()) - } - } -} - pub trait WriteBuffer { fn write_slice(&mut self, data: &[u8]) -> Result<(), Error> { let len = data.len(); @@ -451,78 +285,8 @@ pub trait WriteBuffer { ) -> Result; } -pub trait ResizeBuffer: WriteBuffer { - fn as_ref(&self) -> &[u8]; - - fn as_mut(&mut self) -> &mut [u8]; - - fn reserve(&mut self, len: usize) -> Result<(), Error> { - self.write_with(len, |buf| { - for idx in 0..len { - buf[idx] = 0u8; - } - Ok(len) - })?; - Ok(()) - } - - fn truncate(&mut self, len: usize); -} - -pub struct Writer { - inner: B, - pos: usize, -} - -impl<'b> Writer<&'b mut [u8]> { - #[inline] - pub fn from_slice(slice: &'b mut [u8]) -> Self { - Writer { - inner: slice, - pos: 0, - } - } - - #[inline] - pub fn from_slice_position(slice: &'b mut [u8], pos: usize) -> Self { - Writer { inner: slice, pos } - } - - pub fn position(&self) -> usize { - self.pos - } -} - -impl<'b> WriteBuffer for Writer<&'b mut [u8]> { - fn write_with( - &mut self, - max_len: usize, - f: impl FnOnce(&mut [u8]) -> Result, - ) -> Result { - let total = self.inner.len(); - let end = max_len + self.pos; - if end > total { - return Err(err_msg!("exceeded buffer size")); - } - let written = f(&mut self.inner[self.pos..end])?; - self.pos += written; - Ok(written) - } -} - -impl<'b> ResizeBuffer for Writer<&'b mut [u8]> { - fn as_ref(&self) -> &[u8] { - &self.inner[..self.pos] - } - - fn as_mut(&mut self) -> &mut [u8] { - &mut self.inner[..self.pos] - } - - fn truncate(&mut self, len: usize) { - assert!(len <= self.pos); - self.pos = len; - } +pub trait ResizeBuffer: WriteBuffer + AsRef<[u8]> + AsMut<[u8]> { + fn buffer_resize(&mut self, len: usize) -> Result<(), Error>; } impl WriteBuffer for Vec { @@ -542,16 +306,9 @@ impl WriteBuffer for Vec { } impl ResizeBuffer for Vec { - fn as_ref(&self) -> &[u8] { - &self[..] - } - - fn as_mut(&mut self) -> &mut [u8] { - &mut self[..] - } - - fn truncate(&mut self, len: usize) { - self.truncate(len); + fn buffer_resize(&mut self, len: usize) -> Result<(), Error> { + self.resize(len, 0u8); + Ok(()) } } @@ -561,9 +318,10 @@ impl WriteBuffer for SecretBytes { max_len: usize, f: impl FnOnce(&mut [u8]) -> Result, ) -> Result { - let len = self.len(); - self.0.resize(len + max_len, 0u8); - let written = f(&mut self.0[len..(len + max_len)])?; + let len = self.0.len(); + let new_len = len + max_len; + self.buffer_resize(new_len)?; + let written = f(&mut self.0[len..new_len])?; if written < max_len { self.0.truncate(len + written); } @@ -572,16 +330,18 @@ impl WriteBuffer for SecretBytes { } impl ResizeBuffer for SecretBytes { - fn as_ref(&self) -> &[u8] { - &self.0[..] - } - - fn as_mut(&mut self) -> &mut [u8] { - &mut self.0[..] - } - - fn truncate(&mut self, len: usize) { - self.0.truncate(len); + fn buffer_resize(&mut self, len: usize) -> Result<(), Error> { + let cap = self.0.capacity(); + if cap > 0 && len >= cap { + // allocate a new buffer and copy the secure data over + let new_cap = len.max(cap * 2).max(32); + let mut buf = SecretBytes::with_capacity(new_cap); + buf.0.extend_from_slice(&self.0[..]); + mem::swap(&mut buf, self); + // old buf zeroized on drop + } + self.0.resize(len, 0u8); + Ok(()) } } @@ -589,20 +349,6 @@ impl ResizeBuffer for SecretBytes { mod tests { use super::*; - #[test] - fn write_buffer_slice() { - let mut buf = [0u8; 10]; - let mut w = Writer::from_slice(&mut buf); - w.write_with(5, |buf| { - buf.copy_from_slice(b"hello"); - Ok(2) - }) - .unwrap(); - w.write_slice(b"y").unwrap(); - assert_eq!(w.position(), 3); - assert_eq!(&buf[..3], b"hey"); - } - #[test] fn write_buffer_vec() { let mut w = Vec::new(); @@ -626,10 +372,4 @@ mod tests { w.write_slice(b"y").unwrap(); assert_eq!(&w[..], b"hey"); } - - #[test] - fn test_maybe_str() { - assert_eq!(format!("{:?}", MaybeStr(&[])), "\"\""); - assert_eq!(format!("{:?}", MaybeStr(&[255, 0])), ""); - } } diff --git a/askar-crypto/src/buffer/string.rs b/askar-crypto/src/buffer/string.rs new file mode 100644 index 00000000..8ae93459 --- /dev/null +++ b/askar-crypto/src/buffer/string.rs @@ -0,0 +1,75 @@ +use core::fmt::{self, Debug, Display, Formatter, Write}; + +/// A utility type used to print or serialize a byte string as hex +#[derive(Debug)] +pub struct HexRepr<'r>(pub &'r [u8]); + +impl<'r> Display for HexRepr<'r> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + for c in self.0 { + f.write_fmt(format_args!("{:02x}", c))?; + } + Ok(()) + } +} + +impl PartialEq<[u8]> for HexRepr<'_> { + fn eq(&self, other: &[u8]) -> bool { + struct CmpWrite<'s>(::core::slice::Iter<'s, u8>); + + impl Write for CmpWrite<'_> { + fn write_str(&mut self, s: &str) -> fmt::Result { + for c in s.as_bytes() { + if self.0.next() != Some(c) { + return Err(fmt::Error); + } + } + Ok(()) + } + } + + write!(&mut CmpWrite(other.into_iter()), "{}", self).is_ok() + } +} + +impl PartialEq<&str> for HexRepr<'_> { + fn eq(&self, other: &&str) -> bool { + self == other.as_bytes() + } +} + +/// A utility type for debug printing of byte strings +pub struct MaybeStr<'a>(pub &'a [u8]); + +impl Debug for MaybeStr<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + if let Ok(sval) = core::str::from_utf8(self.0) { + write!(f, "{:?}", sval) + } else { + write!(f, "<{}>", HexRepr(self.0)) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn hex_repr_output() { + assert_eq!(format!("{}", HexRepr(&[])), ""); + assert_eq!(format!("{}", HexRepr(&[255, 0, 255, 0])), "ff00ff00"); + } + + #[test] + fn hex_repr_cmp() { + assert_eq!(HexRepr(&[0, 255, 0, 255]), "00ff00ff"); + assert_ne!(HexRepr(&[100, 101, 102]), "00ff00ff"); + } + + #[test] + fn maybe_str_output() { + assert_eq!(format!("{:?}", MaybeStr(&[])), "\"\""); + assert_eq!(format!("{:?}", MaybeStr(&[255, 0])), ""); + } +} diff --git a/askar-crypto/src/buffer/writer.rs b/askar-crypto/src/buffer/writer.rs new file mode 100644 index 00000000..0cb660c3 --- /dev/null +++ b/askar-crypto/src/buffer/writer.rs @@ -0,0 +1,129 @@ +use alloc::vec::Vec; + +use super::{ResizeBuffer, WriteBuffer}; +use crate::error::Error; + +pub struct Writer<'w, B: ?Sized> { + inner: &'w mut B, + pos: usize, +} + +impl Writer<'_, B> { + pub fn position(&self) -> usize { + self.pos + } +} + +impl<'w> Writer<'w, [u8]> { + #[inline] + pub fn from_slice(slice: &'w mut [u8]) -> Self { + Writer { + inner: slice, + pos: 0, + } + } + + #[inline] + pub fn from_slice_position(slice: &'w mut [u8], pos: usize) -> Self { + Writer { inner: slice, pos } + } +} + +impl AsRef<[u8]> for Writer<'_, [u8]> { + fn as_ref(&self) -> &[u8] { + &self.inner[..self.pos] + } +} + +impl AsMut<[u8]> for Writer<'_, [u8]> { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.inner[..self.pos] + } +} + +impl WriteBuffer for Writer<'_, [u8]> { + fn write_with( + &mut self, + max_len: usize, + f: impl FnOnce(&mut [u8]) -> Result, + ) -> Result { + let total = self.inner.len(); + let end = max_len + self.pos; + if end > total { + return Err(err_msg!("exceeded buffer size")); + } + let written = f(&mut self.inner[self.pos..end])?; + self.pos += written; + Ok(written) + } +} + +impl ResizeBuffer for Writer<'_, [u8]> { + fn buffer_resize(&mut self, len: usize) -> Result<(), Error> { + let len = self.pos + len; + if len > self.inner.len() { + return Err(err_msg!("exceeded allocated buffer")); + } + self.pos = len; + Ok(()) + } +} + +impl<'w> Writer<'w, Vec> { + #[inline] + pub fn from_vec(vec: &'w mut Vec) -> Self { + Writer { inner: vec, pos: 0 } + } + + #[inline] + pub fn from_vec_skip(vec: &'w mut Vec, pos: usize) -> Self { + Writer { inner: vec, pos } + } +} + +impl WriteBuffer for Writer<'_, B> { + fn write_with( + &mut self, + max_len: usize, + f: impl FnOnce(&mut [u8]) -> Result, + ) -> Result { + self.inner.write_with(max_len, f) + } +} + +impl AsRef<[u8]> for Writer<'_, B> { + fn as_ref(&self) -> &[u8] { + &self.inner.as_ref()[self.pos..] + } +} + +impl AsMut<[u8]> for Writer<'_, B> { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.inner.as_mut()[self.pos..] + } +} + +impl ResizeBuffer for Writer<'_, B> { + fn buffer_resize(&mut self, len: usize) -> Result<(), Error> { + self.inner.buffer_resize(self.pos + len) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn write_buffer_slice() { + let mut buf = [0u8; 10]; + let mut w = Writer::from_slice(&mut buf); + w.write_with(5, |buf| { + buf.copy_from_slice(b"hello"); + Ok(2) + }) + .unwrap(); + w.write_slice(b"y").unwrap(); + assert_eq!(w.position(), 3); + assert_eq!(&buf[..3], b"hey"); + } +} diff --git a/askar-crypto/src/caps.rs b/askar-crypto/src/caps.rs index 446e496f..e43c6c56 100644 --- a/askar-crypto/src/caps.rs +++ b/askar-crypto/src/caps.rs @@ -174,7 +174,7 @@ pub enum SignatureFormat { /// Base64-encoded binary signature Base64, /// Base64-URL-encoded binary signature - Base64_URL, + Base64Url, /// Hex-encoded binary signature Hex, /// Raw binary signature (method dependent) diff --git a/askar-crypto/src/encrypt/mod.rs b/askar-crypto/src/encrypt/mod.rs index 93b79662..04bd042a 100644 --- a/askar-crypto/src/encrypt/mod.rs +++ b/askar-crypto/src/encrypt/mod.rs @@ -1,5 +1,7 @@ use crate::{buffer::ResizeBuffer, error::Error}; +pub mod nacl_box; + pub trait KeyAeadInPlace { /// Encrypt a secret value in place, appending the verification tag fn encrypt_in_place( diff --git a/askar-crypto/src/encrypt/nacl_box.rs b/askar-crypto/src/encrypt/nacl_box.rs new file mode 100644 index 00000000..55d76323 --- /dev/null +++ b/askar-crypto/src/encrypt/nacl_box.rs @@ -0,0 +1,162 @@ +use crate::{ + buffer::Writer, + generic_array::{typenum::Unsigned, GenericArray}, +}; +use aead::AeadInPlace; +use blake2::{digest::Update, digest::VariableOutput, VarBlake2b}; +use crypto_box::{self as cbox, SalsaBox}; + +use crate::{ + alg::x25519::X25519KeyPair, + buffer::{ResizeBuffer, SecretBytes, WriteBuffer}, + caps::KeyGen, + error::Error, +}; + +const CBOX_NONCE_SIZE: usize = NonceSize::::USIZE; +const CBOX_KEY_SIZE: usize = crate::alg::x25519::PUBLIC_KEY_LENGTH; + +type NonceSize = ::NonceSize; + +type TagSize = ::TagSize; + +#[inline] +fn secret_key_from(kp: &X25519KeyPair) -> Result { + if let Some(sk) = kp.secret.as_ref() { + Ok(cbox::SecretKey::from(sk.to_bytes())) + } else { + Err(err_msg!(MissingSecretKey)) + } +} + +#[inline] +fn nonce_from(nonce: &[u8]) -> Result<&GenericArray>, Error> { + if nonce.len() == NonceSize::::USIZE { + Ok(GenericArray::from_slice(nonce)) + } else { + Err(err_msg!(InvalidNonce)) + } +} + +pub fn crypto_box_nonce( + ephemeral_pk: &[u8], + recip_pk: &[u8], +) -> Result<[u8; CBOX_NONCE_SIZE], Error> { + let mut key_hash = VarBlake2b::new(CBOX_NONCE_SIZE).unwrap(); + key_hash.update(ephemeral_pk); + key_hash.update(recip_pk); + let mut nonce = [0u8; CBOX_NONCE_SIZE]; + key_hash.finalize_variable(|hash| nonce.copy_from_slice(hash)); + Ok(nonce) +} + +pub fn crypto_box( + recip_pk: &X25519KeyPair, + sender_sk: &X25519KeyPair, + buffer: &mut B, + nonce: &[u8], +) -> Result<(), Error> { + let sender_sk = secret_key_from(sender_sk)?; + let nonce = nonce_from(nonce)?; + let box_inst = SalsaBox::new(&recip_pk.public, &sender_sk); + let tag = box_inst + .encrypt_in_place_detached(nonce, &[], buffer.as_mut()) + .map_err(|_| err_msg!(Encryption, "Error encrypting box"))?; + buffer.write_slice(&tag[..])?; + Ok(()) +} + +pub fn crypto_box_open( + recip_sk: &X25519KeyPair, + sender_pk: &X25519KeyPair, + buffer: &mut B, + nonce: &[u8], +) -> Result<(), Error> { + let recip_sk = secret_key_from(recip_sk)?; + let nonce = nonce_from(nonce)?; + let buf_len = buffer.as_ref().len(); + if buf_len < TagSize::::USIZE { + return Err(err_msg!("invalid size for encrypted data")); + } + let tag_start = buf_len - TagSize::::USIZE; + let mut tag = GenericArray::default(); + tag.clone_from_slice(&buffer.as_ref()[tag_start..]); + + let box_inst = SalsaBox::new(&sender_pk.public, &recip_sk); + + box_inst + .decrypt_in_place_detached(nonce, &[], &mut buffer.as_mut()[..tag_start], &tag) + .map_err(|_| err_msg!(Encryption, "Error decrypting box"))?; + buffer.buffer_resize(tag_start)?; + Ok(()) +} + +// Could add a non-alloc version, if needed +pub fn crypto_box_seal(recip_pk: &X25519KeyPair, message: &[u8]) -> Result { + let ephem_kp = X25519KeyPair::generate()?; + let ephem_pk_bytes = ephem_kp.public.as_bytes(); + let buf_len = CBOX_KEY_SIZE + message.len() + TagSize::::USIZE; + let mut buffer = SecretBytes::with_capacity(buf_len); + buffer.write_slice(ephem_pk_bytes)?; + buffer.write_slice(message)?; + let mut writer = Writer::from_vec_skip(buffer.as_vec_mut(), CBOX_KEY_SIZE); + let nonce = crypto_box_nonce(ephem_pk_bytes, recip_pk.public.as_bytes())?.to_vec(); + crypto_box(recip_pk, &ephem_kp, &mut writer, &nonce[..])?; + Ok(buffer) +} + +pub fn crypto_box_seal_open( + recip_sk: &X25519KeyPair, + ciphertext: &[u8], +) -> Result { + let ephem_pk = X25519KeyPair::from_public_key_bytes(&ciphertext[..CBOX_KEY_SIZE])?; + let mut buffer = SecretBytes::from_slice(&ciphertext[CBOX_KEY_SIZE..]); + let nonce = crypto_box_nonce(ephem_pk.public.as_bytes(), recip_sk.public.as_bytes())?; + crypto_box_open(recip_sk, &ephem_pk, &mut buffer, &nonce)?; + Ok(buffer) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::buffer::SecretBytes; + use crate::caps::KeySecretBytes; + + #[test] + fn crypto_box_round_trip_expected() { + let sk = X25519KeyPair::from_key_secret_bytes(&hex!( + "a8bdb9830f8790d242f66e04b11cc2a14c752a7b63c073f3c68e9adb151cc854" + )) + .unwrap(); + let pk = X25519KeyPair::from_public_key_bytes(&hex!( + "07d0b594683bdb6af5f4eacb1a392687d580a58db196a752dca316dedb7d251c" + )) + .unwrap(); + let message = b"hello there"; + let nonce = b"012345678912012345678912"; + let mut buffer = SecretBytes::from_slice(message); + crypto_box(&pk, &sk, &mut buffer, nonce).unwrap(); + assert_eq!( + buffer, + &hex!("1cc8721d567baa8f2b5583848dc97d373f7aa2223b57780c60f773")[..] + ); + + crypto_box_open(&sk, &pk, &mut buffer, nonce).unwrap(); + assert_eq!(buffer, &message[..]); + } + + #[test] + fn crypto_box_seal_round_trip() { + let recip = X25519KeyPair::generate().unwrap(); + + let recip_public = + X25519KeyPair::from_public_key_bytes(&recip.to_public_key_bytes()).unwrap(); + + let message = b"hello there"; + let sealed = crypto_box_seal(&recip_public, message).unwrap(); + assert_ne!(sealed, &message[..]); + + let open = crypto_box_seal_open(&recip, &sealed).unwrap(); + assert_eq!(open, &message[..]); + } +} diff --git a/askar-crypto/src/error.rs b/askar-crypto/src/error.rs index 6190870b..99c3068b 100644 --- a/askar-crypto/src/error.rs +++ b/askar-crypto/src/error.rs @@ -13,6 +13,9 @@ pub enum ErrorKind { /// The input parameters to the method were incorrect Input, + /// The provided nonce was invalid (bad length) + InvalidNonce, + /// A secret key is required but not present MissingSecretKey, @@ -29,6 +32,7 @@ impl ErrorKind { match self { Self::Encryption => "Encryption error", Self::Input => "Input error", + Self::InvalidNonce => "Invalid encryption nonce", Self::MissingSecretKey => "Missing secret key", Self::Unexpected => "Unexpected error", Self::Unsupported => "Unsupported", diff --git a/askar-crypto/src/lib.rs b/askar-crypto/src/lib.rs index 86032d4c..4e1f3ef7 100644 --- a/askar-crypto/src/lib.rs +++ b/askar-crypto/src/lib.rs @@ -31,7 +31,7 @@ pub use self::any::{AnyPrivateKey, AnyPublicKey}; pub mod alg; mod buffer; -pub use self::buffer::{PassKey, SecretBytes}; +pub use self::buffer::{ResizeBuffer, SecretBytes, WriteBuffer, Writer}; pub mod caps; pub use self::caps::{ diff --git a/askar-crypto/src/pack/nacl_box.rs b/askar-crypto/src/pack/nacl_box.rs deleted file mode 100644 index 70c24fdd..00000000 --- a/askar-crypto/src/pack/nacl_box.rs +++ /dev/null @@ -1,139 +0,0 @@ -use std::convert::TryInto; - -use blake2::{digest::Update, digest::VariableOutput, VarBlake2b}; -use crypto_box::{ - self as cbox, - aead::{generic_array::typenum::Unsigned, Aead}, -}; - -use crate::alg::x25519::X25519KeyPair; -use crate::error::Error; -use crate::random::random_vec; - -const CBOX_NONCE_SIZE: usize = ::NonceSize::USIZE; - -fn crypto_box_key(key: F) -> Result -where - F: AsRef<[u8]>, - T: From<[u8; cbox::KEY_SIZE]>, -{ - let key = key.as_ref(); - if key.len() != cbox::KEY_SIZE { - Err(err_msg!(Encryption, "Invalid crypto box key length")) - } else { - Ok(T::from(key.try_into().unwrap())) - } -} - -pub fn crypto_box_nonce( - ephemeral_pk: &[u8], - recip_pk: &[u8], -) -> Result<[u8; CBOX_NONCE_SIZE], Error> { - let mut key_hash = VarBlake2b::new(CBOX_NONCE_SIZE).unwrap(); - key_hash.update(ephemeral_pk); - key_hash.update(recip_pk); - let mut nonce = [0u8; CBOX_NONCE_SIZE]; - key_hash.finalize_variable(|hash| nonce.copy_from_slice(hash)); - Ok(nonce) -} - -pub fn crypto_box( - recip_pk: &[u8], - sender_sk: &[u8], - message: &[u8], - nonce: Option>, -) -> Result<(Vec, Vec), Error> { - let recip_pk: cbox::PublicKey = crypto_box_key(recip_pk)?; - let sender_sk: cbox::SecretKey = crypto_box_key(sender_sk)?; - let box_inst = cbox::SalsaBox::new(&recip_pk, &sender_sk); - - let nonce = if let Some(nonce) = nonce { - nonce.as_slice().into() - } else { - random_vec(CBOX_NONCE_SIZE) - }; - - let ciphertext = box_inst - .encrypt(nonce.as_slice().into(), message) - .map_err(|_| err_msg!(Encryption, "Error encrypting box"))?; - Ok((ciphertext, nonce)) -} - -pub fn crypto_box_open( - recip_sk: &[u8], - sender_pk: &[u8], - ciphertext: &[u8], - nonce: &[u8], -) -> Result, Error> { - let recip_sk: cbox::SecretKey = crypto_box_key(recip_sk)?; - let sender_pk: cbox::PublicKey = crypto_box_key(sender_pk)?; - let box_inst = cbox::SalsaBox::new(&sender_pk, &recip_sk); - - let plaintext = box_inst - .decrypt(nonce.into(), ciphertext) - .map_err(|_| err_msg!(Encryption, "Error decrypting box"))?; - Ok(plaintext) -} - -pub fn crypto_box_seal(recip_pk: &[u8], message: &[u8]) -> Result, Error> { - let ephem_kp = X25519KeyPair::generate()?; - let ephem_sk = ephem_kp.private_key(); - let ephem_sk_x: cbox::SecretKey = crypto_box_key(&ephem_sk)?; - debug_assert_eq!(ephem_sk_x.to_bytes(), ephem_sk.as_ref()); - let ephem_pk_x = ephem_sk_x.public_key(); - - let nonce = crypto_box_nonce(ephem_pk_x.as_bytes(), &recip_pk)?.to_vec(); - let (mut boxed, _) = crypto_box(recip_pk, ephem_sk.as_ref(), message, Some(nonce))?; - - let mut result = Vec::::with_capacity(cbox::KEY_SIZE); // FIXME - result.extend_from_slice(ephem_pk_x.as_bytes()); - result.append(&mut boxed); - Ok(result) -} - -pub fn crypto_box_seal_open( - recip_pk: &[u8], - recip_sk: &[u8], - ciphertext: &[u8], -) -> Result, Error> { - let ephem_pk = &ciphertext[..32]; - let boxed = &ciphertext[32..]; - - let nonce = crypto_box_nonce(&ephem_pk, &recip_pk)?; - let decode = crypto_box_open(recip_sk, ephem_pk, boxed, &nonce)?; - Ok(decode) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::alg::ed25519::Ed25519KeyPair; - - #[test] - fn crypto_box_open_expected() { - let sk = hex::decode("07d0b594683bdb6af5f4eacb1a392687d580a58db196a752dca316dedb7d251d") - .unwrap(); - let pk = hex::decode("07d0b594683bdb6af5f4eacb1a392687d580a58db196a752dca316dedb7d251c") - .unwrap(); - let message = b"hello there"; - // let nonce = b"012345678912012345678912".to_vec(); - let (boxed, nonce) = crypto_box(&pk, &sk, message, None).unwrap(); - - let open = crypto_box_open(&sk, &pk, &boxed, &nonce).unwrap(); - assert_eq!(open, message); - } - - #[test] - fn crypto_box_seal_expected() { - let kp = Ed25519KeyPair::from_seed(b"000000000000000000000000000Test0").unwrap(); - let kp_x = kp.to_x25519(); - let sk_x = kp_x.private_key(); - let pk_x = kp_x.public_key(); - - let message = b"hello there"; - let sealed = crypto_box_seal(&pk_x.to_bytes(), message).unwrap(); - - let open = crypto_box_seal_open(&pk_x.to_bytes(), sk_x.as_ref(), &sealed).unwrap(); - assert_eq!(open, message); - } -} From 456361952983be67a585216c8c7eb2b9c0e62e07 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Wed, 14 Apr 2021 17:09:35 -0700 Subject: [PATCH 022/116] key trait cleanups and improvements Signed-off-by: Andrew Whitehead --- askar-crypto/Cargo.toml | 1 - askar-crypto/src/alg/aesgcm.rs | 61 +++-- askar-crypto/src/alg/chacha20.rs | 61 +++-- askar-crypto/src/alg/ed25519.rs | 73 +++--- askar-crypto/src/alg/k256.rs | 70 ++++-- askar-crypto/src/alg/p256.rs | 70 ++++-- askar-crypto/src/alg/x25519.rs | 72 +++--- askar-crypto/src/buffer/mod.rs | 220 +++++------------ askar-crypto/src/buffer/secret.rs | 239 +++++++++++++++++++ askar-crypto/src/buffer/writer.rs | 46 +++- askar-crypto/src/caps.rs | 78 ------- askar-crypto/src/encrypt/mod.rs | 17 +- askar-crypto/src/encrypt/nacl_box.rs | 12 +- askar-crypto/src/error.rs | 11 + askar-crypto/src/kdf/argon2.rs | 59 +---- askar-crypto/src/kdf/ecdh_1pu.rs | 2 +- askar-crypto/src/kdf/ecdh_es.rs | 2 +- askar-crypto/src/lib.rs | 14 +- askar-crypto/src/repr.rs | 71 ++++++ askar-crypto/src/store.rs | 337 --------------------------- 20 files changed, 720 insertions(+), 796 deletions(-) create mode 100644 askar-crypto/src/buffer/secret.rs create mode 100644 askar-crypto/src/repr.rs delete mode 100644 askar-crypto/src/store.rs diff --git a/askar-crypto/Cargo.toml b/askar-crypto/Cargo.toml index 0c00a912..0592a778 100644 --- a/askar-crypto/Cargo.toml +++ b/askar-crypto/Cargo.toml @@ -38,7 +38,6 @@ digest = "0.9" group = "0.9" hex = { version = "0.4", default-features = false } hkdf = "0.10" -hmac = "0.10" k256 = { version = "0.7", default-features = false, features = ["arithmetic", "ecdsa", "ecdh", "sha256", "zeroize"] } p256 = { version = "0.7", default-features = false, features = ["arithmetic", "ecdsa", "ecdh", "zeroize"] } rand = { version = "0.7", default-features = false, features = ["getrandom"] } diff --git a/askar-crypto/src/alg/aesgcm.rs b/askar-crypto/src/alg/aesgcm.rs index 964fab4a..aa2979b2 100644 --- a/askar-crypto/src/alg/aesgcm.rs +++ b/askar-crypto/src/alg/aesgcm.rs @@ -1,3 +1,5 @@ +use core::fmt::{self, Debug, Formatter}; + use aead::{Aead, AeadInPlace, NewAead}; use aes_gcm::{Aes128Gcm, Aes256Gcm}; use zeroize::Zeroize; @@ -9,11 +11,11 @@ use crate::{ use crate::{ buffer::{ArrayKey, ResizeBuffer, WriteBuffer}, - caps::{KeyGen, KeySecretBytes}, - encrypt::KeyAeadInPlace, + encrypt::{KeyAeadInPlace, KeyAeadMeta}, error::Error, jwk::{JwkEncoder, ToJwk}, kdf::{FromKeyExchange, KeyExchange}, + repr::{KeyGen, KeyMeta, KeySecretBytes}, }; pub static JWK_KEY_TYPE: &'static str = "oct"; @@ -22,10 +24,6 @@ pub trait AesGcmType { type Aead: NewAead + Aead + AeadInPlace; const JWK_ALG: &'static str; - - fn key_size() -> usize { - ::KeySize::USIZE - } } pub struct A128; @@ -50,17 +48,40 @@ type NonceSize = <::Aead as Aead>::NonceSize; type TagSize = <::Aead as Aead>::TagSize; -#[derive(Clone, Debug, Zeroize)] +#[derive(PartialEq, Eq, Zeroize)] // SECURITY: ArrayKey is zeroized on drop pub struct AesGcmKey(KeyType); impl AesGcmKey { + pub const KEY_LENGTH: usize = KeyType::::SIZE; + pub const NONCE_LENGTH: usize = NonceSize::::USIZE; + pub const TAG_LENGTH: usize = TagSize::::USIZE; + #[inline] pub(crate) fn uninit() -> Self { Self(KeyType::::default()) } } +impl Clone for AesGcmKey { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl Debug for AesGcmKey { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("AesGcmKey") + .field("alg", &T::JWK_ALG) + .field("key", &self.0) + .finish() + } +} + +impl KeyMeta for AesGcmKey { + type SecretKeySize = ::KeySize; +} + impl KeyGen for AesGcmKey { fn generate() -> Result { Ok(AesGcmKey(KeyType::::random())) @@ -68,18 +89,23 @@ impl KeyGen for AesGcmKey { } impl KeySecretBytes for AesGcmKey { - fn from_key_secret_bytes(key: &[u8]) -> Result { + fn from_secret_bytes(key: &[u8]) -> Result { if key.len() != ::KeySize::USIZE { return Err(err_msg!("Invalid length for AES-GCM key")); } Ok(Self(KeyType::::from_slice(key))) } - fn to_key_secret_buffer(&self, out: &mut B) -> Result<(), Error> { - out.write_slice(self.0.as_ref()) + fn with_secret_bytes(&self, f: impl FnOnce(Option<&[u8]>) -> O) -> O { + f(Some(self.0.as_ref())) } } +impl KeyAeadMeta for AesGcmKey { + type NonceSize = NonceSize; + type TagSize = TagSize; +} + impl KeyAeadInPlace for AesGcmKey { /// Encrypt a secret value in place, appending the verification tag fn encrypt_in_place( @@ -89,10 +115,7 @@ impl KeyAeadInPlace for AesGcmKey { aad: &[u8], ) -> Result<(), Error> { if nonce.len() != NonceSize::::USIZE { - return Err(err_msg!( - "invalid size for nonce (expected {} bytes)", - NonceSize::::USIZE - )); + return Err(err_msg!(InvalidNonce)); } let nonce = GenericArray::from_slice(nonce); let chacha = T::Aead::new(self.0.as_ref()); @@ -132,13 +155,11 @@ impl KeyAeadInPlace for AesGcmKey { Ok(()) } - /// Get the required nonce size for encryption - fn nonce_size() -> usize { + fn nonce_length() -> usize { NonceSize::::USIZE } - /// Get the size of the verification tag - fn tag_size() -> usize { + fn tag_length() -> usize { TagSize::::USIZE } } @@ -168,7 +189,7 @@ where let mut key = Self::uninit(); let mut buf = Writer::from_slice(key.0.as_mut()); lhs.key_exchange_buffer(rhs, &mut buf)?; - if buf.position() != T::key_size() { + if buf.position() != Self::KEY_LENGTH { return Err(err_msg!("invalid length for key exchange output")); } Ok(key) @@ -189,7 +210,7 @@ mod tests { let mut nonce = GenericArray::>::default(); fill_random(&mut nonce); key.encrypt_in_place(&mut buffer, &nonce, &[]).unwrap(); - assert_eq!(buffer.len(), input.len() + AesGcmKey::::tag_size()); + assert_eq!(buffer.len(), input.len() + AesGcmKey::::TAG_LENGTH); assert_ne!(&buffer[..], input); key.decrypt_in_place(&mut buffer, &nonce, &[]).unwrap(); assert_eq!(&buffer[..], input); diff --git a/askar-crypto/src/alg/chacha20.rs b/askar-crypto/src/alg/chacha20.rs index fb00aeaa..bde93117 100644 --- a/askar-crypto/src/alg/chacha20.rs +++ b/askar-crypto/src/alg/chacha20.rs @@ -1,3 +1,5 @@ +use core::fmt::{self, Debug, Formatter}; + use aead::{Aead, AeadInPlace, NewAead}; use chacha20poly1305::{ChaCha20Poly1305, XChaCha20Poly1305}; use zeroize::Zeroize; @@ -6,12 +8,12 @@ use crate::generic_array::{typenum::Unsigned, GenericArray}; use crate::{ buffer::{ArrayKey, ResizeBuffer, WriteBuffer, Writer}, - caps::{KeyGen, KeySecretBytes}, - encrypt::KeyAeadInPlace, + encrypt::{KeyAeadInPlace, KeyAeadMeta}, error::Error, jwk::{JwkEncoder, ToJwk}, kdf::{FromKeyExchange, KeyExchange}, random::fill_random_deterministic, + repr::{KeyGen, KeyMeta, KeySecretBytes}, }; pub static JWK_KEY_TYPE: &'static str = "oct"; @@ -20,10 +22,6 @@ pub trait Chacha20Type { type Aead: NewAead + Aead + AeadInPlace; const JWK_ALG: &'static str; - - fn key_size() -> usize { - ::KeySize::USIZE - } } pub struct C20P; @@ -48,11 +46,15 @@ type NonceSize = <::Aead as Aead>::NonceSize; type TagSize = <::Aead as Aead>::TagSize; -#[derive(Clone, Debug, Zeroize)] +#[derive(PartialEq, Eq, Zeroize)] // SECURITY: ArrayKey is zeroized on drop pub struct Chacha20Key(KeyType); impl Chacha20Key { + pub const KEY_LENGTH: usize = KeyType::::SIZE; + pub const NONCE_LENGTH: usize = NonceSize::::USIZE; + pub const TAG_LENGTH: usize = TagSize::::USIZE; + #[inline] pub(crate) fn uninit() -> Self { Self(KeyType::::default()) @@ -67,6 +69,25 @@ impl Chacha20Key { } } +impl Clone for Chacha20Key { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl Debug for Chacha20Key { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("Chacha20Key") + .field("alg", &T::JWK_ALG) + .field("key", &self.0) + .finish() + } +} + +impl KeyMeta for Chacha20Key { + type SecretKeySize = ::KeySize; +} + impl KeyGen for Chacha20Key { fn generate() -> Result { Ok(Chacha20Key(KeyType::::random())) @@ -74,15 +95,15 @@ impl KeyGen for Chacha20Key { } impl KeySecretBytes for Chacha20Key { - fn from_key_secret_bytes(key: &[u8]) -> Result { + fn from_secret_bytes(key: &[u8]) -> Result { if key.len() != ::KeySize::USIZE { return Err(err_msg!("Invalid length for chacha20 key")); } Ok(Self(KeyType::::from_slice(key))) } - fn to_key_secret_buffer(&self, out: &mut B) -> Result<(), Error> { - out.write_slice(self.0.as_ref()) + fn with_secret_bytes(&self, f: impl FnOnce(Option<&[u8]>) -> O) -> O { + f(Some(self.0.as_ref())) } } @@ -99,6 +120,11 @@ impl KeySecretBytes for Chacha20Key { // } // } +impl KeyAeadMeta for Chacha20Key { + type NonceSize = NonceSize; + type TagSize = TagSize; +} + impl KeyAeadInPlace for Chacha20Key { /// Encrypt a secret value in place, appending the verification tag fn encrypt_in_place( @@ -130,10 +156,7 @@ impl KeyAeadInPlace for Chacha20Key { aad: &[u8], ) -> Result<(), Error> { if nonce.len() != NonceSize::::USIZE { - return Err(err_msg!( - "invalid size for nonce (expected {} bytes)", - NonceSize::::USIZE - )); + return Err(err_msg!(InvalidNonce)); } let nonce = GenericArray::from_slice(nonce); let buf_len = buffer.as_ref().len(); @@ -151,13 +174,11 @@ impl KeyAeadInPlace for Chacha20Key { Ok(()) } - /// Get the required nonce size for encryption - fn nonce_size() -> usize { + fn nonce_length() -> usize { NonceSize::::USIZE } - /// Get the size of the verification tag - fn tag_size() -> usize { + fn tag_length() -> usize { TagSize::::USIZE } } @@ -187,7 +208,7 @@ where let mut key = Self::uninit(); let mut buf = Writer::from_slice(key.0.as_mut()); lhs.key_exchange_buffer(rhs, &mut buf)?; - if buf.position() != T::key_size() { + if buf.position() != Self::KEY_LENGTH { return Err(err_msg!("invalid length for key exchange output")); } Ok(key) @@ -208,7 +229,7 @@ mod tests { let mut nonce = GenericArray::>::default(); fill_random(&mut nonce); key.encrypt_in_place(&mut buffer, &nonce, &[]).unwrap(); - assert_eq!(buffer.len(), input.len() + Chacha20Key::::tag_size()); + assert_eq!(buffer.len(), input.len() + Chacha20Key::::TAG_LENGTH); assert_ne!(&buffer[..], input); key.decrypt_in_place(&mut buffer, &nonce, &[]).unwrap(); assert_eq!(&buffer[..], input); diff --git a/askar-crypto/src/alg/ed25519.rs b/askar-crypto/src/alg/ed25519.rs index 7fe975fa..f6c389a6 100644 --- a/askar-crypto/src/alg/ed25519.rs +++ b/askar-crypto/src/alg/ed25519.rs @@ -12,10 +12,11 @@ use x25519_dalek::{PublicKey as XPublicKey, StaticSecret as XSecretKey}; use super::x25519::X25519KeyPair; use crate::{ buffer::{ArrayKey, SecretBytes, WriteBuffer}, - caps::{KeyCapSign, KeyCapVerify, KeyGen, KeySecretBytes, SignatureType}, + caps::{KeyCapSign, KeyCapVerify, SignatureType}, error::Error, - generic_array::typenum::U32, + generic_array::typenum::{U32, U64}, jwk::{FromJwk, JwkEncoder, JwkParts, ToJwk}, + repr::{KeyGen, KeyMeta, KeyPublicBytes, KeySecretBytes, KeypairMeta}, }; // FIXME - check for low-order points when loading public keys? @@ -53,16 +54,6 @@ impl Ed25519KeyPair { }) } - pub fn from_public_key_bytes(key: &[u8]) -> Result { - if key.len() != PUBLIC_KEY_LENGTH { - return Err(err_msg!("Invalid ed25519 public key length")); - } - Ok(Self { - secret: None, - public: PublicKey::from_bytes(key).map_err(|_| err_msg!("Invalid keypair bytes"))?, - }) - } - #[inline] pub(crate) fn from_secret_key(sk: SecretKey) -> Self { let public = PublicKey::from(&sk); @@ -84,10 +75,6 @@ impl Ed25519KeyPair { } } - pub fn to_public_key_bytes(&self) -> [u8; PUBLIC_KEY_LENGTH] { - self.public.to_bytes() - } - pub fn to_signing_key(&self) -> Option> { self.secret .as_ref() @@ -160,8 +147,17 @@ impl KeyGen for Ed25519KeyPair { } } +impl KeyMeta for Ed25519KeyPair { + type SecretKeySize = U32; +} + +impl KeypairMeta for Ed25519KeyPair { + type PublicKeySize = U32; + type KeypairSize = U64; +} + impl KeySecretBytes for Ed25519KeyPair { - fn from_key_secret_bytes(key: &[u8]) -> Result { + fn from_secret_bytes(key: &[u8]) -> Result { if key.len() != SECRET_KEY_LENGTH { return Err(err_msg!("Invalid ed25519 secret key length")); } @@ -169,12 +165,24 @@ impl KeySecretBytes for Ed25519KeyPair { Ok(Self::from_secret_key(sk)) } - fn to_key_secret_buffer(&self, out: &mut B) -> Result<(), Error> { - if let Some(sk) = self.secret.as_ref() { - out.write_slice(sk.as_bytes()) - } else { - Err(err_msg!(MissingSecretKey)) + fn with_secret_bytes(&self, f: impl FnOnce(Option<&[u8]>) -> O) -> O { + f(self.secret.as_ref().map(|sk| &sk.as_bytes()[..])) + } +} + +impl KeyPublicBytes for Ed25519KeyPair { + fn from_public_bytes(key: &[u8]) -> Result { + if key.len() != PUBLIC_KEY_LENGTH { + return Err(err_msg!("Invalid ed25519 public key length")); } + Ok(Self { + secret: None, + public: PublicKey::from_bytes(key).map_err(|_| err_msg!("Invalid keypair bytes"))?, + }) + } + + fn with_public_bytes(&self, f: impl FnOnce(&[u8]) -> O) -> O { + f(&self.public.to_bytes()[..]) } } @@ -220,11 +228,15 @@ impl ToJwk for Ed25519KeyPair { fn to_jwk_buffer(&self, buffer: &mut JwkEncoder) -> Result<(), Error> { buffer.add_str("kty", JWK_KEY_TYPE)?; buffer.add_str("crv", JWK_CURVE)?; - buffer.add_as_base64("x", &self.to_public_key_bytes()[..])?; + self.with_public_bytes(|buf| buffer.add_as_base64("x", buf))?; if buffer.is_secret() { - if let Some(sk) = self.secret.as_ref() { - buffer.add_as_base64("d", sk.as_bytes())?; - } + self.with_secret_bytes(|buf| { + if let Some(sk) = buf { + buffer.add_as_base64("d", sk) + } else { + Ok(()) + } + })?; } buffer.add_str("use", "sig")?; Ok(()) @@ -278,9 +290,9 @@ mod tests { let seed = b"000000000000000000000000Trustee1"; let test_sk = &hex!("3030303030303030303030303030303030303030303030305472757374656531e33aaf381fffa6109ad591fdc38717945f8fabf7abf02086ae401c63e9913097"); - let kp = Ed25519KeyPair::from_key_secret_bytes(seed).unwrap(); + let kp = Ed25519KeyPair::from_secret_bytes(seed).unwrap(); assert_eq!(kp.to_keypair_bytes().unwrap(), &test_sk[..]); - assert_eq!(kp.to_key_secret_bytes().unwrap(), &seed[..]); + assert_eq!(kp.to_secret_bytes().unwrap(), &seed[..]); // test round trip let cmp = Ed25519KeyPair::from_keypair_bytes(test_sk).unwrap(); @@ -315,8 +327,7 @@ mod tests { let test_pvt_b64 = "nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A"; let test_pub_b64 = "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"; let test_pvt = base64::decode_config(test_pvt_b64, base64::URL_SAFE).unwrap(); - let kp = - Ed25519KeyPair::from_key_secret_bytes(&test_pvt).expect("Error creating signing key"); + let kp = Ed25519KeyPair::from_secret_bytes(&test_pvt).expect("Error creating signing key"); let jwk = kp .to_jwk_public() .expect("Error converting public key to JWK"); @@ -325,7 +336,7 @@ mod tests { assert_eq!(jwk.crv, JWK_CURVE); assert_eq!(jwk.x, "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"); let pk_load = Ed25519KeyPair::from_jwk_parts(jwk).unwrap(); - assert_eq!(kp.to_public_key_bytes(), pk_load.to_public_key_bytes()); + assert_eq!(kp.to_public_bytes(), pk_load.to_public_bytes()); let jwk = kp .to_jwk_secret() diff --git a/askar-crypto/src/alg/k256.rs b/askar-crypto/src/alg/k256.rs index 2dc2ad9f..9f69b0ee 100644 --- a/askar-crypto/src/alg/k256.rs +++ b/askar-crypto/src/alg/k256.rs @@ -8,14 +8,17 @@ use k256::{ elliptic_curve::{ecdh::diffie_hellman, sec1::Coordinates, Curve}, EncodedPoint, PublicKey, SecretKey, }; +use zeroize::Zeroizing; use crate::{ buffer::{ArrayKey, SecretBytes, WriteBuffer}, - caps::{KeyCapSign, KeyCapVerify, KeyGen, KeySecretBytes, SignatureType}, + caps::{KeyCapSign, KeyCapVerify, SignatureType}, error::Error, + generic_array::typenum::{U32, U33, U65}, jwk::{FromJwk, JwkEncoder, JwkParts, ToJwk}, kdf::KeyExchange, random::with_rng, + repr::{KeyGen, KeyMeta, KeyPublicBytes, KeySecretBytes, KeypairMeta}, }; pub const ES256K_SIGNATURE_LENGTH: usize = 64; @@ -54,16 +57,6 @@ impl K256KeyPair { }) } - pub fn from_public_key_bytes(key: &[u8]) -> Result { - let pk = EncodedPoint::from_bytes(key) - .and_then(|pt| pt.decode()) - .map_err(|_| err_msg!("Invalid k-256 public key bytes"))?; - Ok(Self { - secret: None, - public: pk, - }) - } - #[inline] pub(crate) fn from_secret_key(sk: SecretKey) -> Self { let pk = sk.public_key(); @@ -86,10 +79,6 @@ impl K256KeyPair { } } - pub fn to_public_key_bytes(&self) -> SecretBytes { - SecretBytes::from(EncodedPoint::encode(self.public, true).as_ref().to_vec()) - } - pub(crate) fn to_signing_key(&self) -> Option { self.secret.as_ref().map(SigningKey::from) } @@ -114,6 +103,15 @@ impl K256KeyPair { } } +impl KeyMeta for K256KeyPair { + type SecretKeySize = U32; +} + +impl KeypairMeta for K256KeyPair { + type PublicKeySize = U33; + type KeypairSize = U65; +} + impl KeyGen for K256KeyPair { fn generate() -> Result { Ok(Self::from_secret_key(with_rng(|r| SecretKey::random(r)))) @@ -121,21 +119,39 @@ impl KeyGen for K256KeyPair { } impl KeySecretBytes for K256KeyPair { - fn from_key_secret_bytes(key: &[u8]) -> Result { + fn from_secret_bytes(key: &[u8]) -> Result { Ok(Self::from_secret_key( SecretKey::from_bytes(key).map_err(|_| err_msg!("Invalid k-256 secret key bytes"))?, )) } - fn to_key_secret_buffer(&self, out: &mut B) -> Result<(), Error> { + fn with_secret_bytes(&self, f: impl FnOnce(Option<&[u8]>) -> O) -> O { if let Some(sk) = self.secret.as_ref() { - out.write_slice(&sk.to_bytes()[..]) + let b = Zeroizing::new(<[u8; SECRET_KEY_LENGTH]>::from(sk.to_bytes())); + f(Some(&b[..])) } else { - Err(err_msg!(MissingSecretKey)) + f(None) } } } +impl KeyPublicBytes for K256KeyPair { + fn from_public_bytes(key: &[u8]) -> Result { + let pk = EncodedPoint::from_bytes(key) + .and_then(|pt| pt.decode()) + .map_err(|_| err_msg!("Invalid k-256 public key bytes"))?; + Ok(Self { + secret: None, + public: pk, + }) + } + + fn with_public_bytes(&self, f: impl FnOnce(&[u8]) -> O) -> O { + let pt = EncodedPoint::encode(self.public, true); + f(pt.as_ref()) + } +} + impl KeyCapSign for K256KeyPair { fn key_sign_buffer( &self, @@ -187,9 +203,13 @@ impl ToJwk for K256KeyPair { buffer.add_as_base64("x", &x[..])?; buffer.add_as_base64("y", &y[..])?; if buffer.is_secret() { - if let Some(sk) = self.secret.as_ref() { - buffer.add_as_base64("d", &sk.to_bytes()[..])?; - } + self.with_secret_bytes(|buf| { + if let Some(sk) = buf { + buffer.add_as_base64("d", sk) + } else { + Ok(()) + } + })?; } // buffer.add_str("use", "enc")?; Ok(()) @@ -263,7 +283,7 @@ mod tests { "36uMVGM7hnw-N6GnjFcihWE3SkrhMLzzLCdPMXPEXlA", ); let test_pvt = base64::decode_config(test_pvt_b64, base64::URL_SAFE).unwrap(); - let sk = K256KeyPair::from_key_secret_bytes(&test_pvt).expect("Error creating signing key"); + let sk = K256KeyPair::from_secret_bytes(&test_pvt).expect("Error creating signing key"); let jwk = sk.to_jwk_public().expect("Error converting key to JWK"); let jwk = jwk.to_parts().expect("Error parsing JWK"); @@ -273,7 +293,7 @@ mod tests { assert_eq!(jwk.y, test_pub_b64.1); assert_eq!(jwk.d, None); let pk_load = K256KeyPair::from_jwk_parts(jwk).unwrap(); - assert_eq!(sk.to_public_key_bytes(), pk_load.to_public_key_bytes()); + assert_eq!(sk.to_public_bytes(), pk_load.to_public_bytes()); let jwk = sk.to_jwk_secret().expect("Error converting key to JWK"); let jwk = jwk.to_parts().expect("Error parsing JWK"); @@ -301,7 +321,7 @@ mod tests { base64::URL_SAFE_NO_PAD, ) .unwrap(); - let kp = K256KeyPair::from_key_secret_bytes(&test_pvt).unwrap(); + let kp = K256KeyPair::from_secret_bytes(&test_pvt).unwrap(); let sig = kp.sign(&test_msg[..]).unwrap(); assert_eq!(sig, &test_sig[..]); assert_eq!(kp.verify_signature(&test_msg[..], &sig[..]), true); diff --git a/askar-crypto/src/alg/p256.rs b/askar-crypto/src/alg/p256.rs index 67a2828c..054704c1 100644 --- a/askar-crypto/src/alg/p256.rs +++ b/askar-crypto/src/alg/p256.rs @@ -8,14 +8,17 @@ use p256::{ elliptic_curve::{ecdh::diffie_hellman, sec1::Coordinates, Curve}, EncodedPoint, PublicKey, SecretKey, }; +use zeroize::Zeroizing; use crate::{ buffer::{ArrayKey, SecretBytes, WriteBuffer}, - caps::{KeyCapSign, KeyCapVerify, KeyGen, KeySecretBytes, SignatureType}, + caps::{KeyCapSign, KeyCapVerify, SignatureType}, error::Error, + generic_array::typenum::{U32, U33, U65}, jwk::{FromJwk, JwkEncoder, JwkParts, ToJwk}, kdf::KeyExchange, random::with_rng, + repr::{KeyGen, KeyMeta, KeyPublicBytes, KeySecretBytes, KeypairMeta}, }; pub const ES256_SIGNATURE_LENGTH: usize = 64; @@ -54,16 +57,6 @@ impl P256KeyPair { }) } - pub fn from_public_key_bytes(key: &[u8]) -> Result { - let pk = EncodedPoint::from_bytes(key) - .and_then(|pt| pt.decode()) - .map_err(|_| err_msg!("Invalid p-256 public key bytes"))?; - Ok(Self { - secret: None, - public: pk, - }) - } - #[inline] pub(crate) fn from_secret_key(sk: SecretKey) -> Self { let pk = sk.public_key(); @@ -86,10 +79,6 @@ impl P256KeyPair { } } - pub fn to_public_key_bytes(&self) -> SecretBytes { - SecretBytes::from(EncodedPoint::encode(self.public, true).as_ref().to_vec()) - } - pub(crate) fn to_signing_key(&self) -> Option { self.secret.clone().map(SigningKey::from) } @@ -114,6 +103,15 @@ impl P256KeyPair { } } +impl KeyMeta for P256KeyPair { + type SecretKeySize = U32; +} + +impl KeypairMeta for P256KeyPair { + type PublicKeySize = U33; + type KeypairSize = U65; +} + impl KeyGen for P256KeyPair { fn generate() -> Result { Ok(Self::from_secret_key(with_rng(|r| SecretKey::random(r)))) @@ -121,21 +119,39 @@ impl KeyGen for P256KeyPair { } impl KeySecretBytes for P256KeyPair { - fn from_key_secret_bytes(key: &[u8]) -> Result { + fn from_secret_bytes(key: &[u8]) -> Result { Ok(Self::from_secret_key( SecretKey::from_bytes(key).map_err(|_| err_msg!("Invalid p-256 secret key bytes"))?, )) } - fn to_key_secret_buffer(&self, out: &mut B) -> Result<(), Error> { + fn with_secret_bytes(&self, f: impl FnOnce(Option<&[u8]>) -> O) -> O { if let Some(sk) = self.secret.as_ref() { - out.write_slice(&sk.to_bytes()[..]) + let b = Zeroizing::new(<[u8; SECRET_KEY_LENGTH]>::from(sk.to_bytes())); + f(Some(&b[..])) } else { - Err(err_msg!(MissingSecretKey)) + f(None) } } } +impl KeyPublicBytes for P256KeyPair { + fn from_public_bytes(key: &[u8]) -> Result { + let pk = EncodedPoint::from_bytes(key) + .and_then(|pt| pt.decode()) + .map_err(|_| err_msg!("Invalid p-256 public key bytes"))?; + Ok(Self { + secret: None, + public: pk, + }) + } + + fn with_public_bytes(&self, f: impl FnOnce(&[u8]) -> O) -> O { + let pt = EncodedPoint::encode(self.public, true); + f(pt.as_ref()) + } +} + impl KeyCapSign for P256KeyPair { fn key_sign_buffer( &self, @@ -187,9 +203,13 @@ impl ToJwk for P256KeyPair { buffer.add_as_base64("x", &x[..])?; buffer.add_as_base64("y", &y[..])?; if buffer.is_secret() { - if let Some(sk) = self.secret.as_ref() { - buffer.add_as_base64("d", &sk.to_bytes()[..])?; - } + self.with_secret_bytes(|buf| { + if let Some(sk) = buf { + buffer.add_as_base64("d", sk) + } else { + Ok(()) + } + })?; } // buffer.add_str("use", "enc")?; Ok(()) @@ -261,7 +281,7 @@ mod tests { "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0", ); let test_pvt = base64::decode_config(test_pvt_b64, base64::URL_SAFE).unwrap(); - let sk = P256KeyPair::from_key_secret_bytes(&test_pvt).expect("Error creating signing key"); + let sk = P256KeyPair::from_secret_bytes(&test_pvt).expect("Error creating signing key"); let jwk = sk.to_jwk_public().expect("Error converting key to JWK"); let jwk = jwk.to_parts().expect("Error parsing JWK"); @@ -271,7 +291,7 @@ mod tests { assert_eq!(jwk.y, test_pub_b64.1); assert_eq!(jwk.d, None); let pk_load = P256KeyPair::from_jwk_parts(jwk).unwrap(); - assert_eq!(sk.to_public_key_bytes(), pk_load.to_public_key_bytes()); + assert_eq!(sk.to_public_bytes(), pk_load.to_public_bytes()); let jwk = sk.to_jwk_secret().expect("Error converting key to JWK"); let jwk = jwk.to_parts().expect("Error parsing JWK"); @@ -299,7 +319,7 @@ mod tests { base64::URL_SAFE_NO_PAD, ) .unwrap(); - let kp = P256KeyPair::from_key_secret_bytes(&test_pvt).unwrap(); + let kp = P256KeyPair::from_secret_bytes(&test_pvt).unwrap(); let sig = kp.sign(&test_msg[..]).unwrap(); assert_eq!(sig, &test_sig[..]); assert_eq!(kp.verify_signature(&test_msg[..], &sig[..]), true); diff --git a/askar-crypto/src/alg/x25519.rs b/askar-crypto/src/alg/x25519.rs index ba6d2f90..6d798fd7 100644 --- a/askar-crypto/src/alg/x25519.rs +++ b/askar-crypto/src/alg/x25519.rs @@ -3,17 +3,17 @@ use core::{ fmt::{self, Debug, Formatter}, }; -use crate::generic_array::typenum::U32; use rand::rngs::OsRng; use x25519_dalek::{PublicKey, StaticSecret as SecretKey}; -use zeroize::Zeroize; +use zeroize::Zeroizing; use crate::{ buffer::{ArrayKey, SecretBytes, WriteBuffer}, - caps::{KeyGen, KeySecretBytes}, error::Error, + generic_array::typenum::{U32, U64}, jwk::{FromJwk, JwkEncoder, JwkParts, ToJwk}, kdf::KeyExchange, + repr::{KeyGen, KeyMeta, KeyPublicBytes, KeySecretBytes, KeypairMeta}, }; pub const PUBLIC_KEY_LENGTH: usize = 32; @@ -66,20 +66,6 @@ impl X25519KeyPair { } } - pub fn from_public_key_bytes(key: &[u8]) -> Result { - if key.len() != PUBLIC_KEY_LENGTH { - return Err(err_msg!("Invalid x25519 key length")); - } - Ok(Self::new( - None, - PublicKey::from(TryInto::<[u8; PUBLIC_KEY_LENGTH]>::try_into(key).unwrap()), - )) - } - - pub fn to_public_key_bytes(&self) -> [u8; PUBLIC_KEY_LENGTH] { - self.public.to_bytes() - } - pub fn to_keypair_bytes(&self) -> Option { if let Some(secret) = self.secret.as_ref() { let output = SecretBytes::new_with(KEYPAIR_LENGTH, |buf| { @@ -109,6 +95,15 @@ impl Debug for X25519KeyPair { } } +impl KeyMeta for X25519KeyPair { + type SecretKeySize = U32; +} + +impl KeypairMeta for X25519KeyPair { + type PublicKeySize = U32; + type KeypairSize = U64; +} + impl KeyGen for X25519KeyPair { fn generate() -> Result { let sk = SecretKey::new(OsRng); @@ -118,7 +113,7 @@ impl KeyGen for X25519KeyPair { } impl KeySecretBytes for X25519KeyPair { - fn from_key_secret_bytes(key: &[u8]) -> Result { + fn from_secret_bytes(key: &[u8]) -> Result { if key.len() != SECRET_KEY_LENGTH { return Err(err_msg!("Invalid x25519 key length")); } @@ -140,26 +135,45 @@ impl KeySecretBytes for X25519KeyPair { Ok(Self::from_secret_key(sk)) } - fn to_key_secret_buffer(&self, out: &mut B) -> Result<(), Error> { + fn with_secret_bytes(&self, f: impl FnOnce(Option<&[u8]>) -> O) -> O { if let Some(sk) = self.secret.as_ref() { - out.write_slice(&sk.to_bytes()[..]) + let b = Zeroizing::new(sk.to_bytes()); + f(Some(&b[..])) } else { - Err(err_msg!(MissingSecretKey)) + f(None) } } } +impl KeyPublicBytes for X25519KeyPair { + fn from_public_bytes(key: &[u8]) -> Result { + if key.len() != PUBLIC_KEY_LENGTH { + return Err(err_msg!("Invalid x25519 public key length")); + } + Ok(Self::new( + None, + PublicKey::from(TryInto::<[u8; PUBLIC_KEY_LENGTH]>::try_into(key).unwrap()), + )) + } + + fn with_public_bytes(&self, f: impl FnOnce(&[u8]) -> O) -> O { + f(&self.public.to_bytes()[..]) + } +} + impl ToJwk for X25519KeyPair { fn to_jwk_buffer(&self, buffer: &mut JwkEncoder) -> Result<(), Error> { buffer.add_str("kty", JWK_KEY_TYPE)?; buffer.add_str("crv", JWK_CURVE)?; - buffer.add_as_base64("x", &self.to_public_key_bytes()[..])?; + self.with_public_bytes(|buf| buffer.add_as_base64("x", buf))?; if buffer.is_secret() { - if let Some(sk) = self.secret.as_ref() { - let mut sk = sk.to_bytes(); - buffer.add_as_base64("d", &sk[..])?; - sk.zeroize(); - } + self.with_secret_bytes(|buf| { + if let Some(sk) = buf { + buffer.add_as_base64("d", sk) + } else { + Ok(()) + } + })?; } buffer.add_str("use", "enc")?; Ok(()) @@ -223,7 +237,7 @@ mod tests { let test_pvt_b64 = "qL25gw-HkNJC9m4EsRzCoUx1KntjwHPzxo6a2xUcyFQ"; let test_pvt = base64::decode_config(test_pvt_b64, base64::URL_SAFE).unwrap(); let kp = - X25519KeyPair::from_key_secret_bytes(&test_pvt).expect("Error creating x25519 keypair"); + X25519KeyPair::from_secret_bytes(&test_pvt).expect("Error creating x25519 keypair"); let jwk = kp .to_jwk_public() .expect("Error converting public key to JWK"); @@ -233,7 +247,7 @@ mod tests { assert_eq!(jwk.x, "tGskN_ae61DP4DLY31_fjkbvnKqf-ze7kA6Cj2vyQxU"); assert_eq!(jwk.d, None); let pk_load = X25519KeyPair::from_jwk_parts(jwk).unwrap(); - assert_eq!(kp.to_public_key_bytes(), pk_load.to_public_key_bytes()); + assert_eq!(kp.to_public_bytes(), pk_load.to_public_bytes()); let jwk = kp .to_jwk_secret() diff --git a/askar-crypto/src/buffer/mod.rs b/askar-crypto/src/buffer/mod.rs index 4c768fa9..18b17dc5 100644 --- a/askar-crypto/src/buffer/mod.rs +++ b/askar-crypto/src/buffer/mod.rs @@ -1,10 +1,10 @@ -use alloc::{string::String, vec::Vec}; +use alloc::vec::Vec; use core::{ cmp::Ordering, fmt::{self, Debug, Formatter}, + iter, marker::PhantomData, - mem, - ops::Deref, + ops::Range, }; use crate::generic_array::{ArrayLength, GenericArray}; @@ -13,8 +13,11 @@ use zeroize::Zeroize; use crate::{error::Error, random::fill_random}; +mod secret; +pub use self::secret::SecretBytes; + mod string; -pub(crate) use self::string::{HexRepr, MaybeStr}; +pub use self::string::HexRepr; mod writer; pub use self::writer::Writer; @@ -154,120 +157,6 @@ impl<'a, L: ArrayLength> Visitor<'a> for KeyVisitor { } } -/// A heap-allocated, zeroized byte buffer -#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Zeroize)] -pub struct SecretBytes(Vec); - -impl SecretBytes { - pub fn new_with(len: usize, f: impl FnOnce(&mut [u8])) -> Self { - let mut slf = Self::with_capacity(len); - slf.0.resize(len, 0u8); - f(slf.0.as_mut()); - slf - } - - pub fn with_capacity(max_len: usize) -> Self { - Self(Vec::with_capacity(max_len)) - } - - pub fn from_slice(data: &[u8]) -> Self { - let mut v = Vec::with_capacity(data.len()); - v.extend_from_slice(data); - Self(v) - } - - /// Try to convert the buffer value to a string reference - pub fn as_opt_str(&self) -> Option<&str> { - core::str::from_utf8(self.0.as_slice()).ok() - } - - pub fn into_vec(mut self) -> Vec { - // FIXME zeroize extra capacity? - let mut v = Vec::new(); // note: no heap allocation for empty vec - mem::swap(&mut v, &mut self.0); - mem::forget(self); - v - } - - pub(crate) fn as_vec_mut(&mut self) -> &mut Vec { - &mut self.0 - } -} - -impl Debug for SecretBytes { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - if cfg!(test) { - f.debug_tuple("Secret") - .field(&MaybeStr(self.0.as_slice())) - .finish() - } else { - f.write_str("") - } - } -} - -impl AsRef<[u8]> for SecretBytes { - fn as_ref(&self) -> &[u8] { - self.0.as_slice() - } -} - -impl AsMut<[u8]> for SecretBytes { - fn as_mut(&mut self) -> &mut [u8] { - self.0.as_mut_slice() - } -} - -impl Deref for SecretBytes { - type Target = [u8]; - - fn deref(&self) -> &Self::Target { - self.0.as_slice() - } -} - -impl Drop for SecretBytes { - fn drop(&mut self) { - self.zeroize(); - } -} - -impl From<&[u8]> for SecretBytes { - fn from(inner: &[u8]) -> Self { - Self(inner.to_vec()) - } -} - -impl From<&str> for SecretBytes { - fn from(inner: &str) -> Self { - Self(inner.as_bytes().to_vec()) - } -} - -impl From for SecretBytes { - fn from(inner: String) -> Self { - Self(inner.into_bytes()) - } -} - -impl From> for SecretBytes { - fn from(inner: Vec) -> Self { - Self(inner) - } -} - -impl PartialEq<&[u8]> for SecretBytes { - fn eq(&self, other: &&[u8]) -> bool { - self.0.eq(other) - } -} - -impl PartialEq> for SecretBytes { - fn eq(&self, other: &Vec) -> bool { - self.0.eq(other) - } -} - pub trait WriteBuffer { fn write_slice(&mut self, data: &[u8]) -> Result<(), Error> { let len = data.len(); @@ -286,7 +175,25 @@ pub trait WriteBuffer { } pub trait ResizeBuffer: WriteBuffer + AsRef<[u8]> + AsMut<[u8]> { + fn buffer_insert_slice(&mut self, pos: usize, data: &[u8]) -> Result<(), Error> { + self.buffer_splice_with(pos..pos, data.len(), |ext| { + ext.copy_from_slice(data); + Ok(()) + }) + } + + fn buffer_remove(&mut self, range: Range) -> Result<(), Error> { + self.buffer_splice_with(range, 0, |_| Ok(())) + } + fn buffer_resize(&mut self, len: usize) -> Result<(), Error>; + + fn buffer_splice_with( + &mut self, + range: Range, + len: usize, + f: impl FnOnce(&mut [u8]) -> Result<(), Error>, + ) -> Result<(), Error>; } impl WriteBuffer for Vec { @@ -310,37 +217,16 @@ impl ResizeBuffer for Vec { self.resize(len, 0u8); Ok(()) } -} -impl WriteBuffer for SecretBytes { - fn write_with( + fn buffer_splice_with( &mut self, - max_len: usize, - f: impl FnOnce(&mut [u8]) -> Result, - ) -> Result { - let len = self.0.len(); - let new_len = len + max_len; - self.buffer_resize(new_len)?; - let written = f(&mut self.0[len..new_len])?; - if written < max_len { - self.0.truncate(len + written); - } - Ok(written) - } -} - -impl ResizeBuffer for SecretBytes { - fn buffer_resize(&mut self, len: usize) -> Result<(), Error> { - let cap = self.0.capacity(); - if cap > 0 && len >= cap { - // allocate a new buffer and copy the secure data over - let new_cap = len.max(cap * 2).max(32); - let mut buf = SecretBytes::with_capacity(new_cap); - buf.0.extend_from_slice(&self.0[..]); - mem::swap(&mut buf, self); - // old buf zeroized on drop - } - self.0.resize(len, 0u8); + range: Range, + len: usize, + f: impl FnOnce(&mut [u8]) -> Result<(), Error>, + ) -> Result<(), Error> { + let start = range.start; + self.splice(range, iter::repeat(0u8).take(len)); + f(&mut self[start..(start + len)])?; Ok(()) } } @@ -349,27 +235,43 @@ impl ResizeBuffer for SecretBytes { mod tests { use super::*; - #[test] - fn write_buffer_vec() { - let mut w = Vec::new(); + pub(crate) fn test_write_buffer>(mut w: B) { w.write_with(5, |buf| { buf.copy_from_slice(b"hello"); Ok(2) }) .unwrap(); w.write_slice(b"y").unwrap(); - assert_eq!(&w[..], b"hey"); + assert_eq!(&w.as_ref()[..], b"hey"); } - #[test] - fn write_buffer_secret() { - let mut w = SecretBytes::with_capacity(10); - w.write_with(5, |buf| { - buf.copy_from_slice(b"hello"); - Ok(2) + pub(crate) fn test_resize_buffer(mut w: B) { + w.write_slice(b"hello").unwrap(); + w.buffer_splice_with(1..3, 5, |ext| { + ext.copy_from_slice(b"sugar"); + Ok(()) }) .unwrap(); - w.write_slice(b"y").unwrap(); - assert_eq!(&w[..], b"hey"); + assert_eq!(&w.as_ref()[..], b"hsugarlo"); + w.buffer_splice_with(1..6, 2, |ext| { + ext.copy_from_slice(b"el"); + Ok(()) + }) + .unwrap(); + assert_eq!(&w.as_ref()[..], b"hello"); + w.buffer_resize(7).unwrap(); + assert_eq!(&w.as_ref()[..], b"hello\0\0"); + w.buffer_resize(5).unwrap(); + assert_eq!(&w.as_ref()[..], b"hello"); + } + + #[test] + fn write_buffer_vec() { + test_write_buffer(Vec::new()); + } + + #[test] + fn resize_buffer_vec() { + test_resize_buffer(Vec::new()); } } diff --git a/askar-crypto/src/buffer/secret.rs b/askar-crypto/src/buffer/secret.rs new file mode 100644 index 00000000..2f02046a --- /dev/null +++ b/askar-crypto/src/buffer/secret.rs @@ -0,0 +1,239 @@ +use alloc::{string::String, vec::Vec}; +use core::{ + fmt::{self, Debug, Formatter}, + iter, mem, + ops::{Deref, Range}, +}; + +use zeroize::Zeroize; + +use super::{string::MaybeStr, ResizeBuffer, WriteBuffer}; +use crate::error::Error; + +/// A heap-allocated, zeroized byte buffer +#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Zeroize)] +pub struct SecretBytes(Vec); + +impl SecretBytes { + pub fn new_with(len: usize, f: impl FnOnce(&mut [u8])) -> Self { + let mut slf = Self::with_capacity(len); + slf.0.resize(len, 0u8); + f(slf.0.as_mut()); + slf + } + + pub fn with_capacity(max_len: usize) -> Self { + Self(Vec::with_capacity(max_len)) + } + + pub fn from_slice(data: &[u8]) -> Self { + let mut v = Vec::with_capacity(data.len()); + v.extend_from_slice(data); + Self(v) + } + + /// Try to convert the buffer value to a string reference + pub fn as_opt_str(&self) -> Option<&str> { + core::str::from_utf8(self.0.as_slice()).ok() + } + + pub fn ensure_capacity(&mut self, min_cap: usize) { + let cap = self.0.capacity(); + if cap == 0 { + self.0.reserve(min_cap); + } else if cap > 0 && min_cap >= cap { + // allocate a new buffer and copy the secure data over + let new_cap = min_cap.max(cap * 2).max(32); + let mut buf = SecretBytes::with_capacity(new_cap); + buf.0.extend_from_slice(&self.0[..]); + mem::swap(&mut buf, self); + // old buf zeroized on drop + } + } + + pub fn reserve(&mut self, extra: usize) { + self.ensure_capacity(self.len() + extra) + } + + pub fn into_vec(mut self) -> Vec { + // FIXME zeroize extra capacity? + let mut v = Vec::new(); // note: no heap allocation for empty vec + mem::swap(&mut v, &mut self.0); + mem::forget(self); + v + } + + pub(crate) fn as_vec_mut(&mut self) -> &mut Vec { + &mut self.0 + } +} + +impl Debug for SecretBytes { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + if cfg!(test) { + f.debug_tuple("Secret") + .field(&MaybeStr(self.0.as_slice())) + .finish() + } else { + f.write_str("") + } + } +} + +impl AsRef<[u8]> for SecretBytes { + fn as_ref(&self) -> &[u8] { + self.0.as_slice() + } +} + +impl AsMut<[u8]> for SecretBytes { + fn as_mut(&mut self) -> &mut [u8] { + self.0.as_mut_slice() + } +} + +impl Deref for SecretBytes { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + self.0.as_slice() + } +} + +impl Drop for SecretBytes { + fn drop(&mut self) { + self.zeroize(); + } +} + +impl From<&[u8]> for SecretBytes { + fn from(inner: &[u8]) -> Self { + Self(inner.to_vec()) + } +} + +impl From<&str> for SecretBytes { + fn from(inner: &str) -> Self { + Self(inner.as_bytes().to_vec()) + } +} + +impl From for SecretBytes { + fn from(inner: String) -> Self { + Self(inner.into_bytes()) + } +} + +impl From> for SecretBytes { + fn from(inner: Vec) -> Self { + Self(inner) + } +} + +impl PartialEq<&[u8]> for SecretBytes { + fn eq(&self, other: &&[u8]) -> bool { + self.0.eq(other) + } +} + +impl PartialEq> for SecretBytes { + fn eq(&self, other: &Vec) -> bool { + self.0.eq(other) + } +} + +impl WriteBuffer for SecretBytes { + fn write_with( + &mut self, + max_len: usize, + f: impl FnOnce(&mut [u8]) -> Result, + ) -> Result { + let len = self.0.len(); + let new_len = len + max_len; + self.buffer_resize(new_len)?; + let written = f(&mut self.0[len..new_len])?; + if written < max_len { + self.0.truncate(len + written); + } + Ok(written) + } +} + +impl ResizeBuffer for SecretBytes { + fn buffer_resize(&mut self, len: usize) -> Result<(), Error> { + self.ensure_capacity(len); + self.0.resize(len, 0u8); + Ok(()) + } + + fn buffer_splice_with( + &mut self, + range: Range, + len: usize, + f: impl FnOnce(&mut [u8]) -> Result<(), Error>, + ) -> Result<(), Error> { + let rem_len = range.len(); + if len > rem_len { + self.reserve(len - rem_len); + } + let start = range.start; + self.0.splice(range, iter::repeat(0u8).take(len)); + f(&mut self.0[start..(start + len)])?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + pub(crate) fn test_write_buffer>(mut w: B) { + w.write_with(5, |buf| { + buf.copy_from_slice(b"hello"); + Ok(2) + }) + .unwrap(); + w.write_slice(b"y").unwrap(); + assert_eq!(&w.as_ref()[..], b"hey"); + } + + pub(crate) fn test_resize_buffer(mut w: B) { + w.write_slice(b"hello").unwrap(); + w.buffer_splice_with(1..3, 5, |ext| { + ext.copy_from_slice(b"sugar"); + Ok(()) + }) + .unwrap(); + assert_eq!(&w.as_ref()[..], b"hsugarlo"); + w.buffer_splice_with(1..6, 2, |ext| { + ext.copy_from_slice(b"el"); + Ok(()) + }) + .unwrap(); + assert_eq!(&w.as_ref()[..], b"hello"); + w.buffer_resize(7).unwrap(); + assert_eq!(&w.as_ref()[..], b"hello\0\0"); + w.buffer_resize(5).unwrap(); + assert_eq!(&w.as_ref()[..], b"hello"); + } + + #[test] + fn write_buffer_vec() { + test_write_buffer(Vec::new()); + } + + #[test] + fn resize_buffer_vec() { + test_resize_buffer(Vec::new()); + } + + #[test] + fn write_buffer_secret() { + test_write_buffer(SecretBytes::with_capacity(10)); + } + + #[test] + fn resize_buffer_secret() { + test_resize_buffer(SecretBytes::with_capacity(10)); + } +} diff --git a/askar-crypto/src/buffer/writer.rs b/askar-crypto/src/buffer/writer.rs index 0cb660c3..3acbbbcc 100644 --- a/askar-crypto/src/buffer/writer.rs +++ b/askar-crypto/src/buffer/writer.rs @@ -1,4 +1,5 @@ use alloc::vec::Vec; +use core::ops::Range; use super::{ResizeBuffer, WriteBuffer}; use crate::error::Error; @@ -50,7 +51,7 @@ impl WriteBuffer for Writer<'_, [u8]> { let total = self.inner.len(); let end = max_len + self.pos; if end > total { - return Err(err_msg!("exceeded buffer size")); + return Err(err_msg!(ExceededBuffer)); } let written = f(&mut self.inner[self.pos..end])?; self.pos += written; @@ -62,11 +63,43 @@ impl ResizeBuffer for Writer<'_, [u8]> { fn buffer_resize(&mut self, len: usize) -> Result<(), Error> { let len = self.pos + len; if len > self.inner.len() { - return Err(err_msg!("exceeded allocated buffer")); + return Err(err_msg!(ExceededBuffer)); } self.pos = len; Ok(()) } + + fn buffer_splice_with( + &mut self, + range: Range, + len: usize, + f: impl FnOnce(&mut [u8]) -> Result<(), Error>, + ) -> Result<(), Error> { + let rem_len = range.len(); + if rem_len < len { + if self.pos + len - rem_len > self.inner.len() { + return Err(err_msg!(ExceededBuffer)); + } + let diff = len - rem_len; + self.pos += diff; + for p in (self.pos - 1)..=range.end { + self.inner[p] = self.inner[p - diff]; + } + } else if rem_len != len { + let diff = rem_len - len; + for p in range.end..self.pos { + self.inner[p] = self.inner[p + diff]; + } + self.pos -= diff; + } + let end = range.start + len; + let mslice = &mut self.inner[range.start..end]; + for p in 0..len { + mslice[p] = 0u8; + } + f(mslice)?; + Ok(()) + } } impl<'w> Writer<'w, Vec> { @@ -107,6 +140,15 @@ impl ResizeBuffer for Writer<'_, B> { fn buffer_resize(&mut self, len: usize) -> Result<(), Error> { self.inner.buffer_resize(self.pos + len) } + + fn buffer_splice_with( + &mut self, + range: Range, + len: usize, + f: impl FnOnce(&mut [u8]) -> Result<(), Error>, + ) -> Result<(), Error> { + self.inner.buffer_splice_with(range, len, f) + } } #[cfg(test)] diff --git a/askar-crypto/src/caps.rs b/askar-crypto/src/caps.rs index e43c6c56..d217bdbb 100644 --- a/askar-crypto/src/caps.rs +++ b/askar-crypto/src/caps.rs @@ -1,4 +1,3 @@ -use alloc::string::{String, ToString}; use core::{ fmt::{self, Display, Formatter}, str::FromStr, @@ -11,33 +10,6 @@ use crate::{ error::Error, }; -// #[cfg(feature = "any")] -// use crate::any::AnyKey; - -/// Generate a new random key. -pub trait KeyGen: Sized { - fn generate() -> Result; -} - -/// Allows a key to be created uninitialized and populated later, -/// for instance when nested inside another struct. -pub trait KeyGenInPlace { - fn generate_in_place(&mut self) -> Result<(), Error>; -} - -/// Initialize a key from an array of bytes. -pub trait KeySecretBytes: Sized { - fn from_key_secret_bytes(key: &[u8]) -> Result; - - fn to_key_secret_buffer(&self, out: &mut B) -> Result<(), Error>; - - fn to_key_secret_bytes(&self) -> Result { - let mut buf = SecretBytes::with_capacity(128); - self.to_key_secret_buffer(&mut buf)?; - Ok(buf) - } -} - pub trait KeyCapSign { fn key_sign_buffer( &self, @@ -117,56 +89,6 @@ impl Display for KeyAlg { } } -/// Categories of keys supported by the default KMS -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Zeroize)] -pub enum KeyCategory { - /// A private key or keypair - PrivateKey, - /// A public key - PublicKey, -} - -impl KeyCategory { - /// Get a reference to a string representing the `KeyCategory` - pub fn as_str(&self) -> &str { - match self { - Self::PrivateKey => "private", - Self::PublicKey => "public", - } - } - - /// Convert the `KeyCategory` into an owned string - pub fn to_string(&self) -> String { - self.as_str().to_string() - } -} - -serde_as_str_impl!(KeyCategory); - -impl AsRef for KeyCategory { - fn as_ref(&self) -> &str { - self.as_str() - } -} - -impl FromStr for KeyCategory { - type Err = Error; - - fn from_str(s: &str) -> Result { - Ok(match s { - "private" => Self::PrivateKey, - "public" => Self::PublicKey, - _ => return Err(err_msg!("Unknown key category: {}", s)), - }) - } -} - -impl Display for KeyCategory { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.write_str(self.as_str()) - } -} - #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum SignatureFormat { /// Base58-encoded binary signature diff --git a/askar-crypto/src/encrypt/mod.rs b/askar-crypto/src/encrypt/mod.rs index 04bd042a..54981f32 100644 --- a/askar-crypto/src/encrypt/mod.rs +++ b/askar-crypto/src/encrypt/mod.rs @@ -1,7 +1,10 @@ +use crate::generic_array::ArrayLength; + use crate::{buffer::ResizeBuffer, error::Error}; pub mod nacl_box; +/// Trait for key types which perform AEAD encryption pub trait KeyAeadInPlace { /// Encrypt a secret value in place, appending the verification tag fn encrypt_in_place( @@ -19,9 +22,15 @@ pub trait KeyAeadInPlace { aad: &[u8], ) -> Result<(), Error>; - /// Get the required nonce size for encryption - fn nonce_size() -> usize; + /// Get the required nonce length for encryption + fn nonce_length() -> usize; + + /// Get the length of the verification tag + fn tag_length() -> usize; +} - /// Get the size of the verification tag - fn tag_size() -> usize; +/// For concrete key types with fixed nonce and tag sizes +pub trait KeyAeadMeta { + type NonceSize: ArrayLength; + type TagSize: ArrayLength; } diff --git a/askar-crypto/src/encrypt/nacl_box.rs b/askar-crypto/src/encrypt/nacl_box.rs index 55d76323..f849010e 100644 --- a/askar-crypto/src/encrypt/nacl_box.rs +++ b/askar-crypto/src/encrypt/nacl_box.rs @@ -9,8 +9,8 @@ use crypto_box::{self as cbox, SalsaBox}; use crate::{ alg::x25519::X25519KeyPair, buffer::{ResizeBuffer, SecretBytes, WriteBuffer}, - caps::KeyGen, error::Error, + repr::{KeyGen, KeyPublicBytes}, }; const CBOX_NONCE_SIZE: usize = NonceSize::::USIZE; @@ -109,7 +109,7 @@ pub fn crypto_box_seal_open( recip_sk: &X25519KeyPair, ciphertext: &[u8], ) -> Result { - let ephem_pk = X25519KeyPair::from_public_key_bytes(&ciphertext[..CBOX_KEY_SIZE])?; + let ephem_pk = X25519KeyPair::from_public_bytes(&ciphertext[..CBOX_KEY_SIZE])?; let mut buffer = SecretBytes::from_slice(&ciphertext[CBOX_KEY_SIZE..]); let nonce = crypto_box_nonce(ephem_pk.public.as_bytes(), recip_sk.public.as_bytes())?; crypto_box_open(recip_sk, &ephem_pk, &mut buffer, &nonce)?; @@ -120,15 +120,15 @@ pub fn crypto_box_seal_open( mod tests { use super::*; use crate::buffer::SecretBytes; - use crate::caps::KeySecretBytes; + use crate::repr::KeySecretBytes; #[test] fn crypto_box_round_trip_expected() { - let sk = X25519KeyPair::from_key_secret_bytes(&hex!( + let sk = X25519KeyPair::from_secret_bytes(&hex!( "a8bdb9830f8790d242f66e04b11cc2a14c752a7b63c073f3c68e9adb151cc854" )) .unwrap(); - let pk = X25519KeyPair::from_public_key_bytes(&hex!( + let pk = X25519KeyPair::from_public_bytes(&hex!( "07d0b594683bdb6af5f4eacb1a392687d580a58db196a752dca316dedb7d251c" )) .unwrap(); @@ -150,7 +150,7 @@ mod tests { let recip = X25519KeyPair::generate().unwrap(); let recip_public = - X25519KeyPair::from_public_key_bytes(&recip.to_public_key_bytes()).unwrap(); + X25519KeyPair::from_public_bytes(recip.to_public_bytes().unwrap().as_ref()).unwrap(); let message = b"hello there"; let sealed = crypto_box_seal(&recip_public, message).unwrap(); diff --git a/askar-crypto/src/error.rs b/askar-crypto/src/error.rs index 99c3068b..571c5e1f 100644 --- a/askar-crypto/src/error.rs +++ b/askar-crypto/src/error.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "std")] +use alloc::boxed::Box; use alloc::string::String; use core::fmt::{self, Display, Formatter}; @@ -13,6 +15,9 @@ pub enum ErrorKind { /// The input parameters to the method were incorrect Input, + /// Out of space in provided buffer + ExceededBuffer, + /// The provided nonce was invalid (bad length) InvalidNonce, @@ -31,6 +36,7 @@ impl ErrorKind { pub fn as_str(&self) -> &'static str { match self { Self::Encryption => "Encryption error", + Self::ExceededBuffer => "Exceeded allocated buffer", Self::Input => "Input error", Self::InvalidNonce => "Invalid encryption nonce", Self::MissingSecretKey => "Missing secret key", @@ -80,6 +86,11 @@ impl Error { self.kind } + /// Accessor for the error message + pub fn message(&self) -> Option<&str> { + self.message.as_ref().map(String::as_str) + } + #[cfg(not(feature = "std"))] pub(crate) fn with_cause>>(mut self, err: T) -> Self { self.cause = err.into(); diff --git a/askar-crypto/src/kdf/argon2.rs b/askar-crypto/src/kdf/argon2.rs index 855415d2..803659d0 100644 --- a/askar-crypto/src/kdf/argon2.rs +++ b/askar-crypto/src/kdf/argon2.rs @@ -1,9 +1,13 @@ -use crate::{buffer::SecretBytes, error::Error, random::random_secret}; +use crate::{ + error::Error, + generic_array::typenum::{Unsigned, U16}, +}; pub use argon2::{Algorithm, Version}; -pub const HASH_SIZE: usize = 32; -pub const SALT_SIZE: usize = 16; +pub type SaltSize = U16; + +pub const SALT_LENGTH: usize = SaltSize::USIZE; pub const PARAMS_INTERACTIVE: Params = Params { alg: Algorithm::Argon2i, @@ -18,6 +22,7 @@ pub const PARAMS_MODERATE: Params = Params { time_cost: 6, }; +#[derive(Copy, Clone, Debug)] pub struct Params { alg: Algorithm, version: Version, @@ -25,47 +30,6 @@ pub struct Params { time_cost: u32, } -// pub const LEVEL_INTERACTIVE: &'static str = "13:int"; -// pub const LEVEL_MODERATE: &'static str = "13:mod"; - -// #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] -// pub enum Level { -// Interactive, -// Moderate, -// } - -// impl Default for Level { -// fn default() -> Self { -// Self::Moderate -// } -// } - -// impl Level { -// pub fn from_str(level: &str) -> Option { -// match level { -// "int" | LEVEL_INTERACTIVE => Some(Self::Interactive), -// "mod" | LEVEL_MODERATE => Some(Self::Moderate), -// "" => Some(Self::default()), -// _ => None, -// } -// } - -// pub fn as_str(&self) -> &'static str { -// match self { -// Self::Interactive => LEVEL_INTERACTIVE, -// Self::Moderate => LEVEL_MODERATE, -// } -// } - -// // pub fn derive_key(&self, salt: &[u8], password: &str) -> Result> { -// // let (mem_cost, time_cost) = match self { -// // Self::Interactive => (32768, 4), -// // Self::Moderate => (131072, 6), -// // }; -// // derive_key(password, salt, mem_cost, time_cost) -// // } -// } - pub struct Argon2; impl Argon2 { @@ -75,7 +39,7 @@ impl Argon2 { params: Params, output: &mut [u8], ) -> Result<(), Error> { - if salt.len() < SALT_SIZE { + if salt.len() < SALT_LENGTH { return Err(err_msg!("invalid salt for argon2i hash")); } if output.len() > u32::MAX as usize { @@ -90,11 +54,6 @@ impl Argon2 { } } -// FIXME generate into buffer -pub fn generate_salt() -> SecretBytes { - random_secret(SALT_SIZE) -} - #[cfg(test)] mod tests { use super::*; diff --git a/askar-crypto/src/kdf/ecdh_1pu.rs b/askar-crypto/src/kdf/ecdh_1pu.rs index 5b6e0e35..a2b89b71 100644 --- a/askar-crypto/src/kdf/ecdh_1pu.rs +++ b/askar-crypto/src/kdf/ecdh_1pu.rs @@ -9,9 +9,9 @@ use super::{ }; use crate::{ buffer::WriteBuffer, - caps::KeyGen, error::Error, jwk::{JwkEncoder, JwkEncoderMode, ToJwk}, + repr::KeyGen, }; pub struct Ecdh1PU(PhantomData); diff --git a/askar-crypto/src/kdf/ecdh_es.rs b/askar-crypto/src/kdf/ecdh_es.rs index d8003a72..5f74d690 100644 --- a/askar-crypto/src/kdf/ecdh_es.rs +++ b/askar-crypto/src/kdf/ecdh_es.rs @@ -9,9 +9,9 @@ use super::{ }; use crate::{ buffer::WriteBuffer, - caps::KeyGen, error::Error, jwk::{JwkEncoder, JwkEncoderMode, ToJwk}, + repr::KeyGen, }; pub struct EcdhEs(PhantomData); diff --git a/askar-crypto/src/lib.rs b/askar-crypto/src/lib.rs index 4e1f3ef7..fcab25c4 100644 --- a/askar-crypto/src/lib.rs +++ b/askar-crypto/src/lib.rs @@ -6,7 +6,7 @@ // #[cfg(feature="alloc")] extern crate alloc; -#[cfg(test)] +#[cfg(any(test, feature = "std"))] #[macro_use] extern crate std; @@ -16,6 +16,7 @@ extern crate hex_literal; #[macro_use] mod error; +pub use self::error::{Error, ErrorKind}; #[macro_use] mod serde_utils; @@ -30,14 +31,10 @@ pub use self::any::{AnyPrivateKey, AnyPublicKey}; pub mod alg; -mod buffer; -pub use self::buffer::{ResizeBuffer, SecretBytes, WriteBuffer, Writer}; +pub mod buffer; pub mod caps; -pub use self::caps::{ - KeyAlg, KeyCapSign, KeyCapVerify, KeyCategory, KeyGen, KeyGenInPlace, KeySecretBytes, - SignatureFormat, SignatureType, -}; +pub use self::caps::{KeyAlg, KeyCapSign, KeyCapVerify, SignatureFormat, SignatureType}; pub mod encrypt; @@ -48,3 +45,6 @@ pub mod kdf; pub mod pack; pub mod random; + +pub mod repr; +pub use repr::{KeyGen, KeyGenInPlace, KeySecretBytes}; diff --git a/askar-crypto/src/repr.rs b/askar-crypto/src/repr.rs new file mode 100644 index 00000000..8ca4577c --- /dev/null +++ b/askar-crypto/src/repr.rs @@ -0,0 +1,71 @@ +use crate::{ + buffer::{SecretBytes, WriteBuffer}, + error::Error, + generic_array::ArrayLength, +}; + +/// Generate a new random key. +pub trait KeyGen: Sized { + fn generate() -> Result; +} + +/// Allows a key to be created uninitialized and populated later, +/// for instance when nested inside another struct. +pub trait KeyGenInPlace { + fn generate_in_place(&mut self) -> Result<(), Error>; +} + +/// Convert between key instance and key secret bytes. +pub trait KeySecretBytes { + fn from_secret_bytes(key: &[u8]) -> Result + where + Self: Sized; + + fn with_secret_bytes(&self, f: impl FnOnce(Option<&[u8]>) -> O) -> O; + + fn to_secret_bytes_buffer(&self, out: &mut B) -> Result<(), Error> { + self.with_secret_bytes(|buf| { + if let Some(buf) = buf { + out.write_slice(buf) + } else { + Err(err_msg!(MissingSecretKey)) + } + }) + } + + fn to_secret_bytes(&self) -> Result { + let mut buf = SecretBytes::with_capacity(128); + self.to_secret_bytes_buffer(&mut buf)?; + Ok(buf) + } +} + +/// Convert between key instance and key public bytes. +pub trait KeyPublicBytes { + fn from_public_bytes(key: &[u8]) -> Result + where + Self: Sized; + + fn with_public_bytes(&self, f: impl FnOnce(&[u8]) -> O) -> O; + + fn to_public_bytes_buffer(&self, out: &mut B) -> Result<(), Error> { + self.with_public_bytes(|buf| out.write_slice(buf)) + } + + fn to_public_bytes(&self) -> Result { + let mut buf = SecretBytes::with_capacity(128); + self.to_public_bytes_buffer(&mut buf)?; + Ok(buf) + } +} + +/// For concrete secret key types +pub trait KeyMeta { + type SecretKeySize: ArrayLength; +} + +/// For concrete secret + public key types +pub trait KeypairMeta: KeyMeta { + type PublicKeySize: ArrayLength; + type KeypairSize: ArrayLength; +} diff --git a/askar-crypto/src/store.rs b/askar-crypto/src/store.rs deleted file mode 100644 index d468aa78..00000000 --- a/askar-crypto/src/store.rs +++ /dev/null @@ -1,337 +0,0 @@ -use std::fmt::Debug; - -use serde::{Deserialize, Serialize}; - -use super::caps::{KeyAlg, KeyCategory}; -use super::encrypt::{aead::ChaChaEncrypt, SymEncrypt, SymEncryptHashKey, SymEncryptKey}; -use crate::error::Error; -use crate::keys::EntryEncryptor; -use crate::types::{sorted_tags, EncEntryTag, EntryTag, SecretBytes}; - -pub type EncKey = ::Key; -pub type HashKey = ::HashKey; -pub type StoreKey = StoreKeyImpl; - -/// A store key combining the keys required to encrypt -/// and decrypt storage records -#[derive(Clone, Debug, Deserialize, Serialize)] -#[serde(bound( - deserialize = "EncKey: for<'a> Deserialize<'a>, HashKey: for<'a> Deserialize<'a>", - serialize = "EncKey: Serialize, HashKey: Serialize" -))] -pub struct StoreKeyImpl { - pub category_key: EncKey, - pub name_key: EncKey, - pub value_key: EncKey, - pub item_hmac_key: HashKey, - pub tag_name_key: EncKey, - pub tag_value_key: EncKey, - pub tags_hmac_key: HashKey, -} - -impl StoreKeyImpl { - pub fn new() -> Result { - Ok(Self { - category_key: E::Key::random_key(), - name_key: E::Key::random_key(), - value_key: E::Key::random_key(), - item_hmac_key: E::HashKey::random_hash_key(), - tag_name_key: E::Key::random_key(), - tag_value_key: E::Key::random_key(), - tags_hmac_key: E::HashKey::random_hash_key(), - }) - } - - pub fn encrypt_tag_name(&self, name: SecretBytes) -> Result, Error> { - encrypt_searchable::(name, &self.tag_name_key, &self.tags_hmac_key) - } - - pub fn encrypt_tag_value(&self, value: SecretBytes) -> Result, Error> { - encrypt_searchable::(value, &self.tag_value_key, &self.tags_hmac_key) - } - - pub fn decrypt_tag_name(&self, enc_tag_name: Vec) -> Result { - E::decrypt(enc_tag_name, &self.tag_name_key) - } - - pub fn decrypt_tag_value(&self, enc_tag_value: Vec) -> Result { - E::decrypt(enc_tag_value, &self.tag_value_key) - } - - pub fn to_string(&self) -> Result { - serde_json::to_string(self).map_err(err_map!(Unexpected, "Error serializing store key")) - } - - pub fn from_slice(input: &[u8]) -> Result { - serde_json::from_slice(input).map_err(err_map!(Unsupported, "Invalid store key")) - } -} - -impl PartialEq for StoreKeyImpl { - fn eq(&self, other: &Self) -> bool { - self.category_key == other.category_key - && self.name_key == other.name_key - && self.value_key == other.value_key - && self.item_hmac_key == other.item_hmac_key - && self.tag_name_key == other.tag_name_key - && self.tag_value_key == other.tag_value_key - && self.tags_hmac_key == other.tags_hmac_key - } -} -impl Eq for StoreKeyImpl {} - -/// Encrypt a value with a predictable nonce, making it searchable -fn encrypt_searchable( - input: SecretBytes, - enc_key: &E::Key, - hmac_key: &E::HashKey, -) -> Result, Error> { - let nonce = E::hashed_nonce(&input, hmac_key)?; - E::encrypt(input, enc_key, Some(nonce)) -} - -impl EntryEncryptor for StoreKeyImpl -where - E: SymEncrypt, -{ - fn prepare_input(input: &[u8]) -> SecretBytes { - E::prepare_input(input) - } - - fn encrypt_entry_category(&self, category: SecretBytes) -> Result, Error> { - encrypt_searchable::(category, &self.category_key, &self.item_hmac_key) - } - - fn encrypt_entry_name(&self, name: SecretBytes) -> Result, Error> { - encrypt_searchable::(name, &self.name_key, &self.item_hmac_key) - } - - fn encrypt_entry_value(&self, value: SecretBytes) -> Result, Error> { - let value_key = E::Key::random_key(); - let mut value = E::encrypt(value, &value_key, None)?; - let key_input = E::prepare_input(value_key.as_bytes()); - let mut result = E::encrypt(key_input, &self.value_key, None)?; - result.append(&mut value); - Ok(result) - } - - fn decrypt_entry_category(&self, enc_category: Vec) -> Result { - decode_utf8(E::decrypt(enc_category, &self.category_key)?.into_vec()) - } - - fn decrypt_entry_name(&self, enc_name: Vec) -> Result { - decode_utf8(E::decrypt(enc_name, &self.name_key)?.into_vec()) - } - - fn decrypt_entry_value(&self, mut enc_value: Vec) -> Result { - let enc_key_size = E::encrypted_size(E::Key::SIZE); - if enc_value.len() < enc_key_size + E::encrypted_size(0) { - return Err(err_msg!( - Encryption, - "Buffer is too short to represent an encrypted value", - )); - } - let value = enc_value[enc_key_size..].to_vec(); - enc_value.truncate(enc_key_size); - let value_key = E::Key::from_slice( - E::decrypt(enc_value, &self.value_key)? - .into_vec() - .as_slice(), - ); - E::decrypt(value, &value_key) - } - - fn encrypt_entry_tags(&self, tags: Vec) -> Result, Error> { - tags.into_iter() - .map(|tag| match tag { - EntryTag::Plaintext(name, value) => { - let name = self.encrypt_tag_name(name.into())?; - Ok(EncEntryTag { - name, - value: value.into_bytes(), - plaintext: true, - }) - } - EntryTag::Encrypted(name, value) => { - let name = self.encrypt_tag_name(name.into())?; - let value = self.encrypt_tag_value(value.into())?; - Ok(EncEntryTag { - name, - value, - plaintext: false, - }) - } - }) - .collect() - } - - fn decrypt_entry_tags(&self, enc_tags: Vec) -> Result, Error> { - enc_tags.into_iter().try_fold(vec![], |mut acc, tag| { - let name = decode_utf8(self.decrypt_tag_name(tag.name)?.into_vec())?; - acc.push(if tag.plaintext { - let value = decode_utf8(tag.value)?; - EntryTag::Plaintext(name, value) - } else { - let value = decode_utf8(self.decrypt_tag_value(tag.value)?.into_vec())?; - EntryTag::Encrypted(name, value) - }); - Result::Ok(acc) - }) - } -} - -#[inline(always)] -fn decode_utf8(value: Vec) -> Result { - String::from_utf8(value).map_err(err_map!(Encryption)) -} - -/// Parameters defining a stored key -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] -pub struct KeyParams { - /// The key algorithm - pub alg: KeyAlg, - - /// Associated key metadata - #[serde(default, rename = "meta", skip_serializing_if = "Option::is_none")] - pub metadata: Option, - - /// An optional external reference for the key - #[serde(default, rename = "ref", skip_serializing_if = "Option::is_none")] - pub reference: Option, - - /// The associated key data in binary format - #[serde( - default, - skip_serializing_if = "Option::is_none", - with = "crate::serde_utils::as_base58" - )] - pub data: Option, -} - -impl KeyParams { - pub(crate) fn to_vec(&self) -> Result, Error> { - serde_json::to_vec(self) - .map_err(|e| err_msg!(Unexpected, "Error serializing key params: {}", e)) - } - - pub(crate) fn from_slice(params: &[u8]) -> Result { - let result = serde_json::from_slice(params) - .map_err(|e| err_msg!(Unexpected, "Error deserializing key params: {}", e)); - result - } -} - -/// A stored key entry -#[derive(Clone, Debug, Eq)] -pub struct KeyEntry { - /// The category of the key entry (public or private) - pub category: KeyCategory, - /// The key entry identifier - pub ident: String, - /// The parameters defining the key - pub params: KeyParams, - /// Tags associated with the key entry record - pub tags: Option>, -} - -impl KeyEntry { - /// Determine if a key entry refers to a local or external key - pub fn is_local(&self) -> bool { - self.params.reference.is_none() - } - - /// Fetch the associated key data - pub fn key_data(&self) -> Option<&[u8]> { - self.params.data.as_ref().map(AsRef::as_ref) - } - - pub(crate) fn sorted_tags(&self) -> Option> { - self.tags.as_ref().and_then(sorted_tags) - } -} - -impl PartialEq for KeyEntry { - fn eq(&self, rhs: &Self) -> bool { - self.category == rhs.category - && self.ident == rhs.ident - && self.params == rhs.params - && self.sorted_tags() == rhs.sorted_tags() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::types::Entry; - - #[test] - fn key_params_roundtrip() { - let params = KeyParams { - alg: KeyAlg::Ed25519, - metadata: Some("meta".to_string()), - reference: None, - data: Some(SecretBytes::from(vec![0, 0, 0, 0])), - }; - let enc_params = params.to_vec().unwrap(); - let p2 = KeyParams::from_slice(&enc_params).unwrap(); - assert_eq!(p2, params); - } - - #[test] - fn store_key_round_trip() { - let key = StoreKey::new().unwrap(); - let test_record = Entry::new( - "category", - "name", - "value", - Some(vec![ - EntryTag::Plaintext("plain".to_string(), "tag".to_string()), - EntryTag::Encrypted("enctag".to_string(), "envtagval".to_string()), - ]), - ); - let enc_category = key - .encrypt_entry_category(test_record.category.clone().into()) - .unwrap(); - let enc_name = key - .encrypt_entry_name(test_record.name.clone().into()) - .unwrap(); - let enc_value = key - .encrypt_entry_value(test_record.value.clone().into()) - .unwrap(); - let enc_tags = key - .encrypt_entry_tags(test_record.tags.clone().unwrap()) - .unwrap(); - assert_ne!(test_record.category.as_bytes(), enc_category.as_slice()); - assert_ne!(test_record.name.as_bytes(), enc_name.as_slice()); - assert_ne!(test_record.value, enc_value); - - let cmp_record = Entry::new( - key.decrypt_entry_category(enc_category).unwrap(), - key.decrypt_entry_name(enc_name).unwrap(), - key.decrypt_entry_value(enc_value).unwrap(), - Some(key.decrypt_entry_tags(enc_tags).unwrap()), - ); - assert_eq!(test_record, cmp_record); - } - - #[test] - fn store_key_searchable() { - const NONCE_SIZE: usize = 12; - let input = SecretBytes::from(&b"hello"[..]); - let key = EncKey::::random_key(); - let hmac_key = EncKey::::random(); - let enc1 = encrypt_searchable::(input.clone(), &key, &hmac_key).unwrap(); - let enc2 = encrypt_searchable::(input.clone(), &key, &hmac_key).unwrap(); - assert_eq!(&enc1[0..NONCE_SIZE], &enc2[0..NONCE_SIZE]); - let dec = ChaChaEncrypt::decrypt(enc1, &key).unwrap(); - assert_eq!(dec, input); - } - - #[test] - fn store_key_serde() { - let key = StoreKey::new().unwrap(); - let key_json = serde_json::to_string(&key).unwrap(); - let key_cmp = serde_json::from_str(&key_json).unwrap(); - assert_eq!(key, key_cmp); - } -} From 4c4bb32275be2b9a9a224cd0a0ccd4cb66488d3e Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Thu, 15 Apr 2021 11:22:58 -0700 Subject: [PATCH 023/116] misc API improvements Signed-off-by: Andrew Whitehead --- askar-crypto/src/alg/aesgcm.rs | 7 +++---- askar-crypto/src/alg/bls.rs | 2 +- askar-crypto/src/alg/chacha20.rs | 7 +++---- askar-crypto/src/alg/ed25519.rs | 6 +++--- askar-crypto/src/alg/k256.rs | 8 ++++---- askar-crypto/src/alg/p256.rs | 8 ++++---- askar-crypto/src/alg/x25519.rs | 6 +++--- askar-crypto/src/buffer/mod.rs | 5 +++++ askar-crypto/src/buffer/secret.rs | 9 +++++++++ askar-crypto/src/encrypt/mod.rs | 15 ++++++++++++--- askar-crypto/src/repr.rs | 2 +- 11 files changed, 48 insertions(+), 27 deletions(-) diff --git a/askar-crypto/src/alg/aesgcm.rs b/askar-crypto/src/alg/aesgcm.rs index aa2979b2..991be996 100644 --- a/askar-crypto/src/alg/aesgcm.rs +++ b/askar-crypto/src/alg/aesgcm.rs @@ -79,7 +79,7 @@ impl Debug for AesGcmKey { } impl KeyMeta for AesGcmKey { - type SecretKeySize = ::KeySize; + type KeySize = ::KeySize; } impl KeyGen for AesGcmKey { @@ -199,7 +199,7 @@ where #[cfg(test)] mod tests { use super::*; - use crate::{buffer::SecretBytes, random::fill_random}; + use crate::buffer::SecretBytes; #[test] fn encrypt_round_trip() { @@ -207,8 +207,7 @@ mod tests { let input = b"hello"; let key = AesGcmKey::::generate().unwrap(); let mut buffer = SecretBytes::from_slice(input); - let mut nonce = GenericArray::>::default(); - fill_random(&mut nonce); + let nonce = AesGcmKey::::random_nonce(); key.encrypt_in_place(&mut buffer, &nonce, &[]).unwrap(); assert_eq!(buffer.len(), input.len() + AesGcmKey::::TAG_LENGTH); assert_ne!(&buffer[..], input); diff --git a/askar-crypto/src/alg/bls.rs b/askar-crypto/src/alg/bls.rs index ad996b43..a95c5c64 100644 --- a/askar-crypto/src/alg/bls.rs +++ b/askar-crypto/src/alg/bls.rs @@ -243,7 +243,7 @@ where fn augment_message(pk: &impl GroupEncoding, message: &[u8]) -> Vec { let pk = pk.to_bytes(); - let mut ext_msg = Vec::with_capacity(pk.as_ref().len() + message.len()); + let mut ext_msg = Vec::with_capacity(pk.len() + message.len()); ext_msg.extend_from_slice(&pk.as_ref()); ext_msg.extend_from_slice(&message); ext_msg diff --git a/askar-crypto/src/alg/chacha20.rs b/askar-crypto/src/alg/chacha20.rs index bde93117..f7688819 100644 --- a/askar-crypto/src/alg/chacha20.rs +++ b/askar-crypto/src/alg/chacha20.rs @@ -85,7 +85,7 @@ impl Debug for Chacha20Key { } impl KeyMeta for Chacha20Key { - type SecretKeySize = ::KeySize; + type KeySize = ::KeySize; } impl KeyGen for Chacha20Key { @@ -218,7 +218,7 @@ where #[cfg(test)] mod tests { use super::*; - use crate::{buffer::SecretBytes, random::fill_random}; + use crate::buffer::SecretBytes; #[test] fn encrypt_round_trip() { @@ -226,8 +226,7 @@ mod tests { let input = b"hello"; let key = Chacha20Key::::generate().unwrap(); let mut buffer = SecretBytes::from_slice(input); - let mut nonce = GenericArray::>::default(); - fill_random(&mut nonce); + let nonce = Chacha20Key::::random_nonce(); key.encrypt_in_place(&mut buffer, &nonce, &[]).unwrap(); assert_eq!(buffer.len(), input.len() + Chacha20Key::::TAG_LENGTH); assert_ne!(&buffer[..], input); diff --git a/askar-crypto/src/alg/ed25519.rs b/askar-crypto/src/alg/ed25519.rs index f6c389a6..d8e542c2 100644 --- a/askar-crypto/src/alg/ed25519.rs +++ b/askar-crypto/src/alg/ed25519.rs @@ -148,7 +148,7 @@ impl KeyGen for Ed25519KeyPair { } impl KeyMeta for Ed25519KeyPair { - type SecretKeySize = U32; + type KeySize = U32; } impl KeypairMeta for Ed25519KeyPair { @@ -247,14 +247,14 @@ impl FromJwk for Ed25519KeyPair { fn from_jwk_parts(jwk: JwkParts<'_>) -> Result { // SECURITY: ArrayKey zeroizes on drop let mut pk = ArrayKey::::default(); - if jwk.x.decode_base64(pk.as_mut())? != pk.as_ref().len() { + if jwk.x.decode_base64(pk.as_mut())? != pk.len() { return Err(err_msg!("invalid length for ed25519 attribute 'x'")); } let pk = PublicKey::from_bytes(&pk.as_ref()[..]) .map_err(|_| err_msg!("Invalid ed25519 public key bytes"))?; let sk = if jwk.d.is_some() { let mut sk = ArrayKey::::default(); - if jwk.d.decode_base64(sk.as_mut())? != sk.as_ref().len() { + if jwk.d.decode_base64(sk.as_mut())? != sk.len() { return Err(err_msg!("invalid length for ed25519 attribute 'd'")); } Some( diff --git a/askar-crypto/src/alg/k256.rs b/askar-crypto/src/alg/k256.rs index 9f69b0ee..72682d93 100644 --- a/askar-crypto/src/alg/k256.rs +++ b/askar-crypto/src/alg/k256.rs @@ -104,7 +104,7 @@ impl K256KeyPair { } impl KeyMeta for K256KeyPair { - type SecretKeySize = U32; + type KeySize = U32; } impl KeypairMeta for K256KeyPair { @@ -221,10 +221,10 @@ impl FromJwk for K256KeyPair { // SECURITY: ArrayKey zeroizes on drop let mut pk_x = ArrayKey::::default(); let mut pk_y = ArrayKey::::default(); - if jwk.x.decode_base64(pk_x.as_mut())? != pk_x.as_ref().len() { + if jwk.x.decode_base64(pk_x.as_mut())? != pk_x.len() { return Err(err_msg!("invalid length for p-256 attribute 'x'")); } - if jwk.y.decode_base64(pk_y.as_mut())? != pk_y.as_ref().len() { + if jwk.y.decode_base64(pk_y.as_mut())? != pk_y.len() { return Err(err_msg!("invalid length for p-256 attribute 'y'")); } let pk = EncodedPoint::from_affine_coordinates(pk_x.as_ref(), pk_y.as_ref(), false) @@ -232,7 +232,7 @@ impl FromJwk for K256KeyPair { .map_err(|_| err_msg!("error decoding p-256 public key"))?; let sk = if jwk.d.is_some() { let mut sk = ArrayKey::::default(); - if jwk.d.decode_base64(sk.as_mut())? != sk.as_ref().len() { + if jwk.d.decode_base64(sk.as_mut())? != sk.len() { return Err(err_msg!("invalid length for p-256 attribute 'd'")); } Some( diff --git a/askar-crypto/src/alg/p256.rs b/askar-crypto/src/alg/p256.rs index 054704c1..3d3c7bb1 100644 --- a/askar-crypto/src/alg/p256.rs +++ b/askar-crypto/src/alg/p256.rs @@ -104,7 +104,7 @@ impl P256KeyPair { } impl KeyMeta for P256KeyPair { - type SecretKeySize = U32; + type KeySize = U32; } impl KeypairMeta for P256KeyPair { @@ -221,10 +221,10 @@ impl FromJwk for P256KeyPair { // SECURITY: ArrayKey zeroizes on drop let mut pk_x = ArrayKey::::default(); let mut pk_y = ArrayKey::::default(); - if jwk.x.decode_base64(pk_x.as_mut())? != pk_x.as_ref().len() { + if jwk.x.decode_base64(pk_x.as_mut())? != pk_x.len() { return Err(err_msg!("invalid length for p-256 attribute 'x'")); } - if jwk.y.decode_base64(pk_y.as_mut())? != pk_y.as_ref().len() { + if jwk.y.decode_base64(pk_y.as_mut())? != pk_y.len() { return Err(err_msg!("invalid length for p-256 attribute 'y'")); } let pk = EncodedPoint::from_affine_coordinates(pk_x.as_ref(), pk_y.as_ref(), false) @@ -232,7 +232,7 @@ impl FromJwk for P256KeyPair { .map_err(|_| err_msg!("error decoding p-256 public key"))?; let sk = if jwk.d.is_some() { let mut sk = ArrayKey::::default(); - if jwk.d.decode_base64(sk.as_mut())? != sk.as_ref().len() { + if jwk.d.decode_base64(sk.as_mut())? != sk.len() { return Err(err_msg!("invalid length for p-256 attribute 'd'")); } Some( diff --git a/askar-crypto/src/alg/x25519.rs b/askar-crypto/src/alg/x25519.rs index 6d798fd7..800e00dc 100644 --- a/askar-crypto/src/alg/x25519.rs +++ b/askar-crypto/src/alg/x25519.rs @@ -96,7 +96,7 @@ impl Debug for X25519KeyPair { } impl KeyMeta for X25519KeyPair { - type SecretKeySize = U32; + type KeySize = U32; } impl KeypairMeta for X25519KeyPair { @@ -184,7 +184,7 @@ impl FromJwk for X25519KeyPair { fn from_jwk_parts(jwk: JwkParts<'_>) -> Result { // SECURITY: ArrayKey zeroizes on drop let mut pk = ArrayKey::::default(); - if jwk.x.decode_base64(pk.as_mut())? != pk.as_ref().len() { + if jwk.x.decode_base64(pk.as_mut())? != pk.len() { return Err(err_msg!("invalid length for x25519 attribute 'x'")); } let pk = PublicKey::from( @@ -192,7 +192,7 @@ impl FromJwk for X25519KeyPair { ); let sk = if jwk.d.is_some() { let mut sk = ArrayKey::::default(); - if jwk.d.decode_base64(sk.as_mut())? != sk.as_ref().len() { + if jwk.d.decode_base64(sk.as_mut())? != sk.len() { return Err(err_msg!("invalid length for x25519 attribute 'd'")); } Some(SecretKey::from( diff --git a/askar-crypto/src/buffer/mod.rs b/askar-crypto/src/buffer/mod.rs index 18b17dc5..aa327cc5 100644 --- a/askar-crypto/src/buffer/mod.rs +++ b/askar-crypto/src/buffer/mod.rs @@ -45,6 +45,11 @@ impl> ArrayKey { Self(GenericArray::from_slice(data).clone()) } + #[inline] + pub fn len(&self) -> usize { + self.0.len() + } + #[inline] pub fn random() -> Self { let mut slf = GenericArray::default(); diff --git a/askar-crypto/src/buffer/secret.rs b/askar-crypto/src/buffer/secret.rs index 2f02046a..cf2aa34c 100644 --- a/askar-crypto/src/buffer/secret.rs +++ b/askar-crypto/src/buffer/secret.rs @@ -22,16 +22,23 @@ impl SecretBytes { slf } + #[inline] pub fn with_capacity(max_len: usize) -> Self { Self(Vec::with_capacity(max_len)) } + #[inline] pub fn from_slice(data: &[u8]) -> Self { let mut v = Vec::with_capacity(data.len()); v.extend_from_slice(data); Self(v) } + #[inline] + pub fn len(&self) -> usize { + self.0.len() + } + /// Try to convert the buffer value to a string reference pub fn as_opt_str(&self) -> Option<&str> { core::str::from_utf8(self.0.as_slice()).ok() @@ -51,10 +58,12 @@ impl SecretBytes { } } + #[inline] pub fn reserve(&mut self, extra: usize) { self.ensure_capacity(self.len() + extra) } + #[inline] pub fn into_vec(mut self) -> Vec { // FIXME zeroize extra capacity? let mut v = Vec::new(); // note: no heap allocation for empty vec diff --git a/askar-crypto/src/encrypt/mod.rs b/askar-crypto/src/encrypt/mod.rs index 54981f32..70b159d3 100644 --- a/askar-crypto/src/encrypt/mod.rs +++ b/askar-crypto/src/encrypt/mod.rs @@ -1,6 +1,9 @@ -use crate::generic_array::ArrayLength; - -use crate::{buffer::ResizeBuffer, error::Error}; +use crate::{ + buffer::ResizeBuffer, + error::Error, + generic_array::{ArrayLength, GenericArray}, + random::fill_random, +}; pub mod nacl_box; @@ -33,4 +36,10 @@ pub trait KeyAeadInPlace { pub trait KeyAeadMeta { type NonceSize: ArrayLength; type TagSize: ArrayLength; + + fn random_nonce() -> GenericArray { + let mut nonce = GenericArray::default(); + fill_random(nonce.as_mut_slice()); + nonce + } } diff --git a/askar-crypto/src/repr.rs b/askar-crypto/src/repr.rs index 8ca4577c..1b5abd44 100644 --- a/askar-crypto/src/repr.rs +++ b/askar-crypto/src/repr.rs @@ -61,7 +61,7 @@ pub trait KeyPublicBytes { /// For concrete secret key types pub trait KeyMeta { - type SecretKeySize: ArrayLength; + type KeySize: ArrayLength; } /// For concrete secret + public key types From d7eacd0589577e20a1f4ba0586bbb2192a50d457 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Thu, 15 Apr 2021 11:23:29 -0700 Subject: [PATCH 024/116] make concat KDF more reusable Signed-off-by: Andrew Whitehead --- askar-crypto/src/kdf/concat.rs | 27 ++++++++++++++++----------- askar-crypto/src/kdf/ecdh_1pu.rs | 25 ++++++++++++++++--------- askar-crypto/src/kdf/ecdh_es.rs | 25 ++++++++++++++++--------- 3 files changed, 48 insertions(+), 29 deletions(-) diff --git a/askar-crypto/src/kdf/concat.rs b/askar-crypto/src/kdf/concat.rs index 6ec58d18..2ae99176 100644 --- a/askar-crypto/src/kdf/concat.rs +++ b/askar-crypto/src/kdf/concat.rs @@ -15,6 +15,8 @@ pub struct ConcatKDFParams<'p> { pub alg: &'p [u8], pub apu: &'p [u8], pub apv: &'p [u8], + pub pub_info: &'p [u8], + pub prv_info: &'p [u8], } impl ConcatKDF @@ -36,7 +38,8 @@ where while remain > 0 { hasher.start_pass(); hasher.hash_message(message); - let hashed = hasher.finish_pass(params, output_len); + hasher.hash_params(params); + let hashed = hasher.finish_pass(); let cp_size = hashed.len().min(remain); &output[..cp_size].copy_from_slice(&hashed[..cp_size]); output = &mut output[cp_size..]; @@ -46,7 +49,7 @@ where } } -pub(crate) struct ConcatKDFHash { +pub struct ConcatKDFHash { hasher: H, counter: u32, } @@ -64,15 +67,11 @@ impl ConcatKDFHash { self.counter += 1; } - pub fn hash_message(&mut self, message: &[u8]) { - self.hasher.update(message); + pub fn hash_message(&mut self, data: &[u8]) { + self.hasher.update(data); } - pub fn finish_pass( - &mut self, - params: ConcatKDFParams<'_>, - output_len: usize, - ) -> GenericArray { + pub fn hash_params(&mut self, params: ConcatKDFParams<'_>) { let hash = &mut self.hasher; hash.update((params.alg.len() as u32).to_be_bytes()); hash.update(params.alg); @@ -80,8 +79,12 @@ impl ConcatKDFHash { hash.update(params.apu); hash.update((params.apv.len() as u32).to_be_bytes()); hash.update(params.apv); - hash.update((output_len as u32 * 8).to_be_bytes()); - hash.finalize_reset() + hash.update(params.pub_info); + hash.update(params.prv_info); + } + + pub fn finish_pass(&mut self) -> GenericArray { + self.hasher.finalize_reset() } } @@ -129,6 +132,8 @@ mod tests { alg: b"A256GCM", apu: b"Alice", apv: b"Bob", + pub_info: &(256u32).to_be_bytes(), + prv_info: &[], }, &mut output, ) diff --git a/askar-crypto/src/kdf/ecdh_1pu.rs b/askar-crypto/src/kdf/ecdh_1pu.rs index a2b89b71..ed52405b 100644 --- a/askar-crypto/src/kdf/ecdh_1pu.rs +++ b/askar-crypto/src/kdf/ecdh_1pu.rs @@ -21,7 +21,9 @@ impl Ecdh1PU { ephem_key: &Key, send_key: &Key, recip_key: &Key, - params: ConcatKDFParams, + alg: &[u8], + apu: &[u8], + apv: &[u8], key_output: &mut [u8], ) -> Result<(), Error> { let output_len = key_output.len(); @@ -34,7 +36,15 @@ impl Ecdh1PU { ephem_key.key_exchange_buffer(recip_key, &mut kdf)?; send_key.key_exchange_buffer(recip_key, &mut kdf)?; - let mut key = kdf.finish_pass(params, output_len); + kdf.hash_params(ConcatKDFParams { + alg, + apu, + apv, + pub_info: &(256u32).to_be_bytes(), // output length in bits + prv_info: &[], + }); + + let mut key = kdf.finish_pass(); key_output.copy_from_slice(&key[..output_len]); key.zeroize(); @@ -57,8 +67,7 @@ impl Ecdh1PU { let mut encoder = JwkEncoder::new(jwk_output, JwkEncoderMode::PublicKey)?; ephem_key.to_jwk_buffer(&mut encoder)?; - let params = ConcatKDFParams { alg, apu, apv }; - Self::derive_key_config(&ephem_key, send_key, recip_key, params, key_output)?; + Self::derive_key_config(&ephem_key, send_key, recip_key, alg, apu, apv, key_output)?; // SECURITY: keys must zeroize themselves on drop drop(ephem_key); @@ -108,11 +117,9 @@ mod tests { &ephem_sk, &alice_sk, &bob_sk, - ConcatKDFParams { - alg: b"A256GCM", - apu: b"Alice", - apv: b"Bob", - }, + b"A256GCM", + b"Alice", + b"Bob", &mut key_output, ) .unwrap(); diff --git a/askar-crypto/src/kdf/ecdh_es.rs b/askar-crypto/src/kdf/ecdh_es.rs index 5f74d690..019efce2 100644 --- a/askar-crypto/src/kdf/ecdh_es.rs +++ b/askar-crypto/src/kdf/ecdh_es.rs @@ -20,7 +20,9 @@ impl EcdhEs { fn derive_key_config( ephem_key: &Key, recip_key: &Key, - params: ConcatKDFParams, + alg: &[u8], + apu: &[u8], + apv: &[u8], key_output: &mut [u8], ) -> Result<(), Error> { let output_len = key_output.len(); @@ -32,7 +34,15 @@ impl EcdhEs { // hash Z directly into the KDF ephem_key.key_exchange_buffer(recip_key, &mut kdf)?; - let mut key = kdf.finish_pass(params, output_len); + kdf.hash_params(ConcatKDFParams { + alg, + apu, + apv, + pub_info: &(256u32).to_be_bytes(), // output length in bits + prv_info: &[], + }); + + let mut key = kdf.finish_pass(); key_output.copy_from_slice(&key[..output_len]); key.zeroize(); @@ -54,8 +64,7 @@ impl EcdhEs { let mut encoder = JwkEncoder::new(jwk_output, JwkEncoderMode::PublicKey)?; ephem_key.to_jwk_buffer(&mut encoder)?; - let params = ConcatKDFParams { alg, apu, apv }; - Self::derive_key_config(&ephem_key, recip_key, params, key_output)?; + Self::derive_key_config(&ephem_key, recip_key, alg, apu, apv, key_output)?; // SECURITY: keys must zeroize themselves on drop drop(ephem_key); @@ -99,11 +108,9 @@ mod tests { EcdhEs::derive_key_config( &ephem_sk, &bob_pk, - ConcatKDFParams { - alg: b"A256GCM", - apu: b"Alice", - apv: b"Bob", - }, + b"A256GCM", + b"Alice", + b"Bob", &mut key_output, ) .unwrap(); From 8ed7a7394ddcc85aa5027807bf797b1f97a63f44 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Thu, 15 Apr 2021 11:23:59 -0700 Subject: [PATCH 025/116] add simple benchmarks Signed-off-by: Andrew Whitehead --- askar-crypto/Cargo.toml | 9 ++++++++ askar-crypto/benches/enc.rs | 45 +++++++++++++++++++++++++++++++++++++ askar-crypto/benches/kdf.rs | 32 ++++++++++++++++++++++++++ 3 files changed, 86 insertions(+) create mode 100644 askar-crypto/benches/enc.rs create mode 100644 askar-crypto/benches/kdf.rs diff --git a/askar-crypto/Cargo.toml b/askar-crypto/Cargo.toml index 0592a778..af96f448 100644 --- a/askar-crypto/Cargo.toml +++ b/askar-crypto/Cargo.toml @@ -19,8 +19,17 @@ any = [] std = [] [dev-dependencies] +criterion = "0.3" hex-literal = "0.3" +[[bench]] +name = "enc" +harness = false + +[[bench]] +name = "kdf" +harness = false + [dependencies] aead = "0.3" aes-gcm = { version = "0.8", default-features = false, features = ["aes", "alloc"] } diff --git a/askar-crypto/benches/enc.rs b/askar-crypto/benches/enc.rs new file mode 100644 index 00000000..1f9590d3 --- /dev/null +++ b/askar-crypto/benches/enc.rs @@ -0,0 +1,45 @@ +#[macro_use] +extern crate criterion; + +#[macro_use] +extern crate hex_literal; + +use askar_crypto::{ + alg::chacha20::{Chacha20Key, C20P}, + buffer::{SecretBytes, WriteBuffer, Writer}, + encrypt::{KeyAeadInPlace, KeyAeadMeta}, + repr::KeySecretBytes, +}; + +use criterion::{black_box, Criterion}; + +fn criterion_benchmark(c: &mut Criterion) { + { + let message = b"test message for encrypting"; + + let key = &hex!("451b5b8e8725321541954997781de51f4142e4a56bab68d24f6a6b92615de5ee"); + + c.bench_function(&format!("chacha20-poly1305 encrypt"), move |b| { + b.iter(|| { + let key = Chacha20Key::::from_secret_bytes(&key[..]).unwrap(); + let mut buffer = [0u8; 255]; + buffer[0..message.len()].copy_from_slice(black_box(&message[..])); + let nonce = Chacha20Key::::random_nonce(); + let mut writer = Writer::from_slice_position(&mut buffer, message.len()); + key.encrypt_in_place(&mut writer, &nonce, &[]).unwrap(); + }) + }); + c.bench_function(&format!("chacha20-poly1305 encrypt alloc"), move |b| { + b.iter(|| { + let key = Chacha20Key::::from_secret_bytes(&key[..]).unwrap(); + let mut buffer = SecretBytes::with_capacity(255); + buffer.write_slice(black_box(&message[..])).unwrap(); + let nonce = Chacha20Key::::random_nonce(); + key.encrypt_in_place(&mut buffer, &nonce, &[]).unwrap(); + }) + }); + } +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); diff --git a/askar-crypto/benches/kdf.rs b/askar-crypto/benches/kdf.rs new file mode 100644 index 00000000..861869f9 --- /dev/null +++ b/askar-crypto/benches/kdf.rs @@ -0,0 +1,32 @@ +#[macro_use] +extern crate criterion; + +use askar_crypto::kdf::concat::{ConcatKDF, ConcatKDFParams}; +use sha2::Sha256; + +use criterion::{black_box, Criterion}; + +fn criterion_benchmark(c: &mut Criterion) { + { + let message = b"test message for encrypting"; + + let params = ConcatKDFParams { + alg: b"A256GCM", + apu: b"sender name", + apv: b"recipient name", + pub_info: &(256u32).to_be_bytes(), + prv_info: &[], + }; + + c.bench_function(&format!("concat kdf sha256"), move |b| { + b.iter(|| { + let mut output = [0u8; 32]; + ConcatKDF::::derive_key(black_box(message), black_box(params), &mut output) + .unwrap(); + }) + }); + } +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); From 66bc4f86942ea8fa2926c95c1ba30b38a6a46b13 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Thu, 15 Apr 2021 12:33:15 -0700 Subject: [PATCH 026/116] update key algorithms; clean up signature traits Signed-off-by: Andrew Whitehead --- askar-crypto/src/alg/ed25519.rs | 6 +- askar-crypto/src/alg/k256.rs | 6 +- askar-crypto/src/alg/mod.rs | 115 +++++++++++++++ askar-crypto/src/alg/p256.rs | 6 +- askar-crypto/src/caps.rs | 144 ------------------- askar-crypto/src/lib.rs | 13 +- askar-crypto/src/serde_utils.rs | 248 -------------------------------- askar-crypto/src/sign.rs | 52 +++++++ 8 files changed, 181 insertions(+), 409 deletions(-) delete mode 100644 askar-crypto/src/caps.rs delete mode 100644 askar-crypto/src/serde_utils.rs create mode 100644 askar-crypto/src/sign.rs diff --git a/askar-crypto/src/alg/ed25519.rs b/askar-crypto/src/alg/ed25519.rs index d8e542c2..007a2f12 100644 --- a/askar-crypto/src/alg/ed25519.rs +++ b/askar-crypto/src/alg/ed25519.rs @@ -12,11 +12,11 @@ use x25519_dalek::{PublicKey as XPublicKey, StaticSecret as XSecretKey}; use super::x25519::X25519KeyPair; use crate::{ buffer::{ArrayKey, SecretBytes, WriteBuffer}, - caps::{KeyCapSign, KeyCapVerify, SignatureType}, error::Error, generic_array::typenum::{U32, U64}, jwk::{FromJwk, JwkEncoder, JwkParts, ToJwk}, repr::{KeyGen, KeyMeta, KeyPublicBytes, KeySecretBytes, KeypairMeta}, + sign::{KeySigVerify, KeySign, SignatureType}, }; // FIXME - check for low-order points when loading public keys? @@ -186,7 +186,7 @@ impl KeyPublicBytes for Ed25519KeyPair { } } -impl KeyCapSign for Ed25519KeyPair { +impl KeySign for Ed25519KeyPair { fn key_sign_buffer( &self, data: &[u8], @@ -209,7 +209,7 @@ impl KeyCapSign for Ed25519KeyPair { } } -impl KeyCapVerify for Ed25519KeyPair { +impl KeySigVerify for Ed25519KeyPair { fn key_verify( &self, message: &[u8], diff --git a/askar-crypto/src/alg/k256.rs b/askar-crypto/src/alg/k256.rs index 72682d93..fe28e35c 100644 --- a/askar-crypto/src/alg/k256.rs +++ b/askar-crypto/src/alg/k256.rs @@ -12,13 +12,13 @@ use zeroize::Zeroizing; use crate::{ buffer::{ArrayKey, SecretBytes, WriteBuffer}, - caps::{KeyCapSign, KeyCapVerify, SignatureType}, error::Error, generic_array::typenum::{U32, U33, U65}, jwk::{FromJwk, JwkEncoder, JwkParts, ToJwk}, kdf::KeyExchange, random::with_rng, repr::{KeyGen, KeyMeta, KeyPublicBytes, KeySecretBytes, KeypairMeta}, + sign::{KeySigVerify, KeySign, SignatureType}, }; pub const ES256K_SIGNATURE_LENGTH: usize = 64; @@ -152,7 +152,7 @@ impl KeyPublicBytes for K256KeyPair { } } -impl KeyCapSign for K256KeyPair { +impl KeySign for K256KeyPair { fn key_sign_buffer( &self, message: &[u8], @@ -174,7 +174,7 @@ impl KeyCapSign for K256KeyPair { } } -impl KeyCapVerify for K256KeyPair { +impl KeySigVerify for K256KeyPair { fn key_verify( &self, message: &[u8], diff --git a/askar-crypto/src/alg/mod.rs b/askar-crypto/src/alg/mod.rs index 9cd6bc93..2e8ae87c 100644 --- a/askar-crypto/src/alg/mod.rs +++ b/askar-crypto/src/alg/mod.rs @@ -1,3 +1,12 @@ +use core::{ + fmt::{self, Display, Formatter}, + str::FromStr, +}; + +use zeroize::Zeroize; + +use crate::error::Error; + // pub mod bls; pub mod aesgcm; @@ -10,3 +19,109 @@ pub mod x25519; pub mod k256; pub mod p256; + +/// Supported key algorithms +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Zeroize)] +pub enum KeyAlg { + /// AES + Aes(AesSizes), + /// (X)ChaCha20-Poly1305 + Chacha20(Chacha20Sizes), + /// Curve25519 signing key + Ed25519, + /// Curve25519 diffie-hellman key exchange key + X25519, + /// Elliptic Curve key for signing or key exchange + EcCurve(EcCurves), + // /// BLS12-1381 signing key in group G1 or G2 + // BLS12_1381(BlsGroup), +} + +impl KeyAlg { + /// Get a reference to a string representing the `KeyAlg` + pub fn as_str(&self) -> &str { + match self { + Self::Aes(AesSizes::A128GCM) => "a128gcm", + Self::Aes(AesSizes::A192GCM) => "a192gcm", + Self::Aes(AesSizes::A256GCM) => "a256gcm", + Self::Chacha20(Chacha20Sizes::C20P) => "c20p", + Self::Chacha20(Chacha20Sizes::XC20P) => "xc20p", + Self::Ed25519 => "ed25519", + Self::X25519 => "x25519", + Self::EcCurve(EcCurves::Secp256k1) => "k256", + Self::EcCurve(EcCurves::Secp256r1) => "p256", + } + } +} + +impl AsRef for KeyAlg { + fn as_ref(&self) -> &str { + self.as_str() + } +} + +impl FromStr for KeyAlg { + type Err = Error; + + fn from_str(s: &str) -> Result { + Ok(match s { + "aes128gcm" => Self::Aes(AesSizes::A128GCM), + "aes192gcm" => Self::Aes(AesSizes::A192GCM), + "aes256gcm" => Self::Aes(AesSizes::A256GCM), + "chacha20poly1305" => Self::Chacha20(Chacha20Sizes::C20P), + "xchacha20poly1305" => Self::Chacha20(Chacha20Sizes::XC20P), + "c20p" => Self::Chacha20(Chacha20Sizes::C20P), + "xc20p" => Self::Chacha20(Chacha20Sizes::XC20P), + "ed25519" => Self::Ed25519, + "x25519" => Self::X25519, + "k256" => Self::EcCurve(EcCurves::Secp256k1), + "p256" => Self::EcCurve(EcCurves::Secp256r1), + "secp256k1" => Self::EcCurve(EcCurves::Secp256k1), + "secp256r1" => Self::EcCurve(EcCurves::Secp256r1), + _ => return Err(err_msg!("Unknown key algorithm: {}", s)), + }) + } +} + +impl Display for KeyAlg { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Zeroize)] +pub enum BlsGroups { + /// A key or signature represented by an element from the BLS12-381 G1 group + G1, + /// A key or signature represented by an element from the BLS12-381 G2 group + G2, +} + +/// Supported algorithms for AES +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Zeroize)] +pub enum AesSizes { + /// AES 128-bit GCM + A128GCM, + /// AES 192-bit GCM + A192GCM, + /// AES 256-bit GCM + A256GCM, +} + +/// Supported algorithms for (X)ChaCha20-Poly1305 +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Zeroize)] +pub enum Chacha20Sizes { + /// ChaCha20-Poly1305 + C20P, + /// XChaCha20-Poly1305 + XC20P, +} + +/// Supported curves for ECC operations +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Zeroize)] +pub enum EcCurves { + /// NIST P-256 curve + Secp256r1, + /// Koblitz 256 curve + Secp256k1, +} diff --git a/askar-crypto/src/alg/p256.rs b/askar-crypto/src/alg/p256.rs index 3d3c7bb1..c97302d5 100644 --- a/askar-crypto/src/alg/p256.rs +++ b/askar-crypto/src/alg/p256.rs @@ -12,13 +12,13 @@ use zeroize::Zeroizing; use crate::{ buffer::{ArrayKey, SecretBytes, WriteBuffer}, - caps::{KeyCapSign, KeyCapVerify, SignatureType}, error::Error, generic_array::typenum::{U32, U33, U65}, jwk::{FromJwk, JwkEncoder, JwkParts, ToJwk}, kdf::KeyExchange, random::with_rng, repr::{KeyGen, KeyMeta, KeyPublicBytes, KeySecretBytes, KeypairMeta}, + sign::{KeySigVerify, KeySign, SignatureType}, }; pub const ES256_SIGNATURE_LENGTH: usize = 64; @@ -152,7 +152,7 @@ impl KeyPublicBytes for P256KeyPair { } } -impl KeyCapSign for P256KeyPair { +impl KeySign for P256KeyPair { fn key_sign_buffer( &self, message: &[u8], @@ -174,7 +174,7 @@ impl KeyCapSign for P256KeyPair { } } -impl KeyCapVerify for P256KeyPair { +impl KeySigVerify for P256KeyPair { fn key_verify( &self, message: &[u8], diff --git a/askar-crypto/src/caps.rs b/askar-crypto/src/caps.rs deleted file mode 100644 index d217bdbb..00000000 --- a/askar-crypto/src/caps.rs +++ /dev/null @@ -1,144 +0,0 @@ -use core::{ - fmt::{self, Display, Formatter}, - str::FromStr, -}; - -use zeroize::Zeroize; - -use crate::{ - buffer::{SecretBytes, WriteBuffer}, - error::Error, -}; - -pub trait KeyCapSign { - fn key_sign_buffer( - &self, - data: &[u8], - sig_type: Option, - out: &mut B, - ) -> Result<(), Error>; - - fn key_sign(&self, data: &[u8], sig_type: Option) -> Result { - let mut buf = SecretBytes::with_capacity(128); - self.key_sign_buffer(data, sig_type, &mut buf)?; - Ok(buf) - } -} - -pub trait KeyCapVerify { - fn key_verify( - &self, - data: &[u8], - signature: &[u8], - sig_type: Option, - ) -> Result; -} - -/// Supported key algorithms -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Zeroize)] -pub enum KeyAlg { - /// Curve25519 signing key - Ed25519, - /// Curve25519 diffie-hellman key exchange key - X25519, - /// Elliptic Curve diffie-hellman key exchange key - Ecdh(EcCurves), - /// Elliptic Curve signing key - Ecdsa(EcCurves), - // /// BLS12-1381 signing key in group G1 or G2 - // BLS12_1381(BlsGroup), -} - -serde_as_str_impl!(KeyAlg); - -impl KeyAlg { - /// Get a reference to a string representing the `KeyAlg` - pub fn as_str(&self) -> &str { - match self { - Self::Ed25519 => "Ed25519", - Self::X25519 => "X25519", - Self::Ecdh(EcCurves::Secp256r1) => "P-256/ecdh", - Self::Ecdsa(EcCurves::Secp256r1) => "P-256/ecdsa", - Self::Ecdh(EcCurves::Secp256k1) => "secp256k1/ecdh", - Self::Ecdsa(EcCurves::Secp256k1) => "secp256k1/ecdsa", - } - } -} - -impl AsRef for KeyAlg { - fn as_ref(&self) -> &str { - self.as_str() - } -} - -impl FromStr for KeyAlg { - type Err = Error; - - fn from_str(s: &str) -> Result { - Ok(match s { - "Ed25519" => Self::Ed25519, - "X25519" => Self::X25519, - _ => return Err(err_msg!("Unknown key algorithm: {}", s)), - }) - } -} - -impl Display for KeyAlg { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.write_str(self.as_str()) - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum SignatureFormat { - /// Base58-encoded binary signature - Base58, - /// Base64-encoded binary signature - Base64, - /// Base64-URL-encoded binary signature - Base64Url, - /// Hex-encoded binary signature - Hex, - /// Raw binary signature (method dependent) - Raw, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum SignatureType { - Bls12_1381(BlsGroup), - /// Standard signature output for ed25519 - EdDSA, - // Elliptic curve DSA using P-256 and SHA-256 - ES256, - // Elliptic curve DSA using K-256 and SHA-256 - ES256K, -} - -impl SignatureType { - pub const fn signature_size(&self) -> usize { - match self { - Self::Bls12_1381(BlsGroup::G1) => 48, - Self::Bls12_1381(BlsGroup::G2) => 96, - Self::EdDSA => 64, - Self::ES256 => 64, - Self::ES256K => 64, - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Zeroize)] -pub enum BlsGroup { - /// A key or signature represented by an element from the BLS12-381 G1 group - G1, - /// A key or signature represented by an element from the BLS12-381 G2 group - G2, -} - -/// Supported curves for ECC operations -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Zeroize)] -pub enum EcCurves { - /// NIST P-256 curve - Secp256r1, - /// Koblitz 256 curve - Secp256k1, -} diff --git a/askar-crypto/src/lib.rs b/askar-crypto/src/lib.rs index fcab25c4..660b4a5a 100644 --- a/askar-crypto/src/lib.rs +++ b/askar-crypto/src/lib.rs @@ -1,7 +1,6 @@ #![no_std] // #![deny(missing_debug_implementations)] // #![deny(missing_docs)] -// #![deny(unsafe_code)] // #[cfg(feature="alloc")] extern crate alloc; @@ -18,9 +17,6 @@ extern crate hex_literal; mod error; pub use self::error::{Error, ErrorKind}; -#[macro_use] -mod serde_utils; - // re-export pub use aead::generic_array; @@ -30,12 +26,10 @@ pub mod any; pub use self::any::{AnyPrivateKey, AnyPublicKey}; pub mod alg; +pub use self::alg::KeyAlg; pub mod buffer; -pub mod caps; -pub use self::caps::{KeyAlg, KeyCapSign, KeyCapVerify, SignatureFormat, SignatureType}; - pub mod encrypt; pub mod jwk; @@ -46,5 +40,8 @@ pub mod pack; pub mod random; +pub mod sign; +pub use self::sign::{KeySigVerify, KeySign, SignatureType}; + pub mod repr; -pub use repr::{KeyGen, KeyGenInPlace, KeySecretBytes}; +pub use self::repr::{KeyGen, KeyGenInPlace, KeySecretBytes}; diff --git a/askar-crypto/src/serde_utils.rs b/askar-crypto/src/serde_utils.rs deleted file mode 100644 index e9d79d80..00000000 --- a/askar-crypto/src/serde_utils.rs +++ /dev/null @@ -1,248 +0,0 @@ -use core::{ - fmt::{self, Display}, - marker::PhantomData, - str::FromStr, -}; - -use serde::{de::Visitor, Deserializer, Serializer}; - -use crate::buffer::SecretBytes; - -macro_rules! serde_as_str_impl { - ($t:ident) => { - impl serde::Serialize for $t { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - $crate::serde_utils::as_str::serialize(self, serializer) - } - } - - impl<'de> serde::Deserialize<'de> for $t { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - $crate::serde_utils::as_str::deserialize(deserializer) - } - } - }; -} - -pub mod as_str { - use super::*; - - pub fn deserialize<'de, D, T>(deserializer: D) -> Result - where - D: Deserializer<'de>, - T: FromStr, - T::Err: Display, - { - deserializer.deserialize_str(FromStrVisitor { _pd: PhantomData }) - } - - pub fn serialize(inst: &T, serializer: S) -> Result - where - S: Serializer, - T: AsRef, - { - serializer.serialize_str(inst.as_ref()) - } - - struct FromStrVisitor { - _pd: PhantomData, - } - - impl<'de, T: FromStr> Visitor<'de> for FromStrVisitor - where - T::Err: Display, - { - type Value = T; - - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter.write_str("a valid string value") - } - - fn visit_str(self, v: &str) -> Result - where - E: serde::de::Error, - { - T::from_str(v).map_err(E::custom) - } - } -} - -// code structure borrowed from serde_bytes crate: - -pub mod as_base58 { - use super::*; - use alloc::{string::String, vec::Vec}; - - pub fn deserialize<'de, D, T>(deserializer: D) -> Result - where - D: Deserializer<'de>, - T: Deserialize<'de>, - { - Deserialize::deserialize(deserializer) - } - - pub fn serialize(inst: &T, serializer: S) -> Result - where - S: Serializer, - T: Serialize, - { - Serialize::serialize(inst, serializer) - } - - pub trait Serialize { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer; - } - - impl Serialize for Vec { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&bs58::encode(self).into_string()) - } - } - - impl Serialize for SecretBytes { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut s = bs58::encode(self).into_string(); - let ret = serializer.serialize_str(&s); - ::zeroize(&mut s); - ret - } - } - - impl<'a, T> Serialize for &'a T - where - T: ?Sized + Serialize, - { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - (**self).serialize(serializer) - } - } - - impl Serialize for Option - where - T: Serialize, - { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - struct Wrap(T); - - impl serde::Serialize for Wrap - where - T: Serialize, - { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - self.0.serialize(serializer) - } - } - - match self { - Some(val) => serializer.serialize_some(&Wrap(val)), - None => serializer.serialize_none(), - } - } - } - - pub trait Deserialize<'de>: Sized { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>; - } - - impl<'de> Deserialize<'de> for Vec { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct FromBase58Visitor; - - impl<'de> Visitor<'de> for FromBase58Visitor { - type Value = Vec; - - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter.write_str("a valid base58 string") - } - - fn visit_str(self, v: &str) -> Result - where - E: serde::de::Error, - { - bs58::decode(v).into_vec().map_err(E::custom) - } - } - - deserializer.deserialize_any(FromBase58Visitor) - } - } - - impl<'de> Deserialize<'de> for SecretBytes { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let result = as Deserialize>::deserialize(deserializer)?; - Ok(Self::from(result)) - } - } - - impl<'de, T> Deserialize<'de> for Option - where - T: Deserialize<'de>, - { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct FromBase58Visitor { - pd: PhantomData, - } - - impl<'de, T> Visitor<'de> for FromBase58Visitor - where - T: Deserialize<'de>, - { - type Value = Option; - - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter.write_str("an optional base58 string") - } - - fn visit_none(self) -> Result - where - E: serde::de::Error, - { - Ok(None) - } - - fn visit_some(self, deserializer: D) -> Result - where - D: Deserializer<'de>, - { - T::deserialize(deserializer).map(Some) - } - } - - deserializer.deserialize_option(FromBase58Visitor { pd: PhantomData }) - } - } -} diff --git a/askar-crypto/src/sign.rs b/askar-crypto/src/sign.rs new file mode 100644 index 00000000..27197dce --- /dev/null +++ b/askar-crypto/src/sign.rs @@ -0,0 +1,52 @@ +use crate::{ + alg::BlsGroups, + buffer::{SecretBytes, WriteBuffer}, + error::Error, +}; + +pub trait KeySign: KeySigVerify { + fn key_sign_buffer( + &self, + data: &[u8], + sig_type: Option, + out: &mut B, + ) -> Result<(), Error>; + + fn key_sign(&self, data: &[u8], sig_type: Option) -> Result { + let mut buf = SecretBytes::with_capacity(128); + self.key_sign_buffer(data, sig_type, &mut buf)?; + Ok(buf) + } +} + +pub trait KeySigVerify { + fn key_verify( + &self, + data: &[u8], + signature: &[u8], + sig_type: Option, + ) -> Result; +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum SignatureType { + Bls12_1381(BlsGroups), + /// Standard signature output for ed25519 + EdDSA, + // Elliptic curve DSA using P-256 and SHA-256 + ES256, + // Elliptic curve DSA using K-256 and SHA-256 + ES256K, +} + +impl SignatureType { + pub const fn signature_size(&self) -> usize { + match self { + Self::Bls12_1381(BlsGroups::G1) => 48, + Self::Bls12_1381(BlsGroups::G2) => 96, + Self::EdDSA => 64, + Self::ES256 => 64, + Self::ES256K => 64, + } + } +} From 266e4a918c2df15ca4e5c0ec9d54bfebd590378c Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Thu, 15 Apr 2021 14:48:41 -0700 Subject: [PATCH 027/116] clean up serde, equality traits for symmetric keys Signed-off-by: Andrew Whitehead --- askar-crypto/Cargo.toml | 1 + askar-crypto/src/alg/aesgcm.rs | 31 +++++- askar-crypto/src/alg/chacha20.rs | 31 +++++- askar-crypto/src/buffer/array.rs | 154 +++++++++++++++++++++++++++++ askar-crypto/src/buffer/mod.rs | 155 +----------------------------- askar-crypto/src/buffer/secret.rs | 36 +++++++ askar-crypto/src/buffer/string.rs | 11 ++- askar-crypto/src/jwk/parts.rs | 7 +- 8 files changed, 263 insertions(+), 163 deletions(-) create mode 100644 askar-crypto/src/buffer/array.rs diff --git a/askar-crypto/Cargo.toml b/askar-crypto/Cargo.toml index af96f448..5f2a5f3f 100644 --- a/askar-crypto/Cargo.toml +++ b/askar-crypto/Cargo.toml @@ -21,6 +21,7 @@ std = [] [dev-dependencies] criterion = "0.3" hex-literal = "0.3" +serde_cbor = "0.11" [[bench]] name = "enc" diff --git a/askar-crypto/src/alg/aesgcm.rs b/askar-crypto/src/alg/aesgcm.rs index 991be996..f2cd12e3 100644 --- a/askar-crypto/src/alg/aesgcm.rs +++ b/askar-crypto/src/alg/aesgcm.rs @@ -2,6 +2,7 @@ use core::fmt::{self, Debug, Formatter}; use aead::{Aead, AeadInPlace, NewAead}; use aes_gcm::{Aes128Gcm, Aes256Gcm}; +use serde::{Deserialize, Serialize}; use zeroize::Zeroize; use crate::{ @@ -48,7 +49,14 @@ type NonceSize = <::Aead as Aead>::NonceSize; type TagSize = <::Aead as Aead>::TagSize; -#[derive(PartialEq, Eq, Zeroize)] +#[derive(Serialize, Deserialize, Zeroize)] +#[serde( + transparent, + bound( + deserialize = "KeyType: for<'a> Deserialize<'a>", + serialize = "KeyType: Serialize" + ) +)] // SECURITY: ArrayKey is zeroized on drop pub struct AesGcmKey(KeyType); @@ -78,6 +86,14 @@ impl Debug for AesGcmKey { } } +impl PartialEq for AesGcmKey { + fn eq(&self, other: &Self) -> bool { + other.0 == self.0 + } +} + +impl Eq for AesGcmKey {} + impl KeyMeta for AesGcmKey { type KeySize = ::KeySize; } @@ -217,4 +233,17 @@ mod tests { test_encrypt::(); test_encrypt::(); } + + #[test] + fn serialize_round_trip() { + fn test_serialize() { + let key = AesGcmKey::::generate().unwrap(); + let sk = key.to_secret_bytes().unwrap(); + let bytes = serde_cbor::to_vec(&key).unwrap(); + let deser: &[u8] = serde_cbor::from_slice(bytes.as_ref()).unwrap(); + assert_eq!(deser, sk.as_ref()); + } + test_serialize::(); + test_serialize::(); + } } diff --git a/askar-crypto/src/alg/chacha20.rs b/askar-crypto/src/alg/chacha20.rs index f7688819..4c809383 100644 --- a/askar-crypto/src/alg/chacha20.rs +++ b/askar-crypto/src/alg/chacha20.rs @@ -2,6 +2,7 @@ use core::fmt::{self, Debug, Formatter}; use aead::{Aead, AeadInPlace, NewAead}; use chacha20poly1305::{ChaCha20Poly1305, XChaCha20Poly1305}; +use serde::{Deserialize, Serialize}; use zeroize::Zeroize; use crate::generic_array::{typenum::Unsigned, GenericArray}; @@ -46,7 +47,14 @@ type NonceSize = <::Aead as Aead>::NonceSize; type TagSize = <::Aead as Aead>::TagSize; -#[derive(PartialEq, Eq, Zeroize)] +#[derive(Serialize, Deserialize, Zeroize)] +#[serde( + transparent, + bound( + deserialize = "KeyType: for<'a> Deserialize<'a>", + serialize = "KeyType: Serialize" + ) +)] // SECURITY: ArrayKey is zeroized on drop pub struct Chacha20Key(KeyType); @@ -84,6 +92,14 @@ impl Debug for Chacha20Key { } } +impl PartialEq for Chacha20Key { + fn eq(&self, other: &Self) -> bool { + other.0 == self.0 + } +} + +impl Eq for Chacha20Key {} + impl KeyMeta for Chacha20Key { type KeySize = ::KeySize; } @@ -236,4 +252,17 @@ mod tests { test_encrypt::(); test_encrypt::(); } + + #[test] + fn serialize_round_trip() { + fn test_serialize() { + let key = Chacha20Key::::generate().unwrap(); + let sk = key.to_secret_bytes().unwrap(); + let bytes = serde_cbor::to_vec(&key).unwrap(); + let deser: &[u8] = serde_cbor::from_slice(bytes.as_ref()).unwrap(); + assert_eq!(deser, sk.as_ref()); + } + test_serialize::(); + test_serialize::(); + } } diff --git a/askar-crypto/src/buffer/array.rs b/askar-crypto/src/buffer/array.rs new file mode 100644 index 00000000..5ffdc0d6 --- /dev/null +++ b/askar-crypto/src/buffer/array.rs @@ -0,0 +1,154 @@ +use core::{ + cmp::Ordering, + fmt::{self, Debug, Formatter}, + marker::PhantomData, +}; + +use crate::generic_array::{ArrayLength, GenericArray}; +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; +use zeroize::Zeroize; + +use crate::random::fill_random; + +/// A secure representation for fixed-length keys +#[derive(Clone, Hash)] +pub struct ArrayKey>(GenericArray); + +impl> ArrayKey { + pub const SIZE: usize = L::USIZE; + + #[inline] + pub fn copy_from_slice>(&mut self, data: D) { + self.0[..].copy_from_slice(data.as_ref()); + } + + #[inline] + pub fn extract(self) -> GenericArray { + self.0.clone() + } + + #[inline] + pub fn from_slice(data: &[u8]) -> Self { + // like <&GenericArray>::from_slice, panics if the length is incorrect + Self(GenericArray::from_slice(data).clone()) + } + + #[inline] + pub fn len(&self) -> usize { + self.0.len() + } + + #[inline] + pub fn random() -> Self { + let mut slf = GenericArray::default(); + fill_random(&mut slf); + Self(slf) + } +} + +impl> AsRef> for ArrayKey { + fn as_ref(&self) -> &GenericArray { + &self.0 + } +} + +impl> AsMut> for ArrayKey { + fn as_mut(&mut self) -> &mut GenericArray { + &mut self.0 + } +} + +impl> Default for ArrayKey { + #[inline] + fn default() -> Self { + Self(GenericArray::default()) + } +} + +impl> From> for ArrayKey { + fn from(key: GenericArray) -> Self { + Self(key) + } +} + +impl> Debug for ArrayKey { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + if cfg!(test) { + f.debug_tuple("ArrayKey").field(&*self).finish() + } else { + f.debug_tuple("ArrayKey").field(&"").finish() + } + } +} + +impl> PartialEq for ArrayKey { + fn eq(&self, other: &Self) -> bool { + self.as_ref() == other.as_ref() + } +} +impl> Eq for ArrayKey {} + +impl> PartialOrd for ArrayKey { + fn partial_cmp(&self, other: &Self) -> Option { + self.0.partial_cmp(other.as_ref()) + } +} +impl> Ord for ArrayKey { + fn cmp(&self, other: &Self) -> Ordering { + self.0.cmp(other.as_ref()) + } +} + +impl> Serialize for ArrayKey { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_bytes(self.as_ref()) + } +} + +impl<'de, L: ArrayLength> Deserialize<'de> for ArrayKey { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_bytes(KeyVisitor { _pd: PhantomData }) + } +} + +impl> Zeroize for ArrayKey { + fn zeroize(&mut self) { + self.0.zeroize(); + } +} + +impl> Drop for ArrayKey { + fn drop(&mut self) { + self.zeroize(); + } +} + +struct KeyVisitor> { + _pd: PhantomData, +} + +impl<'de, L: ArrayLength> de::Visitor<'de> for KeyVisitor { + type Value = ArrayKey; + + fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { + formatter.write_str("ArrayKey") + } + + fn visit_bytes(self, value: &[u8]) -> Result + where + E: de::Error, + { + if value.len() != L::USIZE { + return Err(E::invalid_length(value.len(), &self)); + } + let mut arr = ArrayKey::default(); + arr.as_mut().copy_from_slice(value); + Ok(arr) + } +} diff --git a/askar-crypto/src/buffer/mod.rs b/askar-crypto/src/buffer/mod.rs index aa327cc5..ad8b6501 100644 --- a/askar-crypto/src/buffer/mod.rs +++ b/askar-crypto/src/buffer/mod.rs @@ -1,17 +1,10 @@ use alloc::vec::Vec; -use core::{ - cmp::Ordering, - fmt::{self, Debug, Formatter}, - iter, - marker::PhantomData, - ops::Range, -}; +use core::{iter, ops::Range}; -use crate::generic_array::{ArrayLength, GenericArray}; -use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; -use zeroize::Zeroize; +use crate::error::Error; -use crate::{error::Error, random::fill_random}; +mod array; +pub use self::array::ArrayKey; mod secret; pub use self::secret::SecretBytes; @@ -22,146 +15,6 @@ pub use self::string::HexRepr; mod writer; pub use self::writer::Writer; -/// A secure key representation for fixed-length keys -#[derive(Clone, Hash)] -pub struct ArrayKey>(GenericArray); - -impl> ArrayKey { - pub const SIZE: usize = L::USIZE; - - #[inline] - pub fn copy_from_slice>(&mut self, data: D) { - self.0[..].copy_from_slice(data.as_ref()); - } - - #[inline] - pub fn extract(self) -> GenericArray { - self.0.clone() - } - - #[inline] - pub fn from_slice(data: &[u8]) -> Self { - // like <&GenericArray>::from_slice, panics if the length is incorrect - Self(GenericArray::from_slice(data).clone()) - } - - #[inline] - pub fn len(&self) -> usize { - self.0.len() - } - - #[inline] - pub fn random() -> Self { - let mut slf = GenericArray::default(); - fill_random(&mut slf); - Self(slf) - } -} - -impl> AsRef> for ArrayKey { - fn as_ref(&self) -> &GenericArray { - &self.0 - } -} - -impl> AsMut> for ArrayKey { - fn as_mut(&mut self) -> &mut GenericArray { - &mut self.0 - } -} - -impl> Default for ArrayKey { - #[inline] - fn default() -> Self { - Self(GenericArray::default()) - } -} - -impl> From> for ArrayKey { - fn from(key: GenericArray) -> Self { - Self(key) - } -} - -impl> Debug for ArrayKey { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - if cfg!(test) { - f.debug_tuple("ArrayKey").field(&*self).finish() - } else { - f.debug_tuple("ArrayKey").field(&"").finish() - } - } -} - -impl> PartialEq for ArrayKey { - fn eq(&self, other: &Self) -> bool { - self.as_ref() == other.as_ref() - } -} -impl> Eq for ArrayKey {} - -impl> PartialOrd for ArrayKey { - fn partial_cmp(&self, other: &Self) -> Option { - self.0.partial_cmp(other.as_ref()) - } -} -impl> Ord for ArrayKey { - fn cmp(&self, other: &Self) -> Ordering { - self.0.cmp(other.as_ref()) - } -} - -impl> Serialize for ArrayKey { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.collect_str(&HexRepr(&self.0)) - } -} - -impl<'a, L: ArrayLength> Deserialize<'a> for ArrayKey { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'a>, - { - deserializer.deserialize_str(KeyVisitor { _pd: PhantomData }) - } -} - -impl> Zeroize for ArrayKey { - fn zeroize(&mut self) { - self.0.zeroize(); - } -} - -impl> Drop for ArrayKey { - fn drop(&mut self) { - self.zeroize(); - } -} - -struct KeyVisitor> { - _pd: PhantomData, -} - -impl<'a, L: ArrayLength> Visitor<'a> for KeyVisitor { - type Value = ArrayKey; - - fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { - formatter.write_str(stringify!($name)) - } - - fn visit_str(self, value: &str) -> Result, E> - where - E: serde::de::Error, - { - let mut arr = GenericArray::default(); - hex::decode_to_slice(value, &mut arr[..]).map_err(E::custom)?; - Ok(ArrayKey(arr)) - } -} - pub trait WriteBuffer { fn write_slice(&mut self, data: &[u8]) -> Result<(), Error> { let len = data.len(); diff --git a/askar-crypto/src/buffer/secret.rs b/askar-crypto/src/buffer/secret.rs index cf2aa34c..6cb413f3 100644 --- a/askar-crypto/src/buffer/secret.rs +++ b/askar-crypto/src/buffer/secret.rs @@ -5,6 +5,7 @@ use core::{ ops::{Deref, Range}, }; +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use zeroize::Zeroize; use super::{string::MaybeStr, ResizeBuffer, WriteBuffer}; @@ -192,6 +193,41 @@ impl ResizeBuffer for SecretBytes { } } +impl Serialize for SecretBytes { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_bytes(self.as_ref()) + } +} + +impl<'de> Deserialize<'de> for SecretBytes { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_bytes(SecVisitor) + } +} + +struct SecVisitor; + +impl<'de> de::Visitor<'de> for SecVisitor { + type Value = SecretBytes; + + fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { + formatter.write_str("bytes") + } + + fn visit_bytes(self, value: &[u8]) -> Result + where + E: de::Error, + { + Ok(SecretBytes::from_slice(value)) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/askar-crypto/src/buffer/string.rs b/askar-crypto/src/buffer/string.rs index 8ae93459..05205727 100644 --- a/askar-crypto/src/buffer/string.rs +++ b/askar-crypto/src/buffer/string.rs @@ -2,18 +2,19 @@ use core::fmt::{self, Debug, Display, Formatter, Write}; /// A utility type used to print or serialize a byte string as hex #[derive(Debug)] -pub struct HexRepr<'r>(pub &'r [u8]); +pub struct HexRepr(pub B); -impl<'r> Display for HexRepr<'r> { +impl> Display for HexRepr { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - for c in self.0 { + for c in self.0.as_ref() { f.write_fmt(format_args!("{:02x}", c))?; } Ok(()) } } -impl PartialEq<[u8]> for HexRepr<'_> { +// Compare to another hex value as [u8] +impl> PartialEq<[u8]> for HexRepr { fn eq(&self, other: &[u8]) -> bool { struct CmpWrite<'s>(::core::slice::Iter<'s, u8>); @@ -32,7 +33,7 @@ impl PartialEq<[u8]> for HexRepr<'_> { } } -impl PartialEq<&str> for HexRepr<'_> { +impl> PartialEq<&str> for HexRepr { fn eq(&self, other: &&str) -> bool { self == other.as_bytes() } diff --git a/askar-crypto/src/jwk/parts.rs b/askar-crypto/src/jwk/parts.rs index fadb1fe5..7c0fccef 100644 --- a/askar-crypto/src/jwk/parts.rs +++ b/askar-crypto/src/jwk/parts.rs @@ -168,7 +168,7 @@ impl<'de> Visitor<'de> for JwkMapVisitor<'de> { key_ops, }) } else { - Err(serde::de::Error::custom("missing 'kty' property for JWK")) + Err(serde::de::Error::missing_field("kty")) } } } @@ -199,10 +199,7 @@ impl<'de> Visitor<'de> for KeyOpsVisitor { while let Some(op) = seq.next_element()? { if let Some(op) = KeyOps::from_str(op) { if ops & op { - return Err(serde::de::Error::custom(alloc::format!( - "duplicate key operation: {}", - op - ))); + return Err(serde::de::Error::duplicate_field(op.as_str())); } else { ops = ops | op; } From 7233b230930f75053e393e1b5169bbc2f4a2fa05 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Thu, 15 Apr 2021 14:50:17 -0700 Subject: [PATCH 028/116] update to use askar-crypto; use CBOR for store key serialization Signed-off-by: Andrew Whitehead --- Cargo.toml | 16 +- src/{ => backends}/any.rs | 61 ++--- src/{ => backends}/db_utils.rs | 57 ++-- src/backends/mod.rs | 17 ++ src/{ => backends}/postgres/mod.rs | 61 +++-- src/{ => backends}/postgres/provision.rs | 0 src/{ => backends}/postgres/test_db.rs | 0 src/{ => backends}/sqlite/mod.rs | 12 +- src/{ => backends}/sqlite/provision.rs | 0 src/error.rs | 12 + src/ffi/store.rs | 3 +- src/lib.rs | 62 ++--- src/protect/hmac_key.rs | 104 ++++++++ src/protect/kdf/argon2.rs | 61 +++++ src/{keys/kdf.rs => protect/kdf/mod.rs} | 39 +-- src/{keys => protect}/mod.rs | 103 ++++---- src/protect/pass_key.rs | 106 ++++++++ src/protect/store_key.rs | 298 +++++++++++++++++++++ src/{keys/wrap.rs => protect/wrap_key.rs} | 131 ++++++---- src/{types.rs => storage/entry.rs} | 12 +- src/storage/key.rs | 145 +++++++++++ src/storage/mod.rs | 9 + src/{ => storage}/options.rs | 10 +- src/{store.rs => storage/types.rs} | 301 +++++++++------------- src/{ => storage}/wql/mod.rs | 0 src/{ => storage}/wql/sql.rs | 0 src/{ => storage}/wql/tags.rs | 1 - 27 files changed, 1158 insertions(+), 463 deletions(-) rename src/{ => backends}/any.rs (85%) rename src/{ => backends}/db_utils.rs (94%) create mode 100644 src/backends/mod.rs rename src/{ => backends}/postgres/mod.rs (93%) rename src/{ => backends}/postgres/provision.rs (100%) rename src/{ => backends}/postgres/test_db.rs (100%) rename src/{ => backends}/sqlite/mod.rs (98%) rename src/{ => backends}/sqlite/provision.rs (100%) create mode 100644 src/protect/hmac_key.rs create mode 100644 src/protect/kdf/argon2.rs rename src/{keys/kdf.rs => protect/kdf/mod.rs} (71%) rename src/{keys => protect}/mod.rs (52%) create mode 100644 src/protect/pass_key.rs create mode 100644 src/protect/store_key.rs rename src/{keys/wrap.rs => protect/wrap_key.rs} (73%) rename src/{types.rs => storage/entry.rs} (98%) create mode 100644 src/storage/key.rs create mode 100644 src/storage/mod.rs rename src/{ => storage}/options.rs (95%) rename src/{store.rs => storage/types.rs} (65%) rename src/{ => storage}/wql/mod.rs (100%) rename src/{ => storage}/wql/sql.rs (100%) rename src/{ => storage}/wql/tags.rs (99%) diff --git a/Cargo.toml b/Cargo.toml index 5ee7b9bd..fc71f0a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["askar-keys"] +members = ["askar-crypto"] [package] name = "aries-askar" @@ -25,7 +25,7 @@ rustdoc-args = ["--cfg", "docsrs"] [features] default = ["all", "ffi", "logger"] -all = ["any", "postgres", "sqlite"] +all = [] # all = ["any", "postgres", "sqlite"] any = [] ffi = ["any", "ffi-support", "logger"] indy_compat = ["sqlx", "sqlx/sqlite"] @@ -35,17 +35,19 @@ postgres = ["sqlx", "sqlx/postgres", "sqlx/tls"] sqlite = ["num_cpus", "sqlx", "sqlx/sqlite"] pg_test = ["postgres"] +[dev-dependencies] +hex-literal = "0.3" + [dependencies] +askar-crypto = { path = "./askar-crypto", features = ["std"] } async-global-executor = { version = "1.4", features = ["async-io"] } async-mutex = "1.4" async-stream = "0.3" async-std = "1.7" # temporary addition to encourage common dependencies with sqlx -askar-keys = { path = "./askar-keys" } -base64 = "0.13" -blake2 = "0.9" blocking = "1.0" bs58 = "0.4" chrono = "0.4" +digest = "0.9" env_logger = { version = "0.7", optional = true } ffi-support = { version = "0.4", optional = true } futures-lite = "1.7" @@ -57,11 +59,9 @@ log = { version = "0.4", optional = true } num_cpus = { version = "1.0", optional = true } once_cell = "1.5" percent-encoding = "2.0" -rand = "0.7" -rmp-serde = "0.14" -rust-argon2 = "0.8" serde = { version = "1.0", features = ["derive"] } serde_bytes = "0.11" +serde_cbor = "0.11" serde_json = "1.0" sha2 = "0.9" url = { version = "2.1", default-features = false } diff --git a/src/any.rs b/src/backends/any.rs similarity index 85% rename from src/any.rs rename to src/backends/any.rs index cb7d98dd..a143191c 100644 --- a/src/any.rs +++ b/src/backends/any.rs @@ -1,15 +1,18 @@ -use super::error::Result; -use super::future::BoxFuture; -use super::keys::{wrap::WrapKeyMethod, PassKey}; -use super::options::IntoOptions; -use super::store::{Backend, ManageBackend, QueryBackend, Scan, Session, Store}; -use super::types::{Entry, EntryKind, EntryOperation, EntryTag, TagFilter}; +use crate::error::Error; +use crate::future::BoxFuture; +use crate::protect::{PassKey, WrapKeyMethod}; + +use crate::storage::{ + entry::{Entry, EntryKind, EntryOperation, EntryTag, TagFilter}, + options::IntoOptions, + types::{Backend, ManageBackend, QueryBackend, Scan, Session, Store}, +}; #[cfg(feature = "postgres")] -use super::postgres::PostgresStore; +use super::backends::postgres::{self, PostgresStore}; #[cfg(feature = "sqlite")] -use super::sqlite::SqliteStore; +use super::backends::sqlite::{self, SqliteStore}; /// A generic `Store` implementation for any supported backend pub type AnyStore = Store; @@ -50,7 +53,7 @@ macro_rules! with_backend { impl Backend for AnyBackend { type Session = AnyQueryBackend; - fn create_profile(&self, name: Option) -> BoxFuture<'_, Result> { + fn create_profile(&self, name: Option) -> BoxFuture<'_, Result> { with_backend!(self, store, store.create_profile(name)) } @@ -58,7 +61,7 @@ impl Backend for AnyBackend { with_backend!(self, store, store.get_profile_name()) } - fn remove_profile(&self, name: String) -> BoxFuture<'_, Result> { + fn remove_profile(&self, name: String) -> BoxFuture<'_, Result> { with_backend!(self, store, store.remove_profile(name)) } @@ -70,7 +73,7 @@ impl Backend for AnyBackend { tag_filter: Option, offset: Option, limit: Option, - ) -> BoxFuture<'_, Result>> { + ) -> BoxFuture<'_, Result, Error>> { with_backend!( self, store, @@ -78,7 +81,7 @@ impl Backend for AnyBackend { ) } - fn session(&self, profile: Option, transaction: bool) -> Result { + fn session(&self, profile: Option, transaction: bool) -> Result { match self { #[cfg(feature = "postgres")] Self::Postgres(store) => { @@ -101,11 +104,11 @@ impl Backend for AnyBackend { &mut self, method: WrapKeyMethod, pass_key: PassKey<'_>, - ) -> BoxFuture<'_, Result<()>> { + ) -> BoxFuture<'_, Result<(), Error>> { with_backend!(self, store, store.rekey_backend(method, pass_key)) } - fn close(&self) -> BoxFuture<'_, Result<()>> { + fn close(&self) -> BoxFuture<'_, Result<(), Error>> { with_backend!(self, store, store.close()) } } @@ -132,7 +135,7 @@ impl QueryBackend for AnyQueryBackend { kind: EntryKind, category: &'q str, tag_filter: Option, - ) -> BoxFuture<'q, Result> { + ) -> BoxFuture<'q, Result> { match self { #[cfg(feature = "postgres")] Self::PostgresSession(session) => session.count(kind, category, tag_filter), @@ -150,7 +153,7 @@ impl QueryBackend for AnyQueryBackend { category: &'q str, name: &'q str, for_update: bool, - ) -> BoxFuture<'q, Result>> { + ) -> BoxFuture<'q, Result, Error>> { match self { #[cfg(feature = "postgres")] Self::PostgresSession(session) => session.fetch(kind, category, name, for_update), @@ -169,7 +172,7 @@ impl QueryBackend for AnyQueryBackend { tag_filter: Option, limit: Option, for_update: bool, - ) -> BoxFuture<'q, Result>> { + ) -> BoxFuture<'q, Result, Error>> { match self { #[cfg(feature = "postgres")] Self::PostgresSession(session) => { @@ -190,7 +193,7 @@ impl QueryBackend for AnyQueryBackend { kind: EntryKind, category: &'q str, tag_filter: Option, - ) -> BoxFuture<'q, Result> { + ) -> BoxFuture<'q, Result> { match self { #[cfg(feature = "postgres")] Self::PostgresSession(session) => session.remove_all(kind, category, tag_filter), @@ -211,7 +214,7 @@ impl QueryBackend for AnyQueryBackend { value: Option<&'q [u8]>, tags: Option<&'q [EntryTag]>, expiry_ms: Option, - ) -> BoxFuture<'q, Result<()>> { + ) -> BoxFuture<'q, Result<(), Error>> { match self { #[cfg(feature = "postgres")] Self::PostgresSession(session) => { @@ -227,7 +230,7 @@ impl QueryBackend for AnyQueryBackend { } } - fn close(self, commit: bool) -> BoxFuture<'static, Result<()>> { + fn close(self, commit: bool) -> BoxFuture<'static, Result<(), Error>> { match self { #[cfg(feature = "postgres")] Self::PostgresSession(session) => Box::pin(session.close(commit)), @@ -248,7 +251,7 @@ impl<'a> ManageBackend<'a> for &'a str { method: Option, pass_key: PassKey<'a>, profile: Option<&'a str>, - ) -> BoxFuture<'a, Result> { + ) -> BoxFuture<'a, Result> { Box::pin(async move { let opts = self.into_options()?; debug!("Open store with options: {:?}", &opts); @@ -256,14 +259,14 @@ impl<'a> ManageBackend<'a> for &'a str { match opts.schema.as_ref() { #[cfg(feature = "postgres")] "postgres" => { - let opts = super::postgres::PostgresStoreOptions::new(opts)?; + let opts = postgres::PostgresStoreOptions::new(opts)?; let mgr = opts.open(method, pass_key, profile).await?; Ok(Store::new(AnyBackend::Postgres(mgr.into_inner()))) } #[cfg(feature = "sqlite")] "sqlite" => { - let opts = super::sqlite::SqliteStoreOptions::new(opts)?; + let opts = sqlite::SqliteStoreOptions::new(opts)?; let mgr = opts.open(method, pass_key, profile).await?; Ok(Store::new(AnyBackend::Sqlite(mgr.into_inner()))) } @@ -279,7 +282,7 @@ impl<'a> ManageBackend<'a> for &'a str { pass_key: PassKey<'a>, profile: Option<&'a str>, recreate: bool, - ) -> BoxFuture<'a, Result> { + ) -> BoxFuture<'a, Result> { Box::pin(async move { let opts = self.into_options()?; debug!("Provision store with options: {:?}", &opts); @@ -287,14 +290,14 @@ impl<'a> ManageBackend<'a> for &'a str { match opts.schema.as_ref() { #[cfg(feature = "postgres")] "postgres" => { - let opts = super::postgres::PostgresStoreOptions::new(opts)?; + let opts = postgres::PostgresStoreOptions::new(opts)?; let mgr = opts.provision(method, pass_key, profile, recreate).await?; Ok(Store::new(AnyBackend::Postgres(mgr.into_inner()))) } #[cfg(feature = "sqlite")] "sqlite" => { - let opts = super::sqlite::SqliteStoreOptions::new(opts)?; + let opts = sqlite::SqliteStoreOptions::new(opts)?; let mgr = opts.provision(method, pass_key, profile, recreate).await?; Ok(Store::new(AnyBackend::Sqlite(mgr.into_inner()))) } @@ -304,7 +307,7 @@ impl<'a> ManageBackend<'a> for &'a str { }) } - fn remove_backend(self) -> BoxFuture<'a, Result> { + fn remove_backend(self) -> BoxFuture<'a, Result> { Box::pin(async move { let opts = self.into_options()?; debug!("Remove store with options: {:?}", &opts); @@ -312,13 +315,13 @@ impl<'a> ManageBackend<'a> for &'a str { match opts.schema.as_ref() { #[cfg(feature = "postgres")] "postgres" => { - let opts = super::postgres::PostgresStoreOptions::new(opts)?; + let opts = postgres::PostgresStoreOptions::new(opts)?; Ok(opts.remove().await?) } #[cfg(feature = "sqlite")] "sqlite" => { - let opts = super::sqlite::SqliteStoreOptions::new(opts)?; + let opts = sqlite::SqliteStoreOptions::new(opts)?; Ok(opts.remove().await?) } diff --git a/src/db_utils.rs b/src/backends/db_utils.rs similarity index 94% rename from src/db_utils.rs rename to src/backends/db_utils.rs index c5cf527a..22c5191f 100644 --- a/src/db_utils.rs +++ b/src/backends/db_utils.rs @@ -7,20 +7,18 @@ use sqlx::{ IntoArguments, Pool, TransactionManager, Type, }; -use crate::EntryTag; - -use super::error::Result; -use super::future::BoxFuture; -use super::keys::{ - store::StoreKey, - wrap::{WrapKey, WrapKeyMethod}, - EntryEncryptor, KeyCache, PassKey, -}; -use super::types::{EncEntryTag, Entry, Expiry, ProfileId, TagFilter}; +use super::entry::{EncEntryTag, Entry, EntryTag, Expiry, ProfileId, TagFilter}; use super::wql::{ sql::TagSqlEncoder, tags::{tag_query, TagQueryEncoder}, }; +use crate::error::Error; +use crate::future::BoxFuture; +use crate::keys::{ + store::StoreKey, + wrap::{WrapKey, WrapKeyMethod}, + EntryEncryptor, KeyCache, PassKey, +}; pub const PAGE_SIZE: usize = 32; @@ -91,7 +89,10 @@ impl DbSession { } } - pub(crate) async fn make_active(&mut self, init_key: I) -> Result> + pub(crate) async fn make_active( + &mut self, + init_key: I, + ) -> Result, Error> where I: for<'a> GetProfileKey<'a, DB>, { @@ -136,7 +137,7 @@ impl DbSession { DbSessionRef::Owned(self) } - pub(crate) async fn close(mut self, commit: bool) -> Result<()> { + pub(crate) async fn close(mut self, commit: bool) -> Result<(), Error> { if self.transaction { if let Some(conn) = self.connection_mut() { if commit { @@ -168,7 +169,7 @@ impl<'q, DB: ExtDatabase> Drop for DbSession { } pub(crate) trait GetProfileKey<'a, DB: Database> { - type Fut: Future)>>; + type Fut: Future)>, Error>; fn call_once( self, conn: &'a mut PoolConnection, @@ -180,7 +181,7 @@ pub(crate) trait GetProfileKey<'a, DB: Database> { impl<'a, DB: Database, F, Fut> GetProfileKey<'a, DB> for F where F: FnOnce(&'a mut PoolConnection, Arc, String) -> Fut, - Fut: Future)>> + 'a, + Fut: Future), Error>> + 'a, { type Fut = Fut; fn call_once( @@ -209,7 +210,7 @@ pub trait ExtDatabase: Database { fn start_transaction( conn: &mut PoolConnection, _nested: bool, - ) -> BoxFuture<'_, std::result::Result<(), SqlxError>> { + ) -> BoxFuture<'_, Result<(), SqlxError>> { ::TransactionManager::begin(conn) } } @@ -252,7 +253,7 @@ impl<'q, DB: ExtDatabase> DbSessionActive<'q, DB> { self.inner.connection_mut().unwrap() } - pub async fn commit(mut self) -> Result<()> { + pub async fn commit(mut self) -> Result<(), Error> { if self.txn_depth > 0 && !self.false_txn { let conn = self.connection_mut(); info!("Commit transaction"); @@ -269,7 +270,7 @@ impl<'q, DB: ExtDatabase> DbSessionActive<'q, DB> { } #[allow(unused)] - pub async fn transaction<'t>(&'t mut self) -> Result> + pub async fn transaction<'t>(&'t mut self) -> Result, Error> where 'q: 't, { @@ -283,7 +284,7 @@ impl<'q, DB: ExtDatabase> DbSessionActive<'q, DB> { }) } - pub async fn as_transaction<'t>(&'t mut self) -> Result> + pub async fn as_transaction<'t>(&'t mut self) -> Result, Error> where 'q: 't, { @@ -317,14 +318,14 @@ impl<'a, DB: ExtDatabase> Drop for DbSessionActive<'a, DB> { } pub(crate) trait RunInTransaction<'a, 'q: 'a, DB: ExtDatabase> { - type Fut: Future>; + type Fut: Future>; fn call_once(self, conn: &'a mut DbSessionActive<'q, DB>) -> Self::Fut; } impl<'a, 'q: 'a, DB: ExtDatabase, F, Fut> RunInTransaction<'a, 'q, DB> for F where F: FnOnce(&'a mut DbSessionActive<'q, DB>) -> Fut, - Fut: Future> + 'a, + Fut: Future> + 'a, { type Fut = Fut; fn call_once(self, conn: &'a mut DbSessionActive<'q, DB>) -> Self::Fut { @@ -452,7 +453,7 @@ pub fn replace_arg_placeholders( buffer } -pub(crate) fn decode_tags(tags: Vec) -> std::result::Result, ()> { +pub(crate) fn decode_tags(tags: Vec) -> Result, ()> { let mut idx = 0; let mut plaintext; let mut name_start; @@ -499,7 +500,7 @@ pub fn decrypt_scan_batch( category: String, enc_rows: Vec, key: &StoreKey, -) -> Result> { +) -> Result, Error> { let mut batch = Vec::with_capacity(enc_rows.len()); for enc_entry in enc_rows { batch.push(decrypt_scan_entry(category.clone(), enc_entry, key)?); @@ -511,7 +512,7 @@ pub fn decrypt_scan_entry( category: String, enc_entry: EncScanEntry, key: &StoreKey, -) -> Result { +) -> Result { let name = key.decrypt_entry_name(enc_entry.name)?; let value = key.decrypt_entry_value(enc_entry.value)?; let tags = if let Some(enc_tags) = enc_entry.tags { @@ -524,7 +525,7 @@ pub fn decrypt_scan_entry( Ok(Entry::new(category.to_string(), name, value, tags)) } -pub fn expiry_timestamp(expire_ms: i64) -> Result { +pub fn expiry_timestamp(expire_ms: i64) -> Result { chrono::Utc::now() .checked_add_signed(chrono::Duration::milliseconds(expire_ms)) .ok_or_else(|| err_msg!(Unexpected, "Invalid expiry timestamp")) @@ -534,7 +535,7 @@ pub fn encode_tag_filter( tag_filter: Option, key: &StoreKey, offset: usize, -) -> Result>)>> { +) -> Result>)>, Error> { if let Some(tag_filter) = tag_filter { let tag_query = tag_query(tag_filter.query)?; let mut enc = TagSqlEncoder::new( @@ -585,7 +586,7 @@ pub fn extend_query<'q, Q: QueryPrepare>( tag_filter: Option<(String, Vec>)>, offset: Option, limit: Option, -) -> Result +) -> Result where i64: for<'e> Encode<'e, Q::DB> + Type, Vec: for<'e> Encode<'e, Q::DB> + Type, @@ -605,14 +606,14 @@ where pub fn init_keys<'a>( method: WrapKeyMethod, pass_key: PassKey<'a>, -) -> Result<(StoreKey, Vec, WrapKey, String)> { +) -> Result<(StoreKey, Vec, WrapKey, String), Error> { let (wrap_key, wrap_key_ref) = method.resolve(pass_key)?; let store_key = StoreKey::new()?; let enc_store_key = encode_store_key(&store_key, &wrap_key)?; Ok((store_key, enc_store_key, wrap_key, wrap_key_ref.into_uri())) } -pub fn encode_store_key(store_key: &StoreKey, wrap_key: &WrapKey) -> Result> { +pub fn encode_store_key(store_key: &StoreKey, wrap_key: &WrapKey) -> Result, Error> { let enc_store_key = store_key.to_string()?; let result = wrap_key.wrap_data(enc_store_key.into())?; Ok(result) diff --git a/src/backends/mod.rs b/src/backends/mod.rs new file mode 100644 index 00000000..9eaffaa7 --- /dev/null +++ b/src/backends/mod.rs @@ -0,0 +1,17 @@ +#[cfg(feature = "any")] +#[cfg_attr(docsrs, doc(cfg(feature = "any")))] +/// Generic backend (from URI) support +pub mod any; + +#[cfg(any(feature = "postgres", feature = "sqlite"))] +mod db_utils; + +#[cfg(feature = "postgres")] +#[cfg_attr(docsrs, doc(cfg(feature = "postgres")))] +/// Postgres database support +pub mod postgres; + +#[cfg(feature = "sqlite")] +#[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))] +/// Sqlite database support +pub mod sqlite; diff --git a/src/postgres/mod.rs b/src/backends/postgres/mod.rs similarity index 93% rename from src/postgres/mod.rs rename to src/backends/postgres/mod.rs index 01b76427..a83209ea 100644 --- a/src/postgres/mod.rs +++ b/src/backends/postgres/mod.rs @@ -15,16 +15,21 @@ use sqlx::{ Row, }; -use super::db_utils::{ - decode_tags, decrypt_scan_batch, encode_store_key, encode_tag_filter, expiry_timestamp, - extend_query, prepare_tags, random_profile_name, replace_arg_placeholders, DbSession, - DbSessionActive, DbSessionRef, EncScanEntry, ExtDatabase, QueryParams, QueryPrepare, PAGE_SIZE, +use crate::{ + error::Error, + future::{unblock, BoxFuture}, + keys::{store::StoreKey, wrap::WrapKeyMethod, EntryEncryptor, KeyCache, PassKey}, + storage::{ + db_utils::{ + decode_tags, decrypt_scan_batch, encode_store_key, encode_tag_filter, expiry_timestamp, + extend_query, prepare_tags, random_profile_name, replace_arg_placeholders, DbSession, + DbSessionActive, DbSessionRef, EncScanEntry, ExtDatabase, QueryParams, QueryPrepare, + PAGE_SIZE, + }, + entry::{EncEntryTag, Entry, EntryKind, EntryOperation, EntryTag, ProfileId, TagFilter}, + types::{Backend, QueryBackend, Scan}, + }, }; -use super::error::Result; -use super::future::{unblock, BoxFuture}; -use super::keys::{store::StoreKey, wrap::WrapKeyMethod, EntryEncryptor, KeyCache, PassKey}; -use super::store::{Backend, QueryBackend, Scan}; -use super::types::{EncEntryTag, Entry, EntryKind, EntryOperation, EntryTag, ProfileId, TagFilter}; const COUNT_QUERY: &'static str = "SELECT COUNT(*) FROM items i WHERE profile_id = $1 AND kind = $2 AND category = $3 @@ -96,7 +101,7 @@ impl PostgresStore { impl Backend for PostgresStore { type Session = DbSession; - fn create_profile(&self, name: Option) -> BoxFuture<'_, Result> { + fn create_profile(&self, name: Option) -> BoxFuture<'_, Result> { let name = name.unwrap_or_else(random_profile_name); Box::pin(async move { let key = StoreKey::new()?; @@ -125,7 +130,7 @@ impl Backend for PostgresStore { self.default_profile.as_str() } - fn remove_profile(&self, name: String) -> BoxFuture<'_, Result> { + fn remove_profile(&self, name: String) -> BoxFuture<'_, Result> { Box::pin(async move { let mut conn = self.conn_pool.acquire().await?; Ok(sqlx::query("DELETE FROM profiles WHERE name=$1") @@ -141,7 +146,7 @@ impl Backend for PostgresStore { &mut self, method: WrapKeyMethod, pass_key: PassKey<'_>, - ) -> BoxFuture<'_, Result<()>> { + ) -> BoxFuture<'_, Result<(), Error>> { let pass_key = pass_key.into_owned(); Box::pin(async move { let (wrap_key, wrap_key_ref) = unblock(move || method.resolve(pass_key)).await?; @@ -197,7 +202,7 @@ impl Backend for PostgresStore { tag_filter: Option, offset: Option, limit: Option, - ) -> BoxFuture<'_, Result>> { + ) -> BoxFuture<'_, Result, Error>> { Box::pin(async move { let session = self.session(profile, false)?; let mut active = session.owned_ref(); @@ -222,7 +227,7 @@ impl Backend for PostgresStore { }) } - fn session(&self, profile: Option, transaction: bool) -> Result { + fn session(&self, profile: Option, transaction: bool) -> Result { Ok(DbSession::new( self.conn_pool.clone(), self.key_cache.clone(), @@ -231,7 +236,7 @@ impl Backend for PostgresStore { )) } - fn close(&self) -> BoxFuture<'_, Result<()>> { + fn close(&self) -> BoxFuture<'_, Result<(), Error>> { Box::pin(async move { self.conn_pool.close().await; Ok(()) @@ -255,7 +260,7 @@ impl QueryBackend for DbSession { kind: EntryKind, category: &'q str, tag_filter: Option, - ) -> BoxFuture<'q, Result> { + ) -> BoxFuture<'q, Result> { let category = StoreKey::prepare_input(category.as_bytes()); Box::pin(async move { @@ -290,7 +295,7 @@ impl QueryBackend for DbSession { category: &str, name: &str, for_update: bool, - ) -> BoxFuture<'_, Result>> { + ) -> BoxFuture<'_, Result, Error>> { let category = category.to_string(); let name = name.to_string(); @@ -352,7 +357,7 @@ impl QueryBackend for DbSession { tag_filter: Option, limit: Option, for_update: bool, - ) -> BoxFuture<'q, Result>> { + ) -> BoxFuture<'q, Result, Error>> { let category = category.to_string(); Box::pin(async move { let for_update = for_update && self.is_transaction(); @@ -387,7 +392,7 @@ impl QueryBackend for DbSession { kind: EntryKind, category: &'q str, tag_filter: Option, - ) -> BoxFuture<'q, Result> { + ) -> BoxFuture<'q, Result> { let category = StoreKey::prepare_input(category.as_bytes()); Box::pin(async move { @@ -432,7 +437,7 @@ impl QueryBackend for DbSession { value: Option<&'q [u8]>, tags: Option<&'q [EntryTag]>, expiry_ms: Option, - ) -> BoxFuture<'q, Result<()>> { + ) -> BoxFuture<'q, Result<(), Error>> { let category = StoreKey::prepare_input(category.as_bytes()); let name = StoreKey::prepare_input(name.as_bytes()); @@ -515,7 +520,7 @@ impl QueryBackend for DbSession { } } - fn close(self, commit: bool) -> BoxFuture<'static, Result<()>> { + fn close(self, commit: bool) -> BoxFuture<'static, Result<(), Error>> { Box::pin(DbSession::close(self, commit)) } } @@ -549,7 +554,9 @@ impl QueryPrepare for PostgresStore { } } -async fn acquire_key(session: &mut DbSession) -> Result<(ProfileId, Arc)> { +async fn acquire_key( + session: &mut DbSession, +) -> Result<(ProfileId, Arc), Error> { if let Some(ret) = session.profile_and_key() { Ok(ret) } else { @@ -560,7 +567,7 @@ async fn acquire_key(session: &mut DbSession) -> Result<(ProfileId, Ar async fn acquire_session<'q>( session: &'q mut DbSession, -) -> Result> { +) -> Result, Error> { session.make_active(&resolve_profile_key).await } @@ -568,7 +575,7 @@ async fn resolve_profile_key( conn: &mut PoolConnection, cache: Arc, profile: String, -) -> Result<(ProfileId, Arc)> { +) -> Result<(ProfileId, Arc), Error> { if let Some((pid, key)) = cache.get_profile(profile.as_str()).await { Ok((pid, key)) } else { @@ -595,7 +602,7 @@ async fn perform_insert<'q>( enc_value: &[u8], enc_tags: Option>, expiry_ms: Option, -) -> Result<()> { +) -> Result<(), Error> { trace!("Insert entry"); let row_id: i64 = sqlx::query_scalar(INSERT_QUERY) .bind(active.profile_id) @@ -627,7 +634,7 @@ async fn perform_remove<'q>( enc_category: &[u8], enc_name: &[u8], ignore_error: bool, -) -> Result<()> { +) -> Result<(), Error> { trace!("Remove entry"); let done = sqlx::query(DELETE_QUERY) .bind(active.profile_id) @@ -653,7 +660,7 @@ fn perform_scan<'q>( offset: Option, limit: Option, for_update: bool, -) -> impl Stream>> + 'q { +) -> impl Stream, Error>> + 'q { try_stream! { let mut params = QueryParams::new(); params.push(profile_id); diff --git a/src/postgres/provision.rs b/src/backends/postgres/provision.rs similarity index 100% rename from src/postgres/provision.rs rename to src/backends/postgres/provision.rs diff --git a/src/postgres/test_db.rs b/src/backends/postgres/test_db.rs similarity index 100% rename from src/postgres/test_db.rs rename to src/backends/postgres/test_db.rs diff --git a/src/sqlite/mod.rs b/src/backends/sqlite/mod.rs similarity index 98% rename from src/sqlite/mod.rs rename to src/backends/sqlite/mod.rs index cadd6847..016285c1 100644 --- a/src/sqlite/mod.rs +++ b/src/backends/sqlite/mod.rs @@ -14,16 +14,16 @@ use sqlx::{ Database, Error as SqlxError, Row, TransactionManager, }; -use super::db_utils::{ +use crate::db_utils::{ decode_tags, decrypt_scan_batch, encode_store_key, encode_tag_filter, expiry_timestamp, extend_query, prepare_tags, random_profile_name, DbSession, DbSessionActive, DbSessionRef, EncScanEntry, ExtDatabase, QueryParams, QueryPrepare, PAGE_SIZE, }; -use super::error::Result; -use super::future::{unblock, BoxFuture}; -use super::keys::{store::StoreKey, wrap::WrapKeyMethod, EntryEncryptor, KeyCache, PassKey}; -use super::store::{Backend, QueryBackend, Scan}; -use super::types::{EncEntryTag, Entry, EntryKind, EntryOperation, EntryTag, ProfileId, TagFilter}; +use crate::error::Error; +use crate::future::{unblock, BoxFuture}; +use crate::keys::{store::StoreKey, wrap::WrapKeyMethod, EntryEncryptor, KeyCache, PassKey}; +use crate::store::{Backend, QueryBackend, Scan}; +use crate::types::{EncEntryTag, Entry, EntryKind, EntryOperation, EntryTag, ProfileId, TagFilter}; mod provision; pub use provision::SqliteStoreOptions; diff --git a/src/sqlite/provision.rs b/src/backends/sqlite/provision.rs similarity index 100% rename from src/sqlite/provision.rs rename to src/backends/sqlite/provision.rs diff --git a/src/error.rs b/src/error.rs index d0231bbd..659cbd5f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -83,6 +83,11 @@ impl Error { self.kind } + /// Accessor for the error message + pub fn message(&self) -> Option<&str> { + self.message.as_ref().map(String::as_str) + } + pub(crate) fn with_cause>>(mut self, err: T) -> Self { self.cause = Some(err.into()); self @@ -154,6 +159,13 @@ impl From for Error { } } +impl From for Error { + fn from(err: askar_crypto::Error) -> Self { + // FIXME map error types + Error::from_opt_msg(ErrorKind::Input, err.message()) + } +} + macro_rules! err_msg { () => { $crate::error::Error::from($crate::error::ErrorKind::Input) diff --git a/src/ffi/store.rs b/src/ffi/store.rs index 9e117465..c30b3b4a 100644 --- a/src/ffi/store.rs +++ b/src/ffi/store.rs @@ -3,7 +3,6 @@ use std::ffi::CString; use std::mem; use std::os::raw::c_char; use std::ptr; -use std::str::FromStr; use std::sync::{ atomic::{AtomicUsize, Ordering}, Arc, @@ -18,11 +17,11 @@ use zeroize::Zeroize; use super::error::set_last_error; use super::{CallbackId, EnsureCallback, ErrorCode}; use crate::any::{AnySession, AnyStore}; +use crate::entry::{Entry, EntryOperation, EntryTagSet, TagFilter}; use crate::error::Result as KvResult; use crate::future::spawn_ok; use crate::keys::{wrap::WrapKeyMethod, KeyAlg, KeyCategory, KeyEntry, PassKey}; use crate::store::{ManageBackend, Scan}; -use crate::types::{Entry, EntryOperation, EntryTagSet, TagFilter}; new_handle_type!(StoreHandle, FFI_STORE_COUNTER); new_handle_type!(SessionHandle, FFI_SESSION_COUNTER); diff --git a/src/lib.rs b/src/lib.rs index 8648abdb..f5f021e4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,10 @@ mod error; pub use self::error::{Error, ErrorKind}; +#[cfg(test)] +#[macro_use] +extern crate hex_literal; + #[macro_use] mod macros; @@ -17,10 +21,9 @@ extern crate log; #[macro_use] extern crate serde; -#[cfg(any(feature = "postgres", feature = "sqlite"))] -mod db_utils; +pub mod backends; -mod didcomm; +pub use askar_crypto as crypto; #[doc(hidden)] pub mod future; @@ -30,46 +33,23 @@ pub mod future; /// Indy wallet compatibility support pub mod indy_compat; -mod options; - -mod random; - -#[cfg(feature = "ffi")] -#[macro_use] -extern crate serde_json; - -#[cfg(feature = "ffi")] -mod ffi; - -#[cfg(feature = "postgres")] -#[cfg_attr(docsrs, doc(cfg(feature = "postgres")))] -/// Postgres database support -pub mod postgres; - -#[macro_use] -pub(crate) mod serde_utils; - -#[cfg(feature = "sqlite")] -#[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))] -/// Sqlite database support -pub mod sqlite; +// #[cfg(feature = "ffi")] +// #[macro_use] +// extern crate serde_json; -#[cfg(feature = "any")] -#[cfg_attr(docsrs, doc(cfg(feature = "any")))] -/// Generic backend (from URI) support -pub mod any; +// #[cfg(feature = "ffi")] +// mod ffi; -mod keys; -pub use self::keys::{ - derive_verkey, verify_signature, - wrap::{generate_raw_wrap_key, WrapKeyMethod}, - KeyAlg, KeyCategory, KeyEntry, KeyParams, PassKey, -}; +pub mod protect; -mod store; -pub use self::store::{Backend, ManageBackend, QueryBackend, Scan, Session, Store}; +pub mod storage; -mod types; -pub use self::types::{Entry, EntryOperation, EntryTag, SecretBytes, TagFilter}; +// #[macro_use] +// pub(crate) mod serde_utils; -mod wql; +// mod keys; +// pub use self::keys::{ +// derive_verkey, verify_signature, +// wrap::{generate_raw_wrap_key, WrapKeyMethod}, +// KeyAlg, KeyCategory, KeyEntry, KeyParams, PassKey, +// }; diff --git a/src/protect/hmac_key.rs b/src/protect/hmac_key.rs new file mode 100644 index 00000000..35d55b09 --- /dev/null +++ b/src/protect/hmac_key.rs @@ -0,0 +1,104 @@ +use std::fmt::{self, Debug, Formatter}; +use std::marker::PhantomData; + +use hmac::{ + digest::{BlockInput, FixedOutput, Reset, Update}, + Hmac, Mac, NewMac, +}; +use serde::{Deserialize, Serialize}; + +use crate::{ + crypto::{ + buffer::ArrayKey, + generic_array::{typenum::Unsigned, ArrayLength, GenericArray}, + repr::KeyGen, + }, + error::Error, +}; + +#[derive(Clone, Deserialize, Serialize)] +#[serde( + transparent, + bound( + deserialize = "ArrayKey: for<'a> Deserialize<'a>", + serialize = "ArrayKey: Serialize" + ) +)] +pub struct HmacKey, H>(ArrayKey, PhantomData); + +impl, H> HmacKey { + pub fn from_slice(key: &[u8]) -> Result { + if key.len() != L::USIZE { + return Err(err_msg!(Encryption, "invalid length for hmac key")); + } + Ok(Self(ArrayKey::from_slice(key), PhantomData)) + } +} + +impl, H> AsRef> for HmacKey { + fn as_ref(&self) -> &GenericArray { + self.0.as_ref() + } +} + +impl, H> Debug for HmacKey { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + if cfg!(test) { + f.debug_tuple("HmacKey").field(&*self).finish() + } else { + f.debug_tuple("HmacKey").field(&"").finish() + } + } +} + +impl, H> PartialEq for HmacKey { + fn eq(&self, other: &Self) -> bool { + self.as_ref() == other.as_ref() + } +} +impl, H> Eq for HmacKey {} + +impl, H> KeyGen for HmacKey { + fn generate() -> Result { + Ok(Self(ArrayKey::random(), PhantomData)) + } +} + +pub trait HmacOutput { + fn hmac_to(&self, message: &[u8], output: &mut [u8]) -> Result<(), Error>; +} + +impl, H> HmacOutput for HmacKey +where + H: BlockInput + Default + Reset + Update + Clone + FixedOutput, +{ + fn hmac_to(&self, message: &[u8], output: &mut [u8]) -> Result<(), Error> { + if output.len() > H::OutputSize::USIZE { + return Err(err_msg!(Encryption, "invalid length for hmac output")); + } + let mut nonce_hmac = + Hmac::::new_varkey(self.0.as_ref()).map_err(|e| err_msg!(Encryption, "{}", e))?; + nonce_hmac.update(&message); + let nonce_long = nonce_hmac.finalize().into_bytes(); + output.copy_from_slice(&nonce_long[..output.len()]); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::crypto::generic_array::typenum::U32; + use sha2::Sha256; + + #[test] + fn hmac_expected() { + let key = HmacKey::::from_slice(&hex!( + "c32ef97a2eed6316ae9b0d3129554358980ee6e0b21b81625229c191a3469f7e" + )) + .unwrap(); + let mut output = [0u8; 12]; + key.hmac_to(b"test message", &mut output).unwrap(); + assert_eq!(output, &hex!("4cecfbf6be721395529be686")[..]); + } +} diff --git a/src/protect/kdf/argon2.rs b/src/protect/kdf/argon2.rs new file mode 100644 index 00000000..063c511f --- /dev/null +++ b/src/protect/kdf/argon2.rs @@ -0,0 +1,61 @@ +use crate::{ + crypto::{ + buffer::ArrayKey, + kdf::argon2::{Argon2, Params, PARAMS_INTERACTIVE, PARAMS_MODERATE}, + repr::{KeyMeta, KeySecretBytes}, + }, + error::Error, + protect::wrap_key::{WrapKey, WrapKeyType}, +}; + +pub use crate::crypto::kdf::argon2::SaltSize; + +pub const LEVEL_INTERACTIVE: &'static str = "13:int"; +pub const LEVEL_MODERATE: &'static str = "13:mod"; + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum Level { + Interactive, + Moderate, +} + +impl Default for Level { + fn default() -> Self { + Self::Moderate + } +} + +impl Level { + pub fn from_str(level: &str) -> Option { + match level { + "int" | LEVEL_INTERACTIVE => Some(Self::Interactive), + "mod" | LEVEL_MODERATE => Some(Self::Moderate), + "" => Some(Self::default()), + _ => None, + } + } + + pub fn as_str(&self) -> &'static str { + match self { + Self::Interactive => LEVEL_INTERACTIVE, + Self::Moderate => LEVEL_MODERATE, + } + } + + pub fn generate_salt(&self) -> ArrayKey { + ArrayKey::random() + } + + fn params(&self) -> &Params { + match self { + Self::Interactive => &PARAMS_INTERACTIVE, + Self::Moderate => &PARAMS_MODERATE, + } + } + + pub fn derive_key(&self, password: &[u8], salt: &[u8]) -> Result { + let mut key = ArrayKey::<::KeySize>::default(); + Argon2::derive_key(password, salt, *self.params(), key.as_mut())?; + Ok(WrapKey::from(WrapKeyType::from_secret_bytes(key.as_ref())?)) + } +} diff --git a/src/keys/kdf.rs b/src/protect/kdf/mod.rs similarity index 71% rename from src/keys/kdf.rs rename to src/protect/kdf/mod.rs index 1791194a..f5cd3ef6 100644 --- a/src/keys/kdf.rs +++ b/src/protect/kdf/mod.rs @@ -1,9 +1,17 @@ -// use super::wrap::PREFIX_KDF; -use crate::error::Result; +use super::wrap_key::{WrapKey, PREFIX_KDF}; +use crate::{ + crypto::{ + buffer::{ArrayKey, HexRepr}, + generic_array::ArrayLength, + }, + error::Error, + storage::options::Options, +}; // use crate::keys::wrap::WrapKey; // use crate::options::Options; -use askar_keys::kdf::argon2::{generate_salt, Level as Argon2Level, SALT_SIZE}; +mod argon2; +use self::argon2::{Level as Argon2Level, SaltSize as Argon2Salt}; pub const METHOD_ARGON2I: &'static str = "argon2i"; @@ -54,37 +62,34 @@ impl KdfMethod { } } - pub fn derive_new_key(&self, password: &str) -> Result<(WrapKey, String)> { + pub fn derive_new_key(&self, password: &str) -> Result<(WrapKey, String), Error> { match self { Self::Argon2i(level) => { - let salt = generate_salt(); - let key = level.derive_key(&salt, password)?; - let detail = format!("?salt={}", bs58::encode(&salt)); + let salt = level.generate_salt(); + let key = level.derive_key(password.as_bytes(), salt.as_ref())?; + let detail = format!("?salt={}", HexRepr(salt.as_ref())); Ok((key.into(), detail)) } } } - pub fn derive_key(&self, password: &str, detail: &str) -> Result { + pub fn derive_key(&self, password: &str, detail: &str) -> Result { match self { Self::Argon2i(level) => { - let salt = parse_salt(detail)?; - let key = level.derive_key(&salt, password)?; + let salt = parse_salt::(detail)?; + let key = level.derive_key(password.as_bytes(), salt.as_ref())?; Ok(key.into()) } } } } -fn parse_salt(detail: &str) -> Result> { +fn parse_salt>(detail: &str) -> Result, Error> { let opts = Options::parse_uri(detail)?; if let Some(salt) = opts.query.get("salt") { - if let Ok(salt) = base58::decode(salt) { - if salt.len() >= SALT_SIZE { - Ok(salt) - } else { - Err(err_msg!(Input, "Invalid salt length")) - } + let mut salt_arr = ArrayKey::::default(); + if hex::decode_to_slice(salt, salt_arr.as_mut()).is_ok() { + Ok(salt_arr) } else { Err(err_msg!(Input, "Invalid salt")) } diff --git a/src/keys/mod.rs b/src/protect/mod.rs similarity index 52% rename from src/keys/mod.rs rename to src/protect/mod.rs index c52b5ebb..a5bc2f85 100644 --- a/src/keys/mod.rs +++ b/src/protect/mod.rs @@ -1,31 +1,30 @@ -use std::collections::HashMap; -use std::sync::Arc; +use std::{collections::HashMap, sync::Arc}; +use async_mutex::Mutex; use zeroize::Zeroize; -pub use askar_keys::PassKey; - pub mod kdf; -pub mod wrap; +mod hmac_key; -/// Derive the (public) verification key for a keypair -pub fn derive_verkey(alg: KeyAlg, seed: &[u8]) -> Result { - match alg { - KeyAlg::Ed25519 => (), - _ => return Err(err_msg!(Unsupported, "Unsupported key algorithm")), - } - let sk = Ed25519KeyPair::from_seed(seed) - .map_err(err_map!(Unexpected, "Error generating keypair"))?; - let pk = sk.public_key().to_string(); - Ok(pk) -} +mod pass_key; +pub use self::pass_key::PassKey; -/// Verify that a message signature is consistent with the signer's key -pub fn verify_signature(signer_vk: &str, data: &[u8], signature: &[u8]) -> Result { - let vk = Ed25519PublicKey::from_str(&signer_vk).map_err(err_map!("Invalid verkey"))?; - vk.key_verify(data, signature, None, None) -} +mod store_key; +pub use self::store_key::StoreKey; + +mod wrap_key; +pub use self::wrap_key::{generate_raw_wrap_key, WrapKey, WrapKeyMethod}; + +use crate::{ + crypto::buffer::SecretBytes, + error::Error, + future::unblock, + storage::{ + entry::{EncEntryTag, EntryTag}, + types::ProfileId, + }, +}; #[derive(Debug)] pub struct KeyCache { @@ -41,7 +40,7 @@ impl KeyCache { } } - pub async fn load_key(&self, ciphertext: Vec) -> Result { + pub async fn load_key(&self, ciphertext: SecretBytes) -> Result { let wrap_key = self.wrap_key.clone(); unblock(move || { let mut data = wrap_key @@ -70,70 +69,70 @@ impl KeyCache { } pub(crate) trait EntryEncryptor { - fn prepare_input(input: &[u8]) -> SecretBytes { - SecretBytes::from(input) + fn prepare_input(input: &[u8]) -> Result { + Ok(SecretBytes::from(input)) } - fn encrypt_entry_category(&self, category: SecretBytes) -> Result>; - fn encrypt_entry_name(&self, name: SecretBytes) -> Result>; - fn encrypt_entry_value(&self, value: SecretBytes) -> Result>; - fn encrypt_entry_tags(&self, tags: Vec) -> Result>; + fn encrypt_entry_category(&self, category: SecretBytes) -> Result; + fn encrypt_entry_name(&self, name: SecretBytes) -> Result; + fn encrypt_entry_value(&self, value: SecretBytes) -> Result; + fn encrypt_entry_tags(&self, tags: Vec) -> Result, Error>; - fn decrypt_entry_category(&self, enc_category: Vec) -> Result; - fn decrypt_entry_name(&self, enc_name: Vec) -> Result; - fn decrypt_entry_value(&self, enc_value: Vec) -> Result; - fn decrypt_entry_tags(&self, enc_tags: Vec) -> Result>; + fn decrypt_entry_category(&self, enc_category: SecretBytes) -> Result; + fn decrypt_entry_name(&self, enc_name: SecretBytes) -> Result; + fn decrypt_entry_value(&self, enc_value: SecretBytes) -> Result; + fn decrypt_entry_tags(&self, enc_tags: Vec) -> Result, Error>; } pub struct NullEncryptor; impl EntryEncryptor for NullEncryptor { - fn encrypt_entry_category(&self, category: SecretBytes) -> Result> { - Ok(category.into_vec()) + fn encrypt_entry_category(&self, category: SecretBytes) -> Result { + Ok(category) } - fn encrypt_entry_name(&self, name: SecretBytes) -> Result> { - Ok(name.into_vec()) + fn encrypt_entry_name(&self, name: SecretBytes) -> Result { + Ok(name) } - fn encrypt_entry_value(&self, value: SecretBytes) -> Result> { - Ok(value.into_vec()) + fn encrypt_entry_value(&self, value: SecretBytes) -> Result { + Ok(value) } - fn encrypt_entry_tags(&self, tags: Vec) -> Result> { + fn encrypt_entry_tags(&self, tags: Vec) -> Result, Error> { Ok(tags .into_iter() .map(|tag| match tag { EntryTag::Encrypted(name, value) => EncEntryTag { - name: name.into_bytes(), - value: value.into_bytes(), + name: name.into_bytes().into(), + value: value.into_bytes().into(), plaintext: false, }, EntryTag::Plaintext(name, value) => EncEntryTag { - name: name.into_bytes(), - value: value.into_bytes(), + name: name.into_bytes().into(), + value: value.into_bytes().into(), plaintext: true, }, }) .collect()) } - fn decrypt_entry_category(&self, enc_category: Vec) -> Result { - Ok(String::from_utf8(enc_category).map_err(err_map!(Encryption))?) + fn decrypt_entry_category(&self, enc_category: SecretBytes) -> Result { + Ok(String::from_utf8(enc_category.into_vec()).map_err(err_map!(Encryption))?) } - fn decrypt_entry_name(&self, enc_name: Vec) -> Result { - Ok(String::from_utf8(enc_name).map_err(err_map!(Encryption))?) + fn decrypt_entry_name(&self, enc_name: SecretBytes) -> Result { + Ok(String::from_utf8(enc_name.into_vec()).map_err(err_map!(Encryption))?) } - fn decrypt_entry_value(&self, enc_value: Vec) -> Result { + fn decrypt_entry_value(&self, enc_value: SecretBytes) -> Result { Ok(enc_value.into()) } - fn decrypt_entry_tags(&self, enc_tags: Vec) -> Result> { + fn decrypt_entry_tags(&self, enc_tags: Vec) -> Result, Error> { Ok(enc_tags.into_iter().try_fold(vec![], |mut acc, tag| { - let name = String::from_utf8(tag.name).map_err(err_map!(Encryption))?; - let value = String::from_utf8(tag.value).map_err(err_map!(Encryption))?; + let name = String::from_utf8(tag.name.into_vec()).map_err(err_map!(Encryption))?; + let value = String::from_utf8(tag.value.into_vec()).map_err(err_map!(Encryption))?; acc.push(if tag.plaintext { EntryTag::Plaintext(name, value) } else { EntryTag::Encrypted(name, value) }); - Result::Ok(acc) + Result::<_, Error>::Ok(acc) })?) } } diff --git a/src/protect/pass_key.rs b/src/protect/pass_key.rs new file mode 100644 index 00000000..f0209501 --- /dev/null +++ b/src/protect/pass_key.rs @@ -0,0 +1,106 @@ +use zeroize::Zeroize; + +use std::{ + borrow::Cow, + fmt::{self, Debug, Formatter}, + mem::ManuallyDrop, + ops::Deref, +}; + +/// A possibly-empty password or key used to derive a store wrap key +#[derive(Clone)] +pub struct PassKey<'a>(Option>); + +impl PassKey<'_> { + /// Create a scoped reference to the passkey + pub fn as_ref(&self) -> PassKey<'_> { + PassKey(Some(Cow::Borrowed(&**self))) + } + + pub fn empty() -> PassKey<'static> { + PassKey(None) + } + + pub(crate) fn is_none(&self) -> bool { + self.0.is_none() + } + + pub(crate) fn into_owned(self) -> PassKey<'static> { + let mut slf = ManuallyDrop::new(self); + let val = slf.0.take(); + PassKey(match val { + None => None, + Some(Cow::Borrowed(s)) => Some(Cow::Owned(s.to_string())), + Some(Cow::Owned(s)) => Some(Cow::Owned(s)), + }) + } +} + +impl Debug for PassKey<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + if cfg!(test) { + f.debug_tuple("PassKey").field(&*self).finish() + } else { + f.debug_tuple("PassKey").field(&"").finish() + } + } +} + +impl Default for PassKey<'_> { + fn default() -> Self { + Self(None) + } +} + +impl Deref for PassKey<'_> { + type Target = str; + + fn deref(&self) -> &str { + match self.0.as_ref() { + None => "", + Some(s) => s.as_ref(), + } + } +} + +impl Drop for PassKey<'_> { + fn drop(&mut self) { + self.zeroize(); + } +} + +impl<'a> From<&'a str> for PassKey<'a> { + fn from(inner: &'a str) -> Self { + Self(Some(Cow::Borrowed(inner))) + } +} + +impl From for PassKey<'_> { + fn from(inner: String) -> Self { + Self(Some(Cow::Owned(inner))) + } +} + +impl<'a> From> for PassKey<'a> { + fn from(inner: Option<&'a str>) -> Self { + Self(inner.map(Cow::Borrowed)) + } +} + +impl<'a, 'b> PartialEq> for PassKey<'a> { + fn eq(&self, other: &PassKey<'b>) -> bool { + &**self == &**other + } +} +impl Eq for PassKey<'_> {} + +impl Zeroize for PassKey<'_> { + fn zeroize(&mut self) { + match self.0.take() { + Some(Cow::Owned(mut s)) => { + s.zeroize(); + } + _ => (), + } + } +} diff --git a/src/protect/store_key.rs b/src/protect/store_key.rs new file mode 100644 index 00000000..65892067 --- /dev/null +++ b/src/protect/store_key.rs @@ -0,0 +1,298 @@ +use serde::{Deserialize, Serialize}; +use sha2::Sha256; + +use super::hmac_key::HmacOutput; +use super::EntryEncryptor; +use crate::{ + crypto::{ + alg::chacha20::{Chacha20Key, C20P}, + buffer::{ArrayKey, ResizeBuffer, SecretBytes, WriteBuffer}, + encrypt::{KeyAeadInPlace, KeyAeadMeta}, + generic_array::typenum::{Unsigned, U32}, + repr::{KeyGen, KeyMeta, KeySecretBytes}, + }, + error::Error, + storage::entry::{EncEntryTag, EntryTag}, +}; + +pub type StoreKey = StoreKeyImpl, super::hmac_key::HmacKey>; + +/// A store key combining the keys required to encrypt +/// and decrypt storage records +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(bound( + deserialize = "Key: for<'a> Deserialize<'a>, HmacKey: for<'a> Deserialize<'a>", + serialize = "Key: Serialize, HmacKey: Serialize" +))] +pub struct StoreKeyImpl { + pub category_key: Key, + pub name_key: Key, + pub value_key: Key, + pub item_hmac_key: HmacKey, + pub tag_name_key: Key, + pub tag_value_key: Key, + pub tags_hmac_key: HmacKey, +} + +impl StoreKeyImpl +where + Key: KeyGen, + HmacKey: KeyGen, +{ + pub fn new() -> Result { + Ok(Self { + category_key: KeyGen::generate()?, + name_key: KeyGen::generate()?, + value_key: KeyGen::generate()?, + item_hmac_key: KeyGen::generate()?, + tag_name_key: KeyGen::generate()?, + tag_value_key: KeyGen::generate()?, + tags_hmac_key: KeyGen::generate()?, + }) + } +} + +impl StoreKeyImpl +where + Key: Serialize + for<'de> Deserialize<'de>, + HmacKey: Serialize + for<'de> Deserialize<'de>, +{ + pub fn to_bytes(&self) -> Result { + serde_cbor::to_vec(self) + .map(SecretBytes::from) + .map_err(err_map!(Unexpected, "Error serializing store key")) + } + + pub fn from_slice(input: &[u8]) -> Result { + serde_cbor::from_slice(input).map_err(err_map!(Unsupported, "Invalid store key")) + } +} + +impl StoreKeyImpl +where + Key: KeyGen + KeyMeta + KeyAeadInPlace + KeyAeadMeta + KeySecretBytes, + HmacKey: KeyGen + HmacOutput, +{ + pub fn encrypted_size(len: usize) -> usize { + len + Key::NonceSize::USIZE + Key::TagSize::USIZE + } + + /// Encrypt a value with a predictable nonce, making it searchable + pub fn encrypt_searchable( + mut buffer: SecretBytes, + enc_key: &Key, + hmac_key: &HmacKey, + ) -> Result { + let mut nonce = ArrayKey::::default(); + hmac_key.hmac_to(buffer.as_ref(), nonce.as_mut())?; + enc_key.encrypt_in_place(&mut buffer, nonce.as_ref(), &[])?; + buffer.buffer_insert_slice(0, nonce.as_ref())?; + Ok(buffer) + } + + pub fn encrypt(mut buffer: SecretBytes, enc_key: &Key) -> Result { + let nonce = ArrayKey::::random(); + enc_key.encrypt_in_place(&mut buffer, nonce.as_ref(), &[])?; + buffer.buffer_insert_slice(0, nonce.as_ref())?; + Ok(buffer) + } + + pub fn decrypt(mut buffer: SecretBytes, enc_key: &Key) -> Result { + let nonce_len = Key::nonce_length(); + if buffer.len() < nonce_len { + return Err(err_msg!(Encryption, "invalid encrypted value")); + } + let nonce = ArrayKey::::from_slice(&buffer.as_ref()[..nonce_len]); + buffer.buffer_remove(0..nonce_len)?; + enc_key.decrypt_in_place(&mut buffer, nonce.as_ref(), &[])?; + Ok(buffer) + } + + pub fn encrypt_tag_name(&self, name: SecretBytes) -> Result { + Self::encrypt_searchable(name, &self.tag_name_key, &self.tags_hmac_key) + } + + pub fn encrypt_tag_value(&self, value: SecretBytes) -> Result { + Self::encrypt_searchable(value, &self.tag_value_key, &self.tags_hmac_key) + } + + pub fn decrypt_tag_name(&self, enc_tag_name: SecretBytes) -> Result { + Self::decrypt(enc_tag_name, &self.tag_name_key) + } + + pub fn decrypt_tag_value(&self, enc_tag_value: SecretBytes) -> Result { + Self::decrypt(enc_tag_value, &self.tag_value_key) + } +} + +impl PartialEq for StoreKeyImpl { + fn eq(&self, other: &Self) -> bool { + self.category_key == other.category_key + && self.name_key == other.name_key + && self.value_key == other.value_key + && self.item_hmac_key == other.item_hmac_key + && self.tag_name_key == other.tag_name_key + && self.tag_value_key == other.tag_value_key + && self.tags_hmac_key == other.tags_hmac_key + } +} +impl Eq for StoreKeyImpl {} + +impl EntryEncryptor for StoreKeyImpl +where + Key: KeyGen + KeyMeta + KeyAeadInPlace + KeyAeadMeta + KeySecretBytes, + HmacKey: KeyGen + HmacOutput, +{ + fn prepare_input(input: &[u8]) -> Result { + let mut buf = SecretBytes::with_capacity(Self::encrypted_size(input.len())); + buf.write_slice(input)?; + Ok(buf) + } + + fn encrypt_entry_category(&self, category: SecretBytes) -> Result { + Self::encrypt_searchable(category, &self.category_key, &self.item_hmac_key) + } + + fn encrypt_entry_name(&self, name: SecretBytes) -> Result { + Self::encrypt_searchable(name, &self.name_key, &self.item_hmac_key) + } + + fn encrypt_entry_value(&self, value: SecretBytes) -> Result { + let value_key = Key::generate()?; + let value = Self::encrypt(value, &value_key)?; + let key_input = value_key.with_secret_bytes(|sk| Self::prepare_input(sk.unwrap()))?; + let mut result = Self::encrypt(key_input, &self.value_key)?; + result.write_slice(value.as_ref())?; + Ok(result) + } + + fn decrypt_entry_category(&self, enc_category: SecretBytes) -> Result { + decode_utf8(Self::decrypt(enc_category, &self.category_key)?.into_vec()) + } + + fn decrypt_entry_name(&self, enc_name: SecretBytes) -> Result { + decode_utf8(Self::decrypt(enc_name, &self.name_key)?.into_vec()) + } + + fn decrypt_entry_value(&self, mut enc_value: SecretBytes) -> Result { + let enc_key_size = Self::encrypted_size(Key::KeySize::USIZE); + if enc_value.len() < enc_key_size + Self::encrypted_size(0) { + return Err(err_msg!( + Encryption, + "Buffer is too short to represent an encrypted value", + )); + } + let value = SecretBytes::from_slice(&enc_value[enc_key_size..]); + enc_value.buffer_resize(enc_key_size)?; + let value_key = + Key::from_secret_bytes(Self::decrypt(enc_value, &self.value_key)?.as_ref())?; + Self::decrypt(value, &value_key) + } + + fn encrypt_entry_tags(&self, tags: Vec) -> Result, Error> { + tags.into_iter() + .map(|tag| match tag { + EntryTag::Plaintext(name, value) => { + let name = self.encrypt_tag_name(name.into())?; + Ok(EncEntryTag { + name, + value: value.into_bytes().into(), + plaintext: true, + }) + } + EntryTag::Encrypted(name, value) => { + let name = self.encrypt_tag_name(name.into())?; + let value = self.encrypt_tag_value(value.into())?; + Ok(EncEntryTag { + name, + value, + plaintext: false, + }) + } + }) + .collect() + } + + fn decrypt_entry_tags(&self, enc_tags: Vec) -> Result, Error> { + enc_tags.into_iter().try_fold(vec![], |mut acc, tag| { + let name = decode_utf8(self.decrypt_tag_name(tag.name)?.into_vec())?; + acc.push(if tag.plaintext { + let value = decode_utf8(tag.value.into_vec())?; + EntryTag::Plaintext(name, value) + } else { + let value = decode_utf8(self.decrypt_tag_value(tag.value)?.into_vec())?; + EntryTag::Encrypted(name, value) + }); + Result::Ok(acc) + }) + } +} + +#[inline(always)] +fn decode_utf8(value: Vec) -> Result { + String::from_utf8(value).map_err(err_map!(Encryption)) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::storage::entry::Entry; + + #[test] + fn encrypt_entry_round_trip() { + let key = StoreKey::new().unwrap(); + let test_record = Entry::new( + "category", + "name", + "value", + Some(vec![ + EntryTag::Plaintext("plain".to_string(), "tag".to_string()), + EntryTag::Encrypted("enctag".to_string(), "envtagval".to_string()), + ]), + ); + let enc_category = key + .encrypt_entry_category(test_record.category.clone().into()) + .unwrap(); + let enc_name = key + .encrypt_entry_name(test_record.name.clone().into()) + .unwrap(); + let enc_value = key + .encrypt_entry_value(test_record.value.clone().into()) + .unwrap(); + let enc_tags = key + .encrypt_entry_tags(test_record.tags.clone().unwrap()) + .unwrap(); + assert_ne!(test_record.category.as_bytes(), enc_category.as_ref()); + assert_ne!(test_record.name.as_bytes(), enc_name.as_ref()); + assert_ne!(test_record.value, enc_value); + + let cmp_record = Entry::new( + key.decrypt_entry_category(enc_category).unwrap(), + key.decrypt_entry_name(enc_name).unwrap(), + key.decrypt_entry_value(enc_value).unwrap(), + Some(key.decrypt_entry_tags(enc_tags).unwrap()), + ); + assert_eq!(test_record, cmp_record); + } + + // #[test] + // fn store_key_searchable() { + // const NONCE_SIZE: usize = 12; + // let input = SecretBytes::from(&b"hello"[..]); + // let key = EncKey::::random_key(); + // let hmac_key = EncKey::::random(); + // let enc1 = encrypt_searchable::(input.clone(), &key, &hmac_key).unwrap(); + // let enc2 = encrypt_searchable::(input.clone(), &key, &hmac_key).unwrap(); + // assert_eq!(&enc1[0..NONCE_SIZE], &enc2[0..NONCE_SIZE]); + // let dec = ChaChaEncrypt::decrypt(enc1, &key).unwrap(); + // assert_eq!(dec, input); + // } + + #[test] + fn serialize_round_trip() { + let key = StoreKey::new().unwrap(); + let key_cbor = serde_cbor::to_vec(&key).unwrap(); + let key_cmp = serde_cbor::from_slice(&key_cbor).unwrap(); + assert_eq!(key, key_cmp); + } +} diff --git a/src/keys/wrap.rs b/src/protect/wrap_key.rs similarity index 73% rename from src/keys/wrap.rs rename to src/protect/wrap_key.rs index 0c63cf50..37b9e8ec 100644 --- a/src/keys/wrap.rs +++ b/src/protect/wrap_key.rs @@ -1,79 +1,97 @@ use super::kdf::KdfMethod; -use askar_keys::{ - encrypt::{aead::ChaChaEncrypt, SymEncrypt, SymEncryptKey}, - PassKey, SecretBytes, +use super::pass_key::PassKey; +use crate::{ + crypto::{ + alg::chacha20::{Chacha20Key, C20P}, + buffer::{ArrayKey, ResizeBuffer, SecretBytes}, + encrypt::{KeyAeadInPlace, KeyAeadMeta}, + repr::{KeyGen, KeyMeta, KeySecretBytes}, + }, + error::Error, }; pub const PREFIX_KDF: &'static str = "kdf"; pub const PREFIX_RAW: &'static str = "raw"; pub const PREFIX_NONE: &'static str = "none"; -pub type WrapKeyAlg = ChaChaEncrypt; -pub type WrapKeyData = ::Key; -pub const WRAP_KEY_SIZE: usize = ::Key::SIZE; +pub type WrapKeyType = Chacha20Key; + +type WrapKeyNonce = ArrayKey<::NonceSize>; /// Create a new raw wrap key for a store -pub fn generate_raw_wrap_key(seed: Option<&[u8]>) -> Result> { +pub fn generate_raw_wrap_key(seed: Option<&[u8]>) -> Result, Error> { let key = if let Some(seed) = seed { - WrapKey::from(WrapKeyData::from_seed(seed)?) + WrapKey::from(WrapKeyType::from_seed(seed)?) } else { - WrapKey::from(WrapKeyData::random_key()) + WrapKey::from(WrapKeyType::generate()?) }; - Ok(key.to_opt_string().unwrap().into()) + Ok(key.to_passkey()) } -pub fn parse_raw_key(raw_key: &str) -> Result { - let key = base58::decode(raw_key) +pub fn parse_raw_key(raw_key: &str) -> Result { + let mut key = ArrayKey::<::KeySize>::default(); + let key_len = bs58::decode(raw_key) + .into(key.as_mut()) .map_err(|_| err_msg!(Input, "Error parsing raw key as base58 value"))?; - if key.len() != WRAP_KEY_SIZE { + if key_len != key.len() { Err(err_msg!(Input, "Incorrect length for encoded raw key")) } else { - Ok(WrapKey::from(WrapKeyData::from_slice(key))) + Ok(WrapKey::from(WrapKeyType::from_secret_bytes(key.as_ref())?)) } } #[derive(Clone, Debug)] -pub struct WrapKey(pub Option); +pub struct WrapKey(pub Option); impl WrapKey { pub const fn empty() -> Self { Self(None) } - pub fn random() -> Result { - Ok(Self(Some(WrapKeyData::random()))) + pub fn random() -> Result { + Ok(Self(Some(WrapKeyType::generate()?))) } pub fn is_empty(&self) -> bool { self.0.is_none() } - pub fn prepare_input(&self, input: &[u8]) -> SecretBytes { - WrapKeyAlg::prepare_input(input) - } - - pub fn wrap_data(&self, data: SecretBytes) -> Result> { + pub fn wrap_data(&self, mut data: SecretBytes) -> Result { match &self.0 { - Some(key) => Ok(WrapKeyAlg::encrypt(data, key, None)?), - None => Ok(data.into_vec()), + Some(key) => { + let nonce = WrapKeyNonce::random(); + key.encrypt_in_place(&mut data, nonce.as_ref(), &[])?; + data.buffer_insert_slice(0, nonce.as_ref())?; + Ok(data) + } + None => Ok(data), } } - pub fn unwrap_data(&self, ciphertext: Vec) -> Result { + pub fn unwrap_data(&self, mut ciphertext: SecretBytes) -> Result { match &self.0 { - Some(key) => Ok(WrapKeyAlg::decrypt(ciphertext, key)?), - None => Ok(ciphertext.into()), + Some(key) => { + let nonce = WrapKeyNonce::from_slice(&ciphertext.as_ref()[..WrapKeyNonce::SIZE]); + ciphertext.buffer_remove(0..WrapKeyNonce::SIZE)?; + key.decrypt_in_place(&mut ciphertext, nonce.as_ref(), &[])?; + Ok(ciphertext) + } + None => Ok(ciphertext), } } - pub fn to_opt_string(&self) -> Option { - self.0.as_ref().map(|key| base58::encode(key.as_slice())) + pub fn to_passkey(&self) -> PassKey<'static> { + if let Some(key) = self.0.as_ref() { + PassKey::from(key.with_secret_bytes(|sk| bs58::encode(sk.unwrap()).into_string())) + } else { + PassKey::empty() + } } } -impl From for WrapKey { - fn from(data: WrapKeyData) -> Self { +impl From for WrapKey { + fn from(data: WrapKeyType) -> Self { Self(Some(data)) } } @@ -92,7 +110,7 @@ pub enum WrapKeyMethod { } impl WrapKeyMethod { - pub(crate) fn parse_uri(uri: &str) -> Result { + pub(crate) fn parse_uri(uri: &str) -> Result { let mut prefix_and_detail = uri.splitn(2, ':'); let prefix = prefix_and_detail.next().unwrap_or_default(); // let detail = prefix_and_detail.next().unwrap_or_default(); @@ -107,7 +125,10 @@ impl WrapKeyMethod { } } - pub(crate) fn resolve(&self, pass_key: PassKey<'_>) -> Result<(WrapKey, WrapKeyReference)> { + pub(crate) fn resolve( + &self, + pass_key: PassKey<'_>, + ) -> Result<(WrapKey, WrapKeyReference), Error> { match self { // Self::CreateManagedKey(_mgr_ref) => unimplemented!(), // Self::ExistingManagedKey(String) => unimplemented!(), @@ -148,7 +169,7 @@ pub enum WrapKeyReference { } impl WrapKeyReference { - pub fn parse_uri(uri: &str) -> Result { + pub fn parse_uri(uri: &str) -> Result { let mut prefix_and_detail = uri.splitn(2, ':'); let prefix = prefix_and_detail.next().unwrap_or_default(); match prefix { @@ -189,7 +210,7 @@ impl WrapKeyReference { } } - pub fn resolve(&self, pass_key: PassKey<'_>) -> Result { + pub fn resolve(&self, pass_key: PassKey<'_>) -> Result { match self { // Self::ManagedKey(_key_ref) => unimplemented!(), Self::DeriveKey(method, detail) => { @@ -242,9 +263,9 @@ mod tests { .expect("Error deriving new key"); assert!(!key.is_empty()); let wrapped = key - .wrap_data(key.prepare_input(input)) + .wrap_data((&input[..]).into()) .expect("Error wrapping input"); - assert_ne!(wrapped, input); + assert_ne!(wrapped, &input[..]); let unwrapped = key.unwrap_data(wrapped).expect("Error unwrapping data"); assert_eq!(unwrapped, &input[..]); let key_uri = key_ref.into_uri(); @@ -254,32 +275,36 @@ mod tests { #[test] fn derived_key_unwrap_expected() { let input = b"test data"; - let wrapped: &[u8] = &[ - 194, 156, 102, 253, 229, 11, 48, 184, 160, 119, 218, 30, 169, 188, 244, 223, 235, 95, - 171, 234, 18, 5, 9, 115, 174, 208, 232, 37, 31, 32, 250, 216, 32, 92, 253, 45, 236, - ]; + let wrapped = SecretBytes::from_slice( + &[ + 194, 156, 102, 253, 229, 11, 48, 184, 160, 119, 218, 30, 169, 188, 244, 223, 235, + 95, 171, 234, 18, 5, 9, 115, 174, 208, 232, 37, 31, 32, 250, 216, 32, 92, 253, 45, + 236, + ][..], + ); let pass = PassKey::from("pass"); let key_ref = WrapKeyReference::parse_uri("kdf:argon2i:13:mod?salt=MR6B1jrReV2JioaizEaRo6") .expect("Error parsing derived key ref"); let key = key_ref.resolve(pass).expect("Error deriving existing key"); - let unwrapped = key - .unwrap_data(wrapped.to_vec()) - .expect("Error unwrapping data"); + let unwrapped = key.unwrap_data(wrapped).expect("Error unwrapping data"); assert_eq!(unwrapped, &input[..]); } #[test] fn derived_key_check_bad_password() { - let wrapped: &[u8] = &[ - 194, 156, 102, 253, 229, 11, 48, 184, 160, 119, 218, 30, 169, 188, 244, 223, 235, 95, - 171, 234, 18, 5, 9, 115, 174, 208, 232, 37, 31, 32, 250, 216, 32, 92, 253, 45, 236, - ]; + let wrapped = SecretBytes::from_slice( + &[ + 194, 156, 102, 253, 229, 11, 48, 184, 160, 119, 218, 30, 169, 188, 244, 223, 235, + 95, 171, 234, 18, 5, 9, 115, 174, 208, 232, 37, 31, 32, 250, 216, 32, 92, 253, 45, + 236, + ][..], + ); let key_ref = WrapKeyReference::parse_uri("kdf:argon2i:13:mod?salt=MR6B1jrReV2JioaizEaRo6") .expect("Error parsing derived key ref"); let check_bad_pass = key_ref .resolve("not my pass".into()) .expect("Error deriving comparison key"); - let unwrapped_err = check_bad_pass.unwrap_data(wrapped.to_vec()); + let unwrapped_err = check_bad_pass.unwrap_data(wrapped); assert_eq!(unwrapped_err.is_err(), true); } @@ -293,9 +318,9 @@ mod tests { .expect("Error resolving raw key"); assert_eq!(key.is_empty(), false); let wrapped = key - .wrap_data(key.prepare_input(input)) + .wrap_data((&input[..]).into()) .expect("Error wrapping input"); - assert_ne!(wrapped, input); + assert_ne!(wrapped, &input[..]); // round trip the key reference let key_uri = key_ref.into_uri(); @@ -320,9 +345,9 @@ mod tests { .expect("Error resolving unprotected"); assert_eq!(key.is_empty(), true); let wrapped = key - .wrap_data(key.prepare_input(input)) + .wrap_data((&input[..]).into()) .expect("Error wrapping unprotected"); - assert_eq!(wrapped, input); + assert_eq!(wrapped, &input[..]); // round trip the key reference let key_uri = key_ref.into_uri(); diff --git a/src/types.rs b/src/storage/entry.rs similarity index 98% rename from src/types.rs rename to src/storage/entry.rs index 2be2c196..f61ad487 100644 --- a/src/types.rs +++ b/src/storage/entry.rs @@ -1,6 +1,4 @@ use std::fmt::{self, Debug, Formatter}; -use std::mem; -use std::ops::Deref; use std::str::FromStr; use serde::{ @@ -10,12 +8,8 @@ use serde::{ }; use zeroize::Zeroize; -use super::error::Error; use super::wql; - -pub type ProfileId = i64; - -pub type Expiry = chrono::DateTime; +use crate::{crypto::buffer::SecretBytes, error::Error}; pub(crate) fn sorted_tags(tags: &Vec) -> Option> { if tags.len() > 0 { @@ -305,8 +299,8 @@ impl Serialize for EntryTagSet { #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) struct EncEntryTag { - pub name: Vec, - pub value: Vec, + pub name: SecretBytes, + pub value: SecretBytes, pub plaintext: bool, } diff --git a/src/storage/key.rs b/src/storage/key.rs new file mode 100644 index 00000000..80c7b1a4 --- /dev/null +++ b/src/storage/key.rs @@ -0,0 +1,145 @@ +use std::{ + fmt::{self, Display, Formatter}, + str::FromStr, +}; + +use zeroize::Zeroize; + +pub use crate::crypto::KeyAlg; + +use super::entry::{sorted_tags, EntryTag}; +use crate::{crypto::buffer::SecretBytes, error::Error}; + +/// Categories of keys supported by the default KMS +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Zeroize)] +pub enum KeyCategory { + /// A private key or keypair + PrivateKey, + /// A public key + PublicKey, +} + +impl KeyCategory { + /// Get a reference to a string representing the `KeyCategory` + pub fn as_str(&self) -> &str { + match self { + Self::PrivateKey => "private", + Self::PublicKey => "public", + } + } + + /// Convert the `KeyCategory` into an owned string + pub fn to_string(&self) -> String { + self.as_str().to_string() + } +} + +// serde_as_str_impl!(KeyCategory); + +impl AsRef for KeyCategory { + fn as_ref(&self) -> &str { + self.as_str() + } +} + +impl FromStr for KeyCategory { + type Err = Error; + + fn from_str(s: &str) -> Result { + Ok(match s { + "private" => Self::PrivateKey, + "public" => Self::PublicKey, + _ => return Err(err_msg!("Unknown key category: {}", s)), + }) + } +} + +impl Display for KeyCategory { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +/// Parameters defining a stored key +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub struct KeyParams { + /// Associated key metadata + #[serde(default, rename = "meta", skip_serializing_if = "Option::is_none")] + pub metadata: Option, + + /// An optional external reference for the key + #[serde(default, rename = "ref", skip_serializing_if = "Option::is_none")] + pub reference: Option, + + /// The associated key data in JWK format + #[serde(default, skip_serializing_if = "Option::is_none")] + pub data: Option, +} + +impl KeyParams { + pub(crate) fn to_vec(&self) -> Result, Error> { + serde_json::to_vec(self) + .map_err(|e| err_msg!(Unexpected, "Error serializing key params: {}", e)) + } + + pub(crate) fn from_slice(params: &[u8]) -> Result { + let result = serde_json::from_slice(params) + .map_err(|e| err_msg!(Unexpected, "Error deserializing key params: {}", e)); + result + } +} + +/// A stored key entry +#[derive(Clone, Debug, Eq)] +pub struct KeyEntry { + /// The category of the key entry (public or private) + pub category: KeyCategory, + /// The key entry identifier + pub ident: String, + /// The parameters defining the key + pub params: KeyParams, + /// Tags associated with the key entry record + pub tags: Option>, +} + +impl KeyEntry { + /// Determine if a key entry refers to a local or external key + pub fn is_local(&self) -> bool { + self.params.reference.is_none() + } + + /// Fetch the associated key data + pub fn key_data(&self) -> Option<&[u8]> { + self.params.data.as_ref().map(AsRef::as_ref) + } + + pub(crate) fn sorted_tags(&self) -> Option> { + self.tags.as_ref().and_then(sorted_tags) + } +} + +impl PartialEq for KeyEntry { + fn eq(&self, rhs: &Self) -> bool { + self.category == rhs.category + && self.ident == rhs.ident + && self.params == rhs.params + && self.sorted_tags() == rhs.sorted_tags() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn key_params_roundtrip() { + let params = KeyParams { + metadata: Some("meta".to_string()), + reference: None, + data: Some(SecretBytes::from(vec![0, 0, 0, 0])), + }; + let enc_params = params.to_vec().unwrap(); + let p2 = KeyParams::from_slice(&enc_params).unwrap(); + assert_eq!(p2, params); + } +} diff --git a/src/storage/mod.rs b/src/storage/mod.rs new file mode 100644 index 00000000..9bf0d99c --- /dev/null +++ b/src/storage/mod.rs @@ -0,0 +1,9 @@ +pub mod entry; + +pub mod key; + +pub mod options; + +pub mod types; + +mod wql; diff --git a/src/options.rs b/src/storage/options.rs similarity index 95% rename from src/options.rs rename to src/storage/options.rs index 609bf082..2f9cd9c3 100644 --- a/src/options.rs +++ b/src/storage/options.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use percent_encoding::{percent_decode_str, utf8_percent_encode, NON_ALPHANUMERIC}; -use super::error::Result; +use crate::error::Error; #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct Options<'a> { @@ -17,7 +17,7 @@ pub struct Options<'a> { } impl<'a> Options<'a> { - pub fn parse_uri(uri: &str) -> Result> { + pub fn parse_uri(uri: &str) -> Result, Error> { let mut fragment_and_remain = uri.splitn(2, '#'); let uri = fragment_and_remain.next().unwrap_or_default(); let fragment = percent_decode(fragment_and_remain.next().unwrap_or_default()); @@ -127,17 +127,17 @@ fn percent_encode_into(result: &mut String, s: &str) { } pub trait IntoOptions<'a> { - fn into_options(self) -> Result>; + fn into_options(self) -> Result, Error>; } impl<'a> IntoOptions<'a> for Options<'a> { - fn into_options(self) -> Result> { + fn into_options(self) -> Result, Error> { Ok(self) } } impl<'a> IntoOptions<'a> for &'a str { - fn into_options(self) -> Result> { + fn into_options(self) -> Result, Error> { Options::parse_uri(self) } } diff --git a/src/store.rs b/src/storage/types.rs similarity index 65% rename from src/store.rs rename to src/storage/types.rs index 195fdb59..03a262ed 100644 --- a/src/store.rs +++ b/src/storage/types.rs @@ -1,23 +1,26 @@ -use std::convert::TryInto; -use std::fmt::{self, Debug, Formatter}; -use std::future::Future; -use std::pin::Pin; -use std::str::FromStr; -use std::sync::Arc; +use std::{ + fmt::{self, Debug, Formatter}, + pin::Pin, + str::FromStr, + sync::Arc, +}; use futures_lite::stream::{Stream, StreamExt}; use zeroize::Zeroizing; -use super::didcomm::pack::{pack_message, unpack_message, KeyLookup}; -use super::error::Result; -use super::future::BoxFuture; -use super::keys::{ - alg::ed25519::{Ed25519KeyPair, Ed25519PublicKey}, - caps::KeyCapSign, - wrap::WrapKeyMethod, - AnyPrivateKey, KeyAlg, KeyCategory, KeyEntry, KeyParams, PassKey, +use super::{ + entry::{Entry, EntryKind, EntryOperation, EntryTag, TagFilter}, + key::{KeyCategory, KeyEntry, KeyParams}, +}; +use crate::{ + error::Error, + future::BoxFuture, + protect::{PassKey, WrapKeyMethod}, }; -use super::types::{Entry, EntryKind, EntryOperation, EntryTag, TagFilter}; + +pub type ProfileId = i64; + +pub type Expiry = chrono::DateTime; /// Represents a generic backend implementation pub trait Backend: Send + Sync { @@ -25,13 +28,13 @@ pub trait Backend: Send + Sync { type Session: QueryBackend; /// Create a new profile - fn create_profile(&self, name: Option) -> BoxFuture<'_, Result>; + fn create_profile(&self, name: Option) -> BoxFuture<'_, Result>; /// Get the name of the active profile fn get_profile_name(&self) -> &str; /// Remove an existing profile - fn remove_profile(&self, name: String) -> BoxFuture<'_, Result>; + fn remove_profile(&self, name: String) -> BoxFuture<'_, Result>; /// Create a [`Scan`] against the store fn scan( @@ -42,20 +45,20 @@ pub trait Backend: Send + Sync { tag_filter: Option, offset: Option, limit: Option, - ) -> BoxFuture<'_, Result>>; + ) -> BoxFuture<'_, Result, Error>>; /// Create a new session against the store - fn session(&self, profile: Option, transaction: bool) -> Result; + fn session(&self, profile: Option, transaction: bool) -> Result; /// Replace the wrapping key of the store fn rekey_backend( &mut self, method: WrapKeyMethod, key: PassKey<'_>, - ) -> BoxFuture<'_, Result<()>>; + ) -> BoxFuture<'_, Result<(), Error>>; /// Close the store instance - fn close(&self) -> BoxFuture<'_, Result<()>>; + fn close(&self) -> BoxFuture<'_, Result<(), Error>>; } /// Create, open, or remove a generic backend implementation @@ -69,7 +72,7 @@ pub trait ManageBackend<'a> { method: Option, pass_key: PassKey<'a>, profile: Option<&'a str>, - ) -> BoxFuture<'a, Result>; + ) -> BoxFuture<'a, Result>; /// Provision a new store fn provision_backend( @@ -78,10 +81,10 @@ pub trait ManageBackend<'a> { pass_key: PassKey<'a>, profile: Option<&'a str>, recreate: bool, - ) -> BoxFuture<'a, Result>; + ) -> BoxFuture<'a, Result>; /// Remove an existing store - fn remove_backend(self) -> BoxFuture<'a, Result>; + fn remove_backend(self) -> BoxFuture<'a, Result>; } /// Query from a generic backend implementation @@ -92,7 +95,7 @@ pub trait QueryBackend: Send { kind: EntryKind, category: &'q str, tag_filter: Option, - ) -> BoxFuture<'q, Result>; + ) -> BoxFuture<'q, Result>; /// Fetch a single record from the store by category and name fn fetch<'q>( @@ -101,7 +104,7 @@ pub trait QueryBackend: Send { category: &'q str, name: &'q str, for_update: bool, - ) -> BoxFuture<'q, Result>>; + ) -> BoxFuture<'q, Result, Error>>; /// Fetch all matching records from the store fn fetch_all<'q>( @@ -111,7 +114,7 @@ pub trait QueryBackend: Send { tag_filter: Option, limit: Option, for_update: bool, - ) -> BoxFuture<'q, Result>>; + ) -> BoxFuture<'q, Result, Error>>; /// Remove all matching records from the store fn remove_all<'q>( @@ -119,7 +122,7 @@ pub trait QueryBackend: Send { kind: EntryKind, category: &'q str, tag_filter: Option, - ) -> BoxFuture<'q, Result>; + ) -> BoxFuture<'q, Result>; /// Insert or replace a record in the store fn update<'q>( @@ -131,10 +134,10 @@ pub trait QueryBackend: Send { value: Option<&'q [u8]>, tags: Option<&'q [EntryTag]>, expiry_ms: Option, - ) -> BoxFuture<'q, Result<()>>; + ) -> BoxFuture<'q, Result<(), Error>>; /// Close the current store session - fn close(self, commit: bool) -> BoxFuture<'static, Result<()>>; + fn close(self, commit: bool) -> BoxFuture<'static, Result<(), Error>>; } #[derive(Debug)] @@ -164,17 +167,21 @@ impl Store { } /// Replace the wrapping key on a store - pub async fn rekey(&mut self, method: WrapKeyMethod, pass_key: PassKey<'_>) -> Result<()> { + pub async fn rekey( + &mut self, + method: WrapKeyMethod, + pass_key: PassKey<'_>, + ) -> Result<(), Error> { Ok(self.0.rekey_backend(method, pass_key).await?) } /// Create a new profile with the given profile name - pub async fn create_profile(&self, name: Option) -> Result { + pub async fn create_profile(&self, name: Option) -> Result { Ok(self.0.create_profile(name).await?) } /// Remove an existing profile with the given profile name - pub async fn remove_profile(&self, name: String) -> Result { + pub async fn remove_profile(&self, name: String) -> Result { Ok(self.0.remove_profile(name).await?) } @@ -188,7 +195,7 @@ impl Store { tag_filter: Option, offset: Option, limit: Option, - ) -> Result> { + ) -> Result, Error> { Ok(self .0 .scan( @@ -203,22 +210,22 @@ impl Store { } /// Create a new session against the store - pub async fn session(&self, profile: Option) -> Result> { + pub async fn session(&self, profile: Option) -> Result, Error> { // FIXME - add 'immediate' flag Ok(Session::new(self.0.session(profile, false)?)) } /// Create a new transaction session against the store - pub async fn transaction(&self, profile: Option) -> Result> { + pub async fn transaction(&self, profile: Option) -> Result, Error> { Ok(Session::new(self.0.session(profile, true)?)) } /// Close the store instance, waiting for any shutdown procedures to complete. - pub async fn close(self) -> Result<()> { + pub async fn close(self) -> Result<(), Error> { Ok(self.0.close().await?) } - pub(crate) async fn arc_close(self: Arc) -> Result<()> { + pub(crate) async fn arc_close(self: Arc) -> Result<(), Error> { Ok(self.0.close().await?) } } @@ -235,7 +242,11 @@ impl Session { impl Session { /// Count the number of entries for a given record category - pub async fn count(&mut self, category: &str, tag_filter: Option) -> Result { + pub async fn count( + &mut self, + category: &str, + tag_filter: Option, + ) -> Result { Ok(self.0.count(EntryKind::Item, category, tag_filter).await?) } @@ -248,7 +259,7 @@ impl Session { category: &str, name: &str, for_update: bool, - ) -> Result> { + ) -> Result, Error> { Ok(self .0 .fetch(EntryKind::Item, category, name, for_update) @@ -266,7 +277,7 @@ impl Session { tag_filter: Option, limit: Option, for_update: bool, - ) -> Result> { + ) -> Result, Error> { Ok(self .0 .fetch_all(EntryKind::Item, category, tag_filter, limit, for_update) @@ -281,7 +292,7 @@ impl Session { value: &[u8], tags: Option<&[EntryTag]>, expiry_ms: Option, - ) -> Result<()> { + ) -> Result<(), Error> { Ok(self .0 .update( @@ -297,7 +308,7 @@ impl Session { } /// Remove a record from the store - pub async fn remove(&mut self, category: &str, name: &str) -> Result<()> { + pub async fn remove(&mut self, category: &str, name: &str) -> Result<(), Error> { Ok(self .0 .update( @@ -320,7 +331,7 @@ impl Session { value: &[u8], tags: Option<&[EntryTag]>, expiry_ms: Option, - ) -> Result<()> { + ) -> Result<(), Error> { Ok(self .0 .update( @@ -340,7 +351,7 @@ impl Session { &mut self, category: &str, tag_filter: Option, - ) -> Result { + ) -> Result { Ok(self .0 .remove_all(EntryKind::Item, category, tag_filter) @@ -359,7 +370,7 @@ impl Session { value: Option<&[u8]>, tags: Option<&[EntryTag]>, expiry_ms: Option, - ) -> Result<()> { + ) -> Result<(), Error> { Ok(self .0 .update( @@ -374,57 +385,57 @@ impl Session { .await?) } - /// Create a new keypair in the store - pub async fn create_keypair( - &mut self, - alg: KeyAlg, - metadata: Option<&str>, - seed: Option<&[u8]>, - tags: Option<&[EntryTag]>, - // backend - ) -> Result { - match alg { - KeyAlg::Ed25519 => (), - _ => return Err(err_msg!(Unsupported, "Unsupported key algorithm")), - } - - let keypair = match seed { - None => Ed25519KeyPair::generate(), - Some(s) => Ed25519KeyPair::from_seed(s), - } - .map_err(err_map!(Unexpected, "Error generating keypair"))?; - let pk = keypair.public_key(); - - let category = KeyCategory::PrivateKey; - let ident = pk.to_string(); - - let params = KeyParams { - alg, - metadata: metadata.map(str::to_string), - reference: None, - data: Some(keypair.to_bytes()), - }; - let value = Zeroizing::new(params.to_vec()?); - - self.0 - .update( - EntryKind::Key, - EntryOperation::Insert, - category.as_str(), - &ident, - Some(value.as_slice()), - tags.clone(), - None, - ) - .await?; - - Ok(KeyEntry { - category, - ident, - params, - tags: tags.map(|t| t.to_vec()), - }) - } + // /// Create a new keypair in the store + // pub async fn create_keypair( + // &mut self, + // alg: KeyAlg, + // metadata: Option<&str>, + // seed: Option<&[u8]>, + // tags: Option<&[EntryTag]>, + // // backend + // ) -> Result { + // match alg { + // KeyAlg::Ed25519 => (), + // _ => return Err(err_msg!(Unsupported, "Unsupported key algorithm")), + // } + + // let keypair = match seed { + // None => Ed25519KeyPair::generate(), + // Some(s) => Ed25519KeyPair::from_seed(s), + // } + // .map_err(err_map!(Unexpected, "Error generating keypair"))?; + // let pk = keypair.public_key(); + + // let category = KeyCategory::PrivateKey; + // let ident = pk.to_string(); + + // let params = KeyParams { + // alg, + // metadata: metadata.map(str::to_string), + // reference: None, + // data: Some(keypair.to_bytes()), + // }; + // let value = Zeroizing::new(params.to_vec()?); + + // self.0 + // .update( + // EntryKind::Key, + // EntryOperation::Insert, + // category.as_str(), + // &ident, + // Some(value.as_slice()), + // tags.clone(), + // None, + // ) + // .await?; + + // Ok(KeyEntry { + // category, + // ident, + // params, + // tags: tags.map(|t| t.to_vec()), + // }) + // } /// Fetch an existing key from the store /// @@ -435,7 +446,7 @@ impl Session { category: KeyCategory, ident: &str, for_update: bool, - ) -> Result> { + ) -> Result, Error> { Ok( if let Some(row) = self .0 @@ -456,7 +467,7 @@ impl Session { } /// Remove an existing key from the store - pub async fn remove_key(&mut self, category: KeyCategory, ident: &str) -> Result<()> { + pub async fn remove_key(&mut self, category: KeyCategory, ident: &str) -> Result<(), Error> { self.0 .update( EntryKind::Key, @@ -477,7 +488,7 @@ impl Session { ident: &str, metadata: Option<&str>, tags: Option<&[EntryTag]>, - ) -> Result<()> { + ) -> Result<(), Error> { let row = self .0 .fetch(EntryKind::Key, category.as_str(), &ident, true) @@ -503,107 +514,27 @@ impl Session { Ok(()) } - /// Sign a message using an existing private key in the store identified by `key_ident` - pub async fn sign_message(&mut self, key_ident: &str, data: &[u8]) -> Result> { - if let Some(key) = self - .fetch_key(KeyCategory::PrivateKey, key_ident, false) - .await? - { - let sk: AnyPrivateKey = key.try_into()?; - sk.key_sign(&data, None, None) - .map_err(|e| err_msg!(Unexpected, "Signature error: {}", e)) - } else { - return Err(err_msg!(NotFound, "Unknown key")); - } - } - - /// Pack a message using an existing private key in the store identified by `key_ident` - /// - /// This uses the `pack` algorithm defined for DIDComm v1 - pub async fn pack_message( - &mut self, - recipient_vks: impl IntoIterator, - from_key_ident: Option<&str>, - data: &[u8], - ) -> Result> { - let sign_key = if let Some(ident) = from_key_ident { - let sk = self - .fetch_key(KeyCategory::PrivateKey, ident, false) - .await? - .ok_or_else(|| err_msg!(NotFound, "Unknown sender key"))?; - let data = sk - .key_data() - .ok_or_else(|| err_msg!(NotFound, "Missing private key data"))?; - Some(Ed25519KeyPair::from_bytes(data)?) - } else { - None - }; - let vks = recipient_vks - .into_iter() - .map(|vk| { - let vk = Ed25519PublicKey::from_str(&vk) - .map_err(err_map!("Invalid recipient verkey"))?; - Ok(vk) - }) - .collect::>>()?; - Ok(pack_message(data, vks, sign_key).map_err(err_map!("Error packing message"))?) - } - - /// Unpack a DIDComm v1 message, automatically looking up any associated keypairs - pub async fn unpack_message( - &mut self, - data: &[u8], - ) -> Result<(Vec, Ed25519PublicKey, Option)> { - match unpack_message(data, self).await { - Ok((message, recip, sender)) => Ok((message, recip, sender)), - Err(err) => Err(err_msg!(Unexpected, "Error unpacking message").with_cause(err)), - } - } - /// Commit the pending transaction - pub async fn commit(self) -> Result<()> { + pub async fn commit(self) -> Result<(), Error> { Ok(self.0.close(true).await?) } /// Roll back the pending transaction - pub async fn rollback(self) -> Result<()> { + pub async fn rollback(self) -> Result<(), Error> { Ok(self.0.close(false).await?) } } -impl<'a, Q: QueryBackend> KeyLookup<'a> for &'a mut Session { - fn find<'f>( - self, - keys: &'f Vec, - ) -> std::pin::Pin> + Send + 'f>> - where - 'a: 'f, - { - Box::pin(async move { - for (idx, key) in keys.into_iter().enumerate() { - let ident = key.to_string(); - if let Ok(Some(key)) = self.fetch_key(KeyCategory::PrivateKey, &ident, false).await - { - if let Some(Ok(sk)) = key.key_data().map(Ed25519KeyPair::from_bytes) { - return Some((idx, sk)); - } - } - } - return None; - }) - } -} - /// An active record scan of a store backend pub struct Scan<'s, T> { - stream: Option>> + Send + 's>>>, + stream: Option, Error>> + Send + 's>>>, page_size: usize, } impl<'s, T> Scan<'s, T> { pub(crate) fn new(stream: S, page_size: usize) -> Self where - S: Stream>> + Send + 's, + S: Stream, Error>> + Send + 's, { Self { stream: Some(stream.boxed()), @@ -612,7 +543,7 @@ impl<'s, T> Scan<'s, T> { } /// Fetch the next set of result rows - pub async fn fetch_next(&mut self) -> Result>> { + pub async fn fetch_next(&mut self) -> Result>, Error> { if let Some(mut s) = self.stream.take() { match s.try_next().await? { Some(val) => { diff --git a/src/wql/mod.rs b/src/storage/wql/mod.rs similarity index 100% rename from src/wql/mod.rs rename to src/storage/wql/mod.rs diff --git a/src/wql/sql.rs b/src/storage/wql/sql.rs similarity index 100% rename from src/wql/sql.rs rename to src/storage/wql/sql.rs diff --git a/src/wql/tags.rs b/src/storage/wql/tags.rs similarity index 99% rename from src/wql/tags.rs rename to src/storage/wql/tags.rs index c28c793f..d4ecd3a3 100644 --- a/src/wql/tags.rs +++ b/src/storage/wql/tags.rs @@ -288,7 +288,6 @@ mod tests { use itertools::Itertools; use super::*; - use crate::wql::Query; struct TestEncoder {} From c9606bbf7e36fb81f6506b58e4b921f09d34358e Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Thu, 15 Apr 2021 15:57:04 -0700 Subject: [PATCH 029/116] update sqlite backend Signed-off-by: Andrew Whitehead --- Cargo.toml | 2 +- src/{backends => backend}/any.rs | 4 +- src/{backends => backend}/db_utils.rs | 34 +-- src/{backends => backend}/mod.rs | 2 + src/{backends => backend}/postgres/mod.rs | 0 .../postgres/provision.rs | 0 src/{backends => backend}/postgres/test_db.rs | 0 src/{backends => backend}/sqlite/mod.rs | 84 ++++--- src/{backends => backend}/sqlite/provision.rs | 35 ++- src/ffi/mod.rs | 95 +++---- src/ffi/store.rs | 234 ++---------------- src/lib.rs | 32 ++- src/protect/hmac_key.rs | 1 + src/protect/mod.rs | 53 ++-- src/protect/pass_key.rs | 1 + src/protect/store_key.rs | 49 ++-- src/protect/wrap_key.rs | 41 ++- src/storage/entry.rs | 4 +- src/storage/mod.rs | 4 +- src/storage/types.rs | 4 - tests/backends.rs | 64 ++--- tests/utils/mod.rs | 222 ++++++++--------- 22 files changed, 370 insertions(+), 595 deletions(-) rename src/{backends => backend}/any.rs (98%) rename src/{backends => backend}/db_utils.rs (96%) rename src/{backends => backend}/mod.rs (90%) rename src/{backends => backend}/postgres/mod.rs (100%) rename src/{backends => backend}/postgres/provision.rs (100%) rename src/{backends => backend}/postgres/test_db.rs (100%) rename src/{backends => backend}/sqlite/mod.rs (91%) rename src/{backends => backend}/sqlite/provision.rs (93%) diff --git a/Cargo.toml b/Cargo.toml index fc71f0a7..c11de114 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ rustdoc-args = ["--cfg", "docsrs"] [features] default = ["all", "ffi", "logger"] -all = [] # all = ["any", "postgres", "sqlite"] +all = ["any", "sqlite"] # "postgres" any = [] ffi = ["any", "ffi-support", "logger"] indy_compat = ["sqlx", "sqlx/sqlite"] diff --git a/src/backends/any.rs b/src/backend/any.rs similarity index 98% rename from src/backends/any.rs rename to src/backend/any.rs index a143191c..9169b43f 100644 --- a/src/backends/any.rs +++ b/src/backend/any.rs @@ -9,10 +9,10 @@ use crate::storage::{ }; #[cfg(feature = "postgres")] -use super::backends::postgres::{self, PostgresStore}; +use super::postgres::{self, PostgresStore}; #[cfg(feature = "sqlite")] -use super::backends::sqlite::{self, SqliteStore}; +use super::sqlite::{self, SqliteStore}; /// A generic `Store` implementation for any supported backend pub type AnyStore = Store; diff --git a/src/backends/db_utils.rs b/src/backend/db_utils.rs similarity index 96% rename from src/backends/db_utils.rs rename to src/backend/db_utils.rs index 22c5191f..eb45d9b3 100644 --- a/src/backends/db_utils.rs +++ b/src/backend/db_utils.rs @@ -7,21 +7,23 @@ use sqlx::{ IntoArguments, Pool, TransactionManager, Type, }; -use super::entry::{EncEntryTag, Entry, EntryTag, Expiry, ProfileId, TagFilter}; -use super::wql::{ - sql::TagSqlEncoder, - tags::{tag_query, TagQueryEncoder}, -}; -use crate::error::Error; -use crate::future::BoxFuture; -use crate::keys::{ - store::StoreKey, - wrap::{WrapKey, WrapKeyMethod}, - EntryEncryptor, KeyCache, PassKey, +use crate::{ + error::Error, + future::BoxFuture, + protect::{EntryEncryptor, KeyCache, PassKey, ProfileId, StoreKey, WrapKey, WrapKeyMethod}, + storage::{ + entry::{EncEntryTag, Entry, EntryTag, TagFilter}, + wql::{ + sql::TagSqlEncoder, + tags::{tag_query, TagQueryEncoder}, + }, + }, }; pub const PAGE_SIZE: usize = 32; +pub type Expiry = chrono::DateTime; + #[derive(Debug)] pub(crate) enum DbSessionState { Active { conn: PoolConnection }, @@ -169,7 +171,7 @@ impl<'q, DB: ExtDatabase> Drop for DbSession { } pub(crate) trait GetProfileKey<'a, DB: Database> { - type Fut: Future)>, Error>; + type Fut: Future), Error>>; fn call_once( self, conn: &'a mut PoolConnection, @@ -555,7 +557,7 @@ pub fn encode_tag_filter( // convert a slice of tags into a Vec, when ensuring there is // adequate space in the allocations to reuse them during encryption -pub fn prepare_tags(tags: &[EntryTag]) -> Vec { +pub fn prepare_tags(tags: &[EntryTag]) -> Result, Error> { let mut result = Vec::with_capacity(tags.len()); for tag in tags { result.push(match tag { @@ -577,7 +579,7 @@ pub fn prepare_tags(tags: &[EntryTag]) -> Vec { ), }); } - result + Ok(result) } pub fn extend_query<'q, Q: QueryPrepare>( @@ -614,9 +616,7 @@ pub fn init_keys<'a>( } pub fn encode_store_key(store_key: &StoreKey, wrap_key: &WrapKey) -> Result, Error> { - let enc_store_key = store_key.to_string()?; - let result = wrap_key.wrap_data(enc_store_key.into())?; - Ok(result) + wrap_key.wrap_data(store_key.to_bytes()?) } #[inline] diff --git a/src/backends/mod.rs b/src/backend/mod.rs similarity index 90% rename from src/backends/mod.rs rename to src/backend/mod.rs index 9eaffaa7..7721cae8 100644 --- a/src/backends/mod.rs +++ b/src/backend/mod.rs @@ -1,3 +1,5 @@ +//! Storage backends supported by aries-askar + #[cfg(feature = "any")] #[cfg_attr(docsrs, doc(cfg(feature = "any")))] /// Generic backend (from URI) support diff --git a/src/backends/postgres/mod.rs b/src/backend/postgres/mod.rs similarity index 100% rename from src/backends/postgres/mod.rs rename to src/backend/postgres/mod.rs diff --git a/src/backends/postgres/provision.rs b/src/backend/postgres/provision.rs similarity index 100% rename from src/backends/postgres/provision.rs rename to src/backend/postgres/provision.rs diff --git a/src/backends/postgres/test_db.rs b/src/backend/postgres/test_db.rs similarity index 100% rename from src/backends/postgres/test_db.rs rename to src/backend/postgres/test_db.rs diff --git a/src/backends/sqlite/mod.rs b/src/backend/sqlite/mod.rs similarity index 91% rename from src/backends/sqlite/mod.rs rename to src/backend/sqlite/mod.rs index 016285c1..cacda82a 100644 --- a/src/backends/sqlite/mod.rs +++ b/src/backend/sqlite/mod.rs @@ -14,16 +14,18 @@ use sqlx::{ Database, Error as SqlxError, Row, TransactionManager, }; -use crate::db_utils::{ - decode_tags, decrypt_scan_batch, encode_store_key, encode_tag_filter, expiry_timestamp, - extend_query, prepare_tags, random_profile_name, DbSession, DbSessionActive, DbSessionRef, - EncScanEntry, ExtDatabase, QueryParams, QueryPrepare, PAGE_SIZE, +use crate::{ + backend::db_utils::{ + decode_tags, decrypt_scan_batch, encode_store_key, encode_tag_filter, expiry_timestamp, + extend_query, prepare_tags, random_profile_name, DbSession, DbSessionActive, DbSessionRef, + EncScanEntry, ExtDatabase, QueryParams, QueryPrepare, PAGE_SIZE, + }, + error::Error, + future::{unblock, BoxFuture}, + protect::{EntryEncryptor, KeyCache, PassKey, ProfileId, StoreKey, WrapKeyMethod}, + storage::entry::{EncEntryTag, Entry, EntryKind, EntryOperation, EntryTag, TagFilter}, + storage::types::{Backend, QueryBackend, Scan}, }; -use crate::error::Error; -use crate::future::{unblock, BoxFuture}; -use crate::keys::{store::StoreKey, wrap::WrapKeyMethod, EntryEncryptor, KeyCache, PassKey}; -use crate::store::{Backend, QueryBackend, Scan}; -use crate::types::{EncEntryTag, Entry, EntryKind, EntryOperation, EntryTag, ProfileId, TagFilter}; mod provision; pub use provision::SqliteStoreOptions; @@ -92,16 +94,16 @@ impl QueryPrepare for SqliteStore { impl Backend for SqliteStore { type Session = DbSession; - fn create_profile(&self, name: Option) -> BoxFuture<'_, Result> { + fn create_profile(&self, name: Option) -> BoxFuture<'_, Result> { let name = name.unwrap_or_else(random_profile_name); Box::pin(async move { let key = StoreKey::new()?; - let enc_key = key.to_string()?; + let enc_key = key.to_bytes()?; let mut conn = self.conn_pool.acquire().await?; let done = sqlx::query("INSERT OR IGNORE INTO profiles (name, store_key) VALUES (?1, ?2)") .bind(&name) - .bind(enc_key) + .bind(enc_key.into_vec()) .execute(&mut conn) .await?; if done.rows_affected() == 0 { @@ -118,7 +120,7 @@ impl Backend for SqliteStore { self.default_profile.as_str() } - fn remove_profile(&self, name: String) -> BoxFuture<'_, Result> { + fn remove_profile(&self, name: String) -> BoxFuture<'_, Result> { Box::pin(async move { let mut conn = self.conn_pool.acquire().await?; Ok(sqlx::query("DELETE FROM profiles WHERE name=?") @@ -134,7 +136,7 @@ impl Backend for SqliteStore { &mut self, method: WrapKeyMethod, pass_key: PassKey<'_>, - ) -> BoxFuture<'_, Result<()>> { + ) -> BoxFuture<'_, Result<(), Error>> { let pass_key = pass_key.into_owned(); Box::pin(async move { let (wrap_key, wrap_key_ref) = unblock(move || method.resolve(pass_key)).await?; @@ -190,7 +192,7 @@ impl Backend for SqliteStore { tag_filter: Option, offset: Option, limit: Option, - ) -> BoxFuture<'_, Result>> { + ) -> BoxFuture<'_, Result, Error>> { Box::pin(async move { let session = self.session(profile, false)?; let mut active = session.owned_ref(); @@ -214,7 +216,7 @@ impl Backend for SqliteStore { }) } - fn session(&self, profile: Option, transaction: bool) -> Result { + fn session(&self, profile: Option, transaction: bool) -> Result { Ok(DbSession::new( self.conn_pool.clone(), self.key_cache.clone(), @@ -223,7 +225,7 @@ impl Backend for SqliteStore { )) } - fn close(&self) -> BoxFuture<'_, Result<()>> { + fn close(&self) -> BoxFuture<'_, Result<(), Error>> { Box::pin(async move { self.conn_pool.close().await; Ok(()) @@ -237,7 +239,7 @@ impl QueryBackend for DbSession { kind: EntryKind, category: &'q str, tag_filter: Option, - ) -> BoxFuture<'q, Result> { + ) -> BoxFuture<'q, Result> { let category = StoreKey::prepare_input(category.as_bytes()); Box::pin(async move { @@ -248,7 +250,7 @@ impl QueryBackend for DbSession { let (enc_category, tag_filter) = unblock({ let params_len = params.len() + 1; // plus category move || { - Result::Ok(( + Result::<_, Error>::Ok(( key.encrypt_entry_category(category)?, encode_tag_filter::(tag_filter, &key, params_len)?, )) @@ -272,7 +274,7 @@ impl QueryBackend for DbSession { category: &str, name: &str, _for_update: bool, - ) -> BoxFuture<'_, Result>> { + ) -> BoxFuture<'_, Result, Error>> { let category = category.to_string(); let name = name.to_string(); @@ -283,7 +285,7 @@ impl QueryBackend for DbSession { let category = StoreKey::prepare_input(category.as_bytes()); let name = StoreKey::prepare_input(name.as_bytes()); move || { - Result::Ok(( + Result::<_, Error>::Ok(( key.encrypt_entry_category(category)?, key.encrypt_entry_name(name)?, )) @@ -306,7 +308,7 @@ impl QueryBackend for DbSession { let enc_tags = decode_tags(tags) .map_err(|_| err_msg!(Unexpected, "Error decoding entry tags"))?; let tags = Some(key.decrypt_entry_tags(enc_tags)?); - Result::Ok((value, tags)) + Result::<_, Error>::Ok((value, tags)) }) .await?; Ok(Some(Entry::new(category, name, value, tags))) @@ -323,7 +325,7 @@ impl QueryBackend for DbSession { tag_filter: Option, limit: Option, _for_update: bool, - ) -> BoxFuture<'q, Result>> { + ) -> BoxFuture<'q, Result, Error>> { let category = category.to_string(); Box::pin(async move { let mut active = self.borrow_mut(); @@ -356,7 +358,7 @@ impl QueryBackend for DbSession { kind: EntryKind, category: &'q str, tag_filter: Option, - ) -> BoxFuture<'q, Result> { + ) -> BoxFuture<'q, Result> { let category = StoreKey::prepare_input(category.as_bytes()); Box::pin(async move { @@ -367,7 +369,7 @@ impl QueryBackend for DbSession { let (enc_category, tag_filter) = unblock({ let params_len = params.len() + 1; // plus category move || { - Result::Ok(( + Result::<_, Error>::Ok(( key.encrypt_entry_category(category)?, encode_tag_filter::(tag_filter, &key, params_len)?, )) @@ -396,7 +398,7 @@ impl QueryBackend for DbSession { value: Option<&'q [u8]>, tags: Option<&'q [EntryTag]>, expiry_ms: Option, - ) -> BoxFuture<'q, Result<()>> { + ) -> BoxFuture<'q, Result<(), Error>> { let category = StoreKey::prepare_input(category.as_bytes()); let name = StoreKey::prepare_input(name.as_bytes()); @@ -407,11 +409,13 @@ impl QueryBackend for DbSession { Box::pin(async move { let (_, key) = acquire_key(&mut *self).await?; let (enc_category, enc_name, enc_value, enc_tags) = unblock(move || { - Result::Ok(( + Result::<_, Error>::Ok(( key.encrypt_entry_category(category)?, key.encrypt_entry_name(name)?, key.encrypt_entry_value(value)?, - tags.map(|t| key.encrypt_entry_tags(t)).transpose()?, + tags.transpose()? + .map(|t| key.encrypt_entry_tags(t)) + .transpose()?, )) }) .await?; @@ -438,7 +442,7 @@ impl QueryBackend for DbSession { EntryOperation::Remove => Box::pin(async move { let (_, key) = acquire_key(&mut *self).await?; let (enc_category, enc_name) = unblock(move || { - Result::Ok(( + Result::<_, Error>::Ok(( key.encrypt_entry_category(category)?, key.encrypt_entry_name(name)?, )) @@ -450,7 +454,7 @@ impl QueryBackend for DbSession { } } - fn close(self, commit: bool) -> BoxFuture<'static, Result<()>> { + fn close(self, commit: bool) -> BoxFuture<'static, Result<(), Error>> { Box::pin(DbSession::close(self, commit)) } } @@ -474,7 +478,7 @@ impl ExtDatabase for Sqlite { } } -async fn acquire_key(session: &mut DbSession) -> Result<(ProfileId, Arc)> { +async fn acquire_key(session: &mut DbSession) -> Result<(ProfileId, Arc), Error> { if let Some(ret) = session.profile_and_key() { Ok(ret) } else { @@ -485,7 +489,7 @@ async fn acquire_key(session: &mut DbSession) -> Result<(ProfileId, Arc< async fn acquire_session<'q>( session: &'q mut DbSession, -) -> Result> { +) -> Result, Error> { session.make_active(&resolve_profile_key).await } @@ -493,7 +497,7 @@ async fn resolve_profile_key( conn: &mut PoolConnection, cache: Arc, profile: String, -) -> Result<(ProfileId, Arc)> { +) -> Result<(ProfileId, Arc), Error> { if let Some((pid, key)) = cache.get_profile(profile.as_str()).await { Ok((pid, key)) } else { @@ -520,7 +524,7 @@ async fn perform_insert<'q>( enc_value: &[u8], enc_tags: Option>, expiry_ms: Option, -) -> Result<()> { +) -> Result<(), Error> { trace!("Insert entry"); let done = sqlx::query(INSERT_QUERY) .bind(active.profile_id) @@ -555,7 +559,7 @@ async fn perform_remove<'q>( enc_category: &[u8], enc_name: &[u8], ignore_error: bool, -) -> Result<()> { +) -> Result<(), Error> { trace!("Remove entry"); let done = sqlx::query(DELETE_QUERY) .bind(active.profile_id) @@ -580,7 +584,7 @@ fn perform_scan<'q>( tag_filter: Option, offset: Option, limit: Option, -) -> impl Stream>> + 'q { +) -> impl Stream, Error>> + 'q { try_stream! { let mut params = QueryParams::new(); params.push(profile_id); @@ -590,7 +594,7 @@ fn perform_scan<'q>( let category = StoreKey::prepare_input(category.as_bytes()); let params_len = params.len() + 1; // plus category move || { - Result::Ok(( + Result::<_, Error>::Ok(( key.encrypt_entry_category(category)?, encode_tag_filter::(tag_filter, &key, params_len)? )) @@ -624,9 +628,9 @@ fn perform_scan<'q>( #[cfg(test)] mod tests { use super::*; - use crate::db_utils::replace_arg_placeholders; + use crate::backend::db_utils::replace_arg_placeholders; use crate::future::block_on; - use crate::keys::wrap::{generate_raw_wrap_key, WrapKeyMethod}; + use crate::protect::{generate_raw_wrap_key, WrapKeyMethod}; #[test] fn sqlite_check_expiry_timestamp() { @@ -646,7 +650,7 @@ mod tests { if !cmp { panic!("now ({}) > expiry timestamp ({})", now, cmp_ts); } - Result::Ok(()) + Result::<_, Error>::Ok(()) }) .unwrap(); } diff --git a/src/backends/sqlite/provision.rs b/src/backend/sqlite/provision.rs similarity index 93% rename from src/backends/sqlite/provision.rs rename to src/backend/sqlite/provision.rs index 80d77568..9a9c4362 100644 --- a/src/backends/sqlite/provision.rs +++ b/src/backend/sqlite/provision.rs @@ -9,15 +9,14 @@ use sqlx::{ }; use super::SqliteStore; -use crate::db_utils::{init_keys, random_profile_name}; -use crate::error::Result; -use crate::future::{unblock, BoxFuture}; -use crate::keys::{ - wrap::{WrapKeyMethod, WrapKeyReference}, - KeyCache, PassKey, +use crate::{ + backend::db_utils::{init_keys, random_profile_name}, + error::Error, + future::{unblock, BoxFuture}, + protect::{KeyCache, PassKey, WrapKeyMethod, WrapKeyReference}, + storage::options::{IntoOptions, Options}, + storage::types::{ManageBackend, Store}, }; -use crate::options::{IntoOptions, Options}; -use crate::store::{ManageBackend, Store}; /// Configuration options for Sqlite stores #[derive(Debug)] @@ -29,7 +28,7 @@ pub struct SqliteStoreOptions { impl SqliteStoreOptions { /// Initialize `SqliteStoreOptions` from a generic set of options - pub fn new<'a>(options: impl IntoOptions<'a>) -> Result { + pub fn new<'a>(options: impl IntoOptions<'a>) -> Result { let mut opts = options.into_options()?; let max_connections = if let Some(max_conn) = opts.query.remove("max_connections") { max_conn @@ -74,7 +73,7 @@ impl SqliteStoreOptions { pass_key: PassKey<'_>, profile: Option<&'_ str>, recreate: bool, - ) -> Result> { + ) -> Result, Error> { if recreate && !self.in_memory { try_remove_file(self.path.to_string()).await?; } @@ -119,7 +118,7 @@ impl SqliteStoreOptions { method: Option, pass_key: PassKey<'_>, profile: Option<&'_ str>, - ) -> Result> { + ) -> Result, Error> { let conn_pool = match self.pool(false).await { Ok(pool) => Ok(pool), Err(SqlxError::Database(db_err)) => { @@ -139,7 +138,7 @@ impl SqliteStoreOptions { } /// Remove the Sqlite store defined by these configuration options - pub async fn remove(self) -> Result { + pub async fn remove(self) -> Result { if self.in_memory { Ok(true) } else { @@ -170,7 +169,7 @@ impl<'a> ManageBackend<'a> for SqliteStoreOptions { method: Option, pass_key: PassKey<'a>, profile: Option<&'a str>, - ) -> BoxFuture<'a, Result>> { + ) -> BoxFuture<'a, Result, Error>> { Box::pin(self.open(method, pass_key, profile)) } @@ -180,11 +179,11 @@ impl<'a> ManageBackend<'a> for SqliteStoreOptions { pass_key: PassKey<'a>, profile: Option<&'a str>, recreate: bool, - ) -> BoxFuture<'a, Result>> { + ) -> BoxFuture<'a, Result, Error>> { Box::pin(self.provision(method, pass_key, profile, recreate)) } - fn remove_backend(self) -> BoxFuture<'a, Result> { + fn remove_backend(self) -> BoxFuture<'a, Result> { Box::pin(self.remove()) } } @@ -194,7 +193,7 @@ async fn init_db( profile_name: &str, method: WrapKeyMethod, pass_key: PassKey<'_>, -) -> Result { +) -> Result { let (store_key, enc_store_key, wrap_key, wrap_key_ref) = unblock({ let pass_key = pass_key.into_owned(); move || init_keys(method, pass_key) @@ -290,7 +289,7 @@ async fn open_db( pass_key: PassKey<'_>, profile: Option<&str>, path: String, -) -> Result> { +) -> Result, Error> { let mut conn = conn_pool.acquire().await?; let mut ver_ok = false; let mut default_profile: Option = None; @@ -356,7 +355,7 @@ async fn open_db( ))) } -async fn try_remove_file(path: String) -> Result { +async fn try_remove_file(path: String) -> Result { unblock(|| match remove_file(path) { Ok(()) => Ok(true), Err(err) if err.kind() == IoErrorKind::NotFound => Ok(false), diff --git a/src/ffi/mod.rs b/src/ffi/mod.rs index 29c5c609..011e50cd 100644 --- a/src/ffi/mod.rs +++ b/src/ffi/mod.rs @@ -1,9 +1,8 @@ use std::marker::PhantomData; use std::os::raw::c_char; use std::ptr; -use std::str::FromStr; -use ffi_support::{rust_string_to_c, ByteBuffer, FfiStr}; +use ffi_support::{rust_string_to_c, ByteBuffer}; use zeroize::{Zeroize, Zeroizing}; #[cfg(feature = "jemalloc")] @@ -24,7 +23,7 @@ mod store; use self::error::{set_last_error, ErrorCode}; use crate::error::Error; use crate::future::{spawn_ok, unblock}; -use crate::keys::{derive_verkey, verify_signature, wrap::generate_raw_wrap_key, KeyAlg}; +use crate::protect::generate_raw_wrap_key; pub type CallbackId = i64; @@ -63,34 +62,6 @@ impl)> Drop for EnsureCallback { } } -#[no_mangle] -pub extern "C" fn askar_derive_verkey( - alg: FfiStr<'_>, - seed: ByteBuffer, - cb: Option, - cb_id: CallbackId, -) -> ErrorCode { - catch_err! { - trace!("Derive verkey"); - let alg = alg.as_opt_str().map(|alg| KeyAlg::from_str(alg).unwrap()).ok_or_else(|| err_msg!("Key algorithm not provided"))?; - let seed = Zeroizing::new(seed.as_slice().to_vec()); - let cb = cb.ok_or_else(|| err_msg!("No callback provided"))?; - let cb = EnsureCallback::new(move |result| - match result { - Ok(key) => cb(cb_id, ErrorCode::Success, rust_string_to_c(key)), - Err(err) => cb(cb_id, set_last_error(Some(err)), ptr::null()), - } - ); - spawn_ok(async move { - let result = unblock(move || derive_verkey( - alg, seed.as_slice() - )).await; - cb.resolve(result); - }); - Ok(ErrorCode::Success) - } -} - #[no_mangle] pub extern "C" fn askar_generate_raw_key( seed: ByteBuffer, @@ -120,37 +91,37 @@ pub extern "C" fn askar_generate_raw_key( } } -#[no_mangle] -pub extern "C" fn askar_verify_signature( - signer_vk: FfiStr<'_>, - message: ByteBuffer, - signature: ByteBuffer, - cb: Option, - cb_id: CallbackId, -) -> ErrorCode { - catch_err! { - trace!("Verify signature"); - let signer_vk = signer_vk.into_opt_string().ok_or_else(|| err_msg!("Signer verkey not provided"))?; - let message = message.as_slice().to_vec(); - let signature = signature.as_slice().to_vec(); - let cb = cb.ok_or_else(|| err_msg!("No callback provided"))?; - let cb = EnsureCallback::new(move |result| - match result { - Ok(verify) => cb(cb_id, ErrorCode::Success, verify as i8), - Err(err) => cb(cb_id, set_last_error(Some(err)), 0), - } - ); - spawn_ok(async move { - let result = unblock(move || verify_signature( - &signer_vk, - &message, - &signature - )).await; - cb.resolve(result); - }); - Ok(ErrorCode::Success) - } -} +// #[no_mangle] +// pub extern "C" fn askar_verify_signature( +// signer_vk: FfiStr<'_>, +// message: ByteBuffer, +// signature: ByteBuffer, +// cb: Option, +// cb_id: CallbackId, +// ) -> ErrorCode { +// catch_err! { +// trace!("Verify signature"); +// let signer_vk = signer_vk.into_opt_string().ok_or_else(|| err_msg!("Signer verkey not provided"))?; +// let message = message.as_slice().to_vec(); +// let signature = signature.as_slice().to_vec(); +// let cb = cb.ok_or_else(|| err_msg!("No callback provided"))?; +// let cb = EnsureCallback::new(move |result| +// match result { +// Ok(verify) => cb(cb_id, ErrorCode::Success, verify as i8), +// Err(err) => cb(cb_id, set_last_error(Some(err)), 0), +// } +// ); +// spawn_ok(async move { +// let result = unblock(move || verify_signature( +// &signer_vk, +// &message, +// &signature +// )).await; +// cb.resolve(result); +// }); +// Ok(ErrorCode::Success) +// } +// } #[no_mangle] pub extern "C" fn askar_version() -> *mut c_char { diff --git a/src/ffi/store.rs b/src/ffi/store.rs index c30b3b4a..e12abe07 100644 --- a/src/ffi/store.rs +++ b/src/ffi/store.rs @@ -1,11 +1,14 @@ -use std::collections::BTreeMap; -use std::ffi::CString; -use std::mem; -use std::os::raw::c_char; -use std::ptr; -use std::sync::{ - atomic::{AtomicUsize, Ordering}, - Arc, +use std::{ + collections::BTreeMap, + ffi::CString, + mem, + os::raw::c_char, + ptr, + str::FromStr, + sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, + }, }; use async_mutex::{Mutex, MutexGuardArc}; @@ -14,14 +17,18 @@ use indy_utils::new_handle_type; use once_cell::sync::Lazy; use zeroize::Zeroize; -use super::error::set_last_error; -use super::{CallbackId, EnsureCallback, ErrorCode}; -use crate::any::{AnySession, AnyStore}; -use crate::entry::{Entry, EntryOperation, EntryTagSet, TagFilter}; -use crate::error::Result as KvResult; -use crate::future::spawn_ok; -use crate::keys::{wrap::WrapKeyMethod, KeyAlg, KeyCategory, KeyEntry, PassKey}; -use crate::store::{ManageBackend, Scan}; +use super::{error::set_last_error, CallbackId, EnsureCallback, ErrorCode}; +use crate::{ + backend::any::{AnySession, AnyStore}, + error::Result as KvResult, + future::spawn_ok, + protect::{PassKey, WrapKeyMethod}, + storage::{ + entry::{Entry, EntryOperation, EntryTagSet, TagFilter}, + key::{KeyCategory, KeyEntry}, + types::{ManageBackend, Scan}, + }, +}; new_handle_type!(StoreHandle, FFI_STORE_COUNTER); new_handle_type!(SessionHandle, FFI_SESSION_COUNTER); @@ -263,13 +270,6 @@ impl FfiEntry { } } -#[repr(C)] -pub struct FfiUnpackResult { - unpacked: ByteBuffer, - recipient: *const c_char, - sender: *const c_char, -} - #[no_mangle] pub extern "C" fn askar_store_provision( spec_uri: FfiStr<'_>, @@ -879,62 +879,6 @@ pub extern "C" fn askar_session_update( } } -#[no_mangle] -pub extern "C" fn askar_session_create_keypair( - handle: SessionHandle, - alg: FfiStr<'_>, - metadata: FfiStr<'_>, - tags: FfiStr<'_>, - seed: ByteBuffer, - cb: Option, - cb_id: CallbackId, -) -> ErrorCode { - catch_err! { - trace!("Create keypair"); - let cb = cb.ok_or_else(|| err_msg!("No callback provided"))?; - let alg = alg.as_opt_str().map(|alg| KeyAlg::from_str(alg).unwrap()).ok_or_else(|| err_msg!("Key algorithm not provided"))?; - let metadata = metadata.into_opt_string(); - let tags = if let Some(tags) = tags.as_opt_str() { - Some( - serde_json::from_str::(tags) - .map_err(err_map!("Error decoding tags"))? - .into_inner(), - ) - } else { - None - }; - let seed = if seed.as_slice().len() > 0 { - Some(seed.as_slice().to_vec()) - } else { - None - }; - - let cb = EnsureCallback::new(move |result| - match result { - Ok(ident) => { - cb(cb_id, ErrorCode::Success, rust_string_to_c(ident)) - } - Err(err) => cb(cb_id, set_last_error(Some(err)), ptr::null()), - } - ); - - spawn_ok(async move { - let result = async { - let mut session = handle.load().await?; - let key_entry = session.create_keypair( - alg, - metadata.as_ref().map(String::as_str), - seed.as_ref().map(Vec::as_ref), - tags.as_ref().map(Vec::as_slice), - ).await?; - Ok(key_entry.ident.clone()) - }.await; - cb.resolve(result); - }); - Ok(ErrorCode::Success) - } -} - #[no_mangle] pub extern "C" fn askar_session_fetch_keypair( handle: SessionHandle, @@ -1027,138 +971,6 @@ pub extern "C" fn askar_session_update_keypair( } } -#[no_mangle] -pub extern "C" fn askar_session_sign_message( - handle: SessionHandle, - key_ident: FfiStr<'_>, - message: ByteBuffer, - cb: Option, - cb_id: CallbackId, -) -> ErrorCode { - catch_err! { - trace!("Sign message"); - let cb = cb.ok_or_else(|| err_msg!("No callback provided"))?; - let key_ident = key_ident.into_opt_string().ok_or_else(|| err_msg!("Key identity not provided"))?; - // copy message so the caller can drop it - let message = message.as_slice().to_vec(); - - let cb = EnsureCallback::new(move |result| - match result { - Ok(sig) => { - cb(cb_id, ErrorCode::Success, ByteBuffer::from_vec(sig)) - } - Err(err) => cb(cb_id, set_last_error(Some(err)), ByteBuffer::default()), - } - ); - - spawn_ok(async move { - let result = async { - let mut session = handle.load().await?; - let signature = session.sign_message( - &key_ident, - &message, - ).await?; - Ok(signature) - }.await; - cb.resolve(result); - }); - Ok(ErrorCode::Success) - } -} - -#[no_mangle] -pub extern "C" fn askar_session_pack_message( - handle: SessionHandle, - recipient_vks: FfiStr<'_>, - from_key_ident: FfiStr<'_>, - message: ByteBuffer, - cb: Option, - cb_id: CallbackId, -) -> ErrorCode { - catch_err! { - trace!("Pack message"); - let cb = cb.ok_or_else(|| err_msg!("No callback provided"))?; - let mut recips = recipient_vks.as_opt_str().ok_or_else(|| err_msg!("Recipient verkey(s) not provided"))?; - let mut recipient_vks = vec![]; - loop { - if let Some(pos) = recips.find(",") { - recipient_vks.push((&recips[..pos]).to_string()); - recips = &recips[(pos+1)..]; - } else { - if !recips.is_empty() { - recipient_vks.push(recips.to_string()); - } - break; - } - } - let from_key_ident = from_key_ident.into_opt_string(); - let message = message.as_slice().to_vec(); - - let cb = EnsureCallback::new(move |result| - match result { - Ok(packed) => { - cb(cb_id, ErrorCode::Success, ByteBuffer::from_vec(packed)) - } - Err(err) => cb(cb_id, set_last_error(Some(err)), ByteBuffer::default()), - } - ); - - spawn_ok(async move { - let result = async { - let mut session = handle.load().await?; - let packed = session.pack_message( - recipient_vks.iter().map(String::as_str), - from_key_ident.as_ref().map(String::as_str), - &message - ).await?; - Ok(packed) - }.await; - cb.resolve(result); - }); - Ok(ErrorCode::Success) - } -} - -#[no_mangle] -pub extern "C" fn askar_session_unpack_message( - handle: SessionHandle, - message: ByteBuffer, - cb: Option, - cb_id: CallbackId, -) -> ErrorCode { - catch_err! { - trace!("Unpack message"); - let cb = cb.ok_or_else(|| err_msg!("No callback provided"))?; - let message = message.as_slice().to_vec(); - - let cb = EnsureCallback::new(move |result: KvResult<(Vec, String, Option)>| - match result { - Ok((unpacked, recipient, sender)) => { - cb(cb_id, ErrorCode::Success, FfiUnpackResult { - unpacked: ByteBuffer::from_vec(unpacked), recipient: rust_string_to_c(recipient), sender: sender.map(rust_string_to_c).unwrap_or(ptr::null_mut())} - ) - } - Err(err) => { - eprintln!("err: {:?}", &err); - cb(cb_id, set_last_error(Some(err)), FfiUnpackResult { unpacked: ByteBuffer::default(), recipient: ptr::null(), sender: ptr::null() }) - } - } - ); - - spawn_ok(async move { - let result = async { - let mut session = handle.load().await?; - let (unpacked, recipient, sender) = session.unpack_message( - &message - ).await?; - Ok((unpacked, recipient.to_base58(), sender.map(|s| s.to_base58()))) - }.await; - cb.resolve(result); - }); - Ok(ErrorCode::Success) - } -} - #[no_mangle] pub extern "C" fn askar_session_close( handle: SessionHandle, diff --git a/src/lib.rs b/src/lib.rs index f5f021e4..82bfe90b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,7 +21,7 @@ extern crate log; #[macro_use] extern crate serde; -pub mod backends; +pub mod backend; pub use askar_crypto as crypto; @@ -33,23 +33,19 @@ pub mod future; /// Indy wallet compatibility support pub mod indy_compat; -// #[cfg(feature = "ffi")] -// #[macro_use] -// extern crate serde_json; - -// #[cfg(feature = "ffi")] -// mod ffi; - -pub mod protect; +#[cfg(feature = "ffi")] +#[macro_use] +extern crate serde_json; -pub mod storage; +#[cfg(feature = "ffi")] +mod ffi; -// #[macro_use] -// pub(crate) mod serde_utils; +mod protect; +pub use protect::{generate_raw_wrap_key, PassKey, WrapKeyMethod}; -// mod keys; -// pub use self::keys::{ -// derive_verkey, verify_signature, -// wrap::{generate_raw_wrap_key, WrapKeyMethod}, -// KeyAlg, KeyCategory, KeyEntry, KeyParams, PassKey, -// }; +mod storage; +pub use storage::{ + entry::{Entry, EntryTag, TagFilter}, + key::KeyAlg, + types::{Backend, ManageBackend, Store}, +}; diff --git a/src/protect/hmac_key.rs b/src/protect/hmac_key.rs index 35d55b09..970138e5 100644 --- a/src/protect/hmac_key.rs +++ b/src/protect/hmac_key.rs @@ -27,6 +27,7 @@ use crate::{ pub struct HmacKey, H>(ArrayKey, PhantomData); impl, H> HmacKey { + #[allow(dead_code)] pub fn from_slice(key: &[u8]) -> Result { if key.len() != L::USIZE { return Err(err_msg!(Encryption, "invalid length for hmac key")); diff --git a/src/protect/mod.rs b/src/protect/mod.rs index a5bc2f85..4541d621 100644 --- a/src/protect/mod.rs +++ b/src/protect/mod.rs @@ -14,18 +14,17 @@ mod store_key; pub use self::store_key::StoreKey; mod wrap_key; -pub use self::wrap_key::{generate_raw_wrap_key, WrapKey, WrapKeyMethod}; +pub use self::wrap_key::{generate_raw_wrap_key, WrapKey, WrapKeyMethod, WrapKeyReference}; use crate::{ crypto::buffer::SecretBytes, error::Error, future::unblock, - storage::{ - entry::{EncEntryTag, EntryTag}, - types::ProfileId, - }, + storage::entry::{EncEntryTag, EntryTag}, }; +pub type ProfileId = i64; + #[derive(Debug)] pub struct KeyCache { profile_info: Mutex)>>, @@ -40,7 +39,7 @@ impl KeyCache { } } - pub async fn load_key(&self, ciphertext: SecretBytes) -> Result { + pub async fn load_key(&self, ciphertext: Vec) -> Result { let wrap_key = self.wrap_key.clone(); unblock(move || { let mut data = wrap_key @@ -69,32 +68,32 @@ impl KeyCache { } pub(crate) trait EntryEncryptor { - fn prepare_input(input: &[u8]) -> Result { - Ok(SecretBytes::from(input)) + fn prepare_input(input: &[u8]) -> SecretBytes { + SecretBytes::from(input) } - fn encrypt_entry_category(&self, category: SecretBytes) -> Result; - fn encrypt_entry_name(&self, name: SecretBytes) -> Result; - fn encrypt_entry_value(&self, value: SecretBytes) -> Result; + fn encrypt_entry_category(&self, category: SecretBytes) -> Result, Error>; + fn encrypt_entry_name(&self, name: SecretBytes) -> Result, Error>; + fn encrypt_entry_value(&self, value: SecretBytes) -> Result, Error>; fn encrypt_entry_tags(&self, tags: Vec) -> Result, Error>; - fn decrypt_entry_category(&self, enc_category: SecretBytes) -> Result; - fn decrypt_entry_name(&self, enc_name: SecretBytes) -> Result; - fn decrypt_entry_value(&self, enc_value: SecretBytes) -> Result; + fn decrypt_entry_category(&self, enc_category: Vec) -> Result; + fn decrypt_entry_name(&self, enc_name: Vec) -> Result; + fn decrypt_entry_value(&self, enc_value: Vec) -> Result; fn decrypt_entry_tags(&self, enc_tags: Vec) -> Result, Error>; } pub struct NullEncryptor; impl EntryEncryptor for NullEncryptor { - fn encrypt_entry_category(&self, category: SecretBytes) -> Result { - Ok(category) + fn encrypt_entry_category(&self, category: SecretBytes) -> Result, Error> { + Ok(category.into_vec()) } - fn encrypt_entry_name(&self, name: SecretBytes) -> Result { - Ok(name) + fn encrypt_entry_name(&self, name: SecretBytes) -> Result, Error> { + Ok(name.into_vec()) } - fn encrypt_entry_value(&self, value: SecretBytes) -> Result { - Ok(value) + fn encrypt_entry_value(&self, value: SecretBytes) -> Result, Error> { + Ok(value.into_vec()) } fn encrypt_entry_tags(&self, tags: Vec) -> Result, Error> { Ok(tags @@ -114,19 +113,19 @@ impl EntryEncryptor for NullEncryptor { .collect()) } - fn decrypt_entry_category(&self, enc_category: SecretBytes) -> Result { - Ok(String::from_utf8(enc_category.into_vec()).map_err(err_map!(Encryption))?) + fn decrypt_entry_category(&self, enc_category: Vec) -> Result { + Ok(String::from_utf8(enc_category).map_err(err_map!(Encryption))?) } - fn decrypt_entry_name(&self, enc_name: SecretBytes) -> Result { - Ok(String::from_utf8(enc_name.into_vec()).map_err(err_map!(Encryption))?) + fn decrypt_entry_name(&self, enc_name: Vec) -> Result { + Ok(String::from_utf8(enc_name).map_err(err_map!(Encryption))?) } - fn decrypt_entry_value(&self, enc_value: SecretBytes) -> Result { + fn decrypt_entry_value(&self, enc_value: Vec) -> Result { Ok(enc_value.into()) } fn decrypt_entry_tags(&self, enc_tags: Vec) -> Result, Error> { Ok(enc_tags.into_iter().try_fold(vec![], |mut acc, tag| { - let name = String::from_utf8(tag.name.into_vec()).map_err(err_map!(Encryption))?; - let value = String::from_utf8(tag.value.into_vec()).map_err(err_map!(Encryption))?; + let name = String::from_utf8(tag.name).map_err(err_map!(Encryption))?; + let value = String::from_utf8(tag.value).map_err(err_map!(Encryption))?; acc.push(if tag.plaintext { EntryTag::Plaintext(name, value) } else { diff --git a/src/protect/pass_key.rs b/src/protect/pass_key.rs index f0209501..09101700 100644 --- a/src/protect/pass_key.rs +++ b/src/protect/pass_key.rs @@ -17,6 +17,7 @@ impl PassKey<'_> { PassKey(Some(Cow::Borrowed(&**self))) } + /// Create an empty passkey pub fn empty() -> PassKey<'static> { PassKey(None) } diff --git a/src/protect/store_key.rs b/src/protect/store_key.rs index 65892067..ecd3ec78 100644 --- a/src/protect/store_key.rs +++ b/src/protect/store_key.rs @@ -82,45 +82,46 @@ where mut buffer: SecretBytes, enc_key: &Key, hmac_key: &HmacKey, - ) -> Result { + ) -> Result, Error> { let mut nonce = ArrayKey::::default(); hmac_key.hmac_to(buffer.as_ref(), nonce.as_mut())?; enc_key.encrypt_in_place(&mut buffer, nonce.as_ref(), &[])?; buffer.buffer_insert_slice(0, nonce.as_ref())?; - Ok(buffer) + Ok(buffer.into_vec()) } - pub fn encrypt(mut buffer: SecretBytes, enc_key: &Key) -> Result { + pub fn encrypt(mut buffer: SecretBytes, enc_key: &Key) -> Result, Error> { let nonce = ArrayKey::::random(); enc_key.encrypt_in_place(&mut buffer, nonce.as_ref(), &[])?; buffer.buffer_insert_slice(0, nonce.as_ref())?; - Ok(buffer) + Ok(buffer.into_vec()) } - pub fn decrypt(mut buffer: SecretBytes, enc_key: &Key) -> Result { + pub fn decrypt(ciphertext: Vec, enc_key: &Key) -> Result { let nonce_len = Key::nonce_length(); - if buffer.len() < nonce_len { + if ciphertext.len() < nonce_len { return Err(err_msg!(Encryption, "invalid encrypted value")); } + let mut buffer = SecretBytes::from(ciphertext); let nonce = ArrayKey::::from_slice(&buffer.as_ref()[..nonce_len]); buffer.buffer_remove(0..nonce_len)?; enc_key.decrypt_in_place(&mut buffer, nonce.as_ref(), &[])?; Ok(buffer) } - pub fn encrypt_tag_name(&self, name: SecretBytes) -> Result { + pub fn encrypt_tag_name(&self, name: SecretBytes) -> Result, Error> { Self::encrypt_searchable(name, &self.tag_name_key, &self.tags_hmac_key) } - pub fn encrypt_tag_value(&self, value: SecretBytes) -> Result { + pub fn encrypt_tag_value(&self, value: SecretBytes) -> Result, Error> { Self::encrypt_searchable(value, &self.tag_value_key, &self.tags_hmac_key) } - pub fn decrypt_tag_name(&self, enc_tag_name: SecretBytes) -> Result { + pub fn decrypt_tag_name(&self, enc_tag_name: Vec) -> Result { Self::decrypt(enc_tag_name, &self.tag_name_key) } - pub fn decrypt_tag_value(&self, enc_tag_value: SecretBytes) -> Result { + pub fn decrypt_tag_value(&self, enc_tag_value: Vec) -> Result { Self::decrypt(enc_tag_value, &self.tag_value_key) } } @@ -143,38 +144,38 @@ where Key: KeyGen + KeyMeta + KeyAeadInPlace + KeyAeadMeta + KeySecretBytes, HmacKey: KeyGen + HmacOutput, { - fn prepare_input(input: &[u8]) -> Result { + fn prepare_input(input: &[u8]) -> SecretBytes { let mut buf = SecretBytes::with_capacity(Self::encrypted_size(input.len())); - buf.write_slice(input)?; - Ok(buf) + buf.write_slice(input).unwrap(); + buf } - fn encrypt_entry_category(&self, category: SecretBytes) -> Result { + fn encrypt_entry_category(&self, category: SecretBytes) -> Result, Error> { Self::encrypt_searchable(category, &self.category_key, &self.item_hmac_key) } - fn encrypt_entry_name(&self, name: SecretBytes) -> Result { + fn encrypt_entry_name(&self, name: SecretBytes) -> Result, Error> { Self::encrypt_searchable(name, &self.name_key, &self.item_hmac_key) } - fn encrypt_entry_value(&self, value: SecretBytes) -> Result { + fn encrypt_entry_value(&self, value: SecretBytes) -> Result, Error> { let value_key = Key::generate()?; let value = Self::encrypt(value, &value_key)?; - let key_input = value_key.with_secret_bytes(|sk| Self::prepare_input(sk.unwrap()))?; + let key_input = value_key.with_secret_bytes(|sk| Self::prepare_input(sk.unwrap())); let mut result = Self::encrypt(key_input, &self.value_key)?; result.write_slice(value.as_ref())?; Ok(result) } - fn decrypt_entry_category(&self, enc_category: SecretBytes) -> Result { + fn decrypt_entry_category(&self, enc_category: Vec) -> Result { decode_utf8(Self::decrypt(enc_category, &self.category_key)?.into_vec()) } - fn decrypt_entry_name(&self, enc_name: SecretBytes) -> Result { + fn decrypt_entry_name(&self, enc_name: Vec) -> Result { decode_utf8(Self::decrypt(enc_name, &self.name_key)?.into_vec()) } - fn decrypt_entry_value(&self, mut enc_value: SecretBytes) -> Result { + fn decrypt_entry_value(&self, mut enc_value: Vec) -> Result { let enc_key_size = Self::encrypted_size(Key::KeySize::USIZE); if enc_value.len() < enc_key_size + Self::encrypted_size(0) { return Err(err_msg!( @@ -182,7 +183,7 @@ where "Buffer is too short to represent an encrypted value", )); } - let value = SecretBytes::from_slice(&enc_value[enc_key_size..]); + let value = Vec::from(&enc_value[enc_key_size..]); enc_value.buffer_resize(enc_key_size)?; let value_key = Key::from_secret_bytes(Self::decrypt(enc_value, &self.value_key)?.as_ref())?; @@ -217,7 +218,7 @@ where enc_tags.into_iter().try_fold(vec![], |mut acc, tag| { let name = decode_utf8(self.decrypt_tag_name(tag.name)?.into_vec())?; acc.push(if tag.plaintext { - let value = decode_utf8(tag.value.into_vec())?; + let value = decode_utf8(tag.value)?; EntryTag::Plaintext(name, value) } else { let value = decode_utf8(self.decrypt_tag_value(tag.value)?.into_vec())?; @@ -262,8 +263,8 @@ mod tests { let enc_tags = key .encrypt_entry_tags(test_record.tags.clone().unwrap()) .unwrap(); - assert_ne!(test_record.category.as_bytes(), enc_category.as_ref()); - assert_ne!(test_record.name.as_bytes(), enc_name.as_ref()); + assert_ne!(test_record.category.as_bytes(), &enc_category[..]); + assert_ne!(test_record.name.as_bytes(), &enc_name[..]); assert_ne!(test_record.value, enc_value); let cmp_record = Entry::new( diff --git a/src/protect/wrap_key.rs b/src/protect/wrap_key.rs index 37b9e8ec..2f7960c2 100644 --- a/src/protect/wrap_key.rs +++ b/src/protect/wrap_key.rs @@ -57,27 +57,28 @@ impl WrapKey { self.0.is_none() } - pub fn wrap_data(&self, mut data: SecretBytes) -> Result { + pub fn wrap_data(&self, mut data: SecretBytes) -> Result, Error> { match &self.0 { Some(key) => { let nonce = WrapKeyNonce::random(); key.encrypt_in_place(&mut data, nonce.as_ref(), &[])?; data.buffer_insert_slice(0, nonce.as_ref())?; - Ok(data) + Ok(data.into_vec()) } - None => Ok(data), + None => Ok(data.into_vec()), } } - pub fn unwrap_data(&self, mut ciphertext: SecretBytes) -> Result { + pub fn unwrap_data(&self, ciphertext: Vec) -> Result { match &self.0 { Some(key) => { - let nonce = WrapKeyNonce::from_slice(&ciphertext.as_ref()[..WrapKeyNonce::SIZE]); - ciphertext.buffer_remove(0..WrapKeyNonce::SIZE)?; - key.decrypt_in_place(&mut ciphertext, nonce.as_ref(), &[])?; - Ok(ciphertext) + let nonce = WrapKeyNonce::from_slice(&ciphertext[..WrapKeyNonce::SIZE]); + let mut buffer = SecretBytes::from(ciphertext); + buffer.buffer_remove(0..WrapKeyNonce::SIZE)?; + key.decrypt_in_place(&mut buffer, nonce.as_ref(), &[])?; + Ok(buffer) } - None => Ok(ciphertext), + None => Ok(ciphertext.into()), } } @@ -275,13 +276,10 @@ mod tests { #[test] fn derived_key_unwrap_expected() { let input = b"test data"; - let wrapped = SecretBytes::from_slice( - &[ - 194, 156, 102, 253, 229, 11, 48, 184, 160, 119, 218, 30, 169, 188, 244, 223, 235, - 95, 171, 234, 18, 5, 9, 115, 174, 208, 232, 37, 31, 32, 250, 216, 32, 92, 253, 45, - 236, - ][..], - ); + let wrapped = vec![ + 194, 156, 102, 253, 229, 11, 48, 184, 160, 119, 218, 30, 169, 188, 244, 223, 235, 95, + 171, 234, 18, 5, 9, 115, 174, 208, 232, 37, 31, 32, 250, 216, 32, 92, 253, 45, 236, + ]; let pass = PassKey::from("pass"); let key_ref = WrapKeyReference::parse_uri("kdf:argon2i:13:mod?salt=MR6B1jrReV2JioaizEaRo6") .expect("Error parsing derived key ref"); @@ -292,13 +290,10 @@ mod tests { #[test] fn derived_key_check_bad_password() { - let wrapped = SecretBytes::from_slice( - &[ - 194, 156, 102, 253, 229, 11, 48, 184, 160, 119, 218, 30, 169, 188, 244, 223, 235, - 95, 171, 234, 18, 5, 9, 115, 174, 208, 232, 37, 31, 32, 250, 216, 32, 92, 253, 45, - 236, - ][..], - ); + let wrapped = vec![ + 194, 156, 102, 253, 229, 11, 48, 184, 160, 119, 218, 30, 169, 188, 244, 223, 235, 95, + 171, 234, 18, 5, 9, 115, 174, 208, 232, 37, 31, 32, 250, 216, 32, 92, 253, 45, 236, + ]; let key_ref = WrapKeyReference::parse_uri("kdf:argon2i:13:mod?salt=MR6B1jrReV2JioaizEaRo6") .expect("Error parsing derived key ref"); let check_bad_pass = key_ref diff --git a/src/storage/entry.rs b/src/storage/entry.rs index f61ad487..65fd2a3c 100644 --- a/src/storage/entry.rs +++ b/src/storage/entry.rs @@ -299,8 +299,8 @@ impl Serialize for EntryTagSet { #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) struct EncEntryTag { - pub name: SecretBytes, - pub value: SecretBytes, + pub name: Vec, + pub value: Vec, pub plaintext: bool, } diff --git a/src/storage/mod.rs b/src/storage/mod.rs index 9bf0d99c..f75ef3a0 100644 --- a/src/storage/mod.rs +++ b/src/storage/mod.rs @@ -2,8 +2,8 @@ pub mod entry; pub mod key; -pub mod options; +pub(crate) mod options; pub mod types; -mod wql; +pub(crate) mod wql; diff --git a/src/storage/types.rs b/src/storage/types.rs index 03a262ed..13597a7b 100644 --- a/src/storage/types.rs +++ b/src/storage/types.rs @@ -18,10 +18,6 @@ use crate::{ protect::{PassKey, WrapKeyMethod}, }; -pub type ProfileId = i64; - -pub type Expiry = chrono::DateTime; - /// Represents a generic backend implementation pub trait Backend: Send + Sync { /// The type of session managed by this backend diff --git a/tests/backends.rs b/tests/backends.rs index 9f43e4f0..de2693a0 100644 --- a/tests/backends.rs +++ b/tests/backends.rs @@ -105,37 +105,37 @@ macro_rules! backend_tests { }) } - #[test] - fn keypair_create_fetch() { - block_on(async { - let db = $init.await; - super::utils::db_keypair_create_fetch(&db).await; - }) - } - - #[test] - fn keypair_sign_verify() { - block_on(async { - let db = $init.await; - super::utils::db_keypair_sign_verify(&db).await; - }) - } - - #[test] - fn keypair_pack_unpack_anon() { - block_on(async { - let db = $init.await; - super::utils::db_keypair_pack_unpack_anon(&db).await; - }) - } - - #[test] - fn keypair_pack_unpack_auth() { - block_on(async { - let db = $init.await; - super::utils::db_keypair_pack_unpack_auth(&db).await; - }) - } + // #[test] + // fn keypair_create_fetch() { + // block_on(async { + // let db = $init.await; + // super::utils::db_keypair_create_fetch(&db).await; + // }) + // } + + // #[test] + // fn keypair_sign_verify() { + // block_on(async { + // let db = $init.await; + // super::utils::db_keypair_sign_verify(&db).await; + // }) + // } + + // #[test] + // fn keypair_pack_unpack_anon() { + // block_on(async { + // let db = $init.await; + // super::utils::db_keypair_pack_unpack_anon(&db).await; + // }) + // } + + // #[test] + // fn keypair_pack_unpack_auth() { + // block_on(async { + // let db = $init.await; + // super::utils::db_keypair_pack_unpack_auth(&db).await; + // }) + // } #[test] fn txn_rollback() { @@ -181,7 +181,7 @@ macro_rules! backend_tests { #[cfg(feature = "sqlite")] mod sqlite { - use aries_askar::sqlite::{SqliteStore, SqliteStoreOptions}; + use aries_askar::backend::sqlite::{SqliteStore, SqliteStoreOptions}; use aries_askar::{generate_raw_wrap_key, ManageBackend, Store, WrapKeyMethod}; use std::path::Path; diff --git a/tests/utils/mod.rs b/tests/utils/mod.rs index 528b6631..4adca855 100644 --- a/tests/utils/mod.rs +++ b/tests/utils/mod.rs @@ -1,6 +1,4 @@ -use aries_askar::{ - verify_signature, Backend, Entry, EntryTag, ErrorKind, KeyAlg, Store, TagFilter, -}; +use aries_askar::{Backend, Entry, EntryTag, ErrorKind, Store, TagFilter}; const ERR_PROFILE: &'static str = "Error creating profile"; const ERR_SESSION: &'static str = "Error starting session"; @@ -464,115 +462,115 @@ pub async fn db_remove_all(db: &Store) { assert_eq!(removed, 2); } -pub async fn db_keypair_create_fetch(db: &Store) { - let mut conn = db.session(None).await.expect(ERR_SESSION); - - let metadata = "meta".to_owned(); - let key_info = conn - .create_keypair(KeyAlg::Ed25519, Some(&metadata), None, None) - .await - .expect(ERR_CREATE_KEYPAIR); - assert_eq!(key_info.params.metadata, Some(metadata)); - - let found = conn - .fetch_key(key_info.category.clone(), &key_info.ident, false) - .await - .expect(ERR_FETCH_KEY); - assert_eq!(Some(key_info), found); -} - -pub async fn db_keypair_sign_verify(db: &Store) { - let mut conn = db.session(None).await.expect(ERR_SESSION); - - let key_info = conn - .create_keypair(KeyAlg::Ed25519, None, None, None) - .await - .expect(ERR_CREATE_KEYPAIR); - - let message = b"message".to_vec(); - let sig = conn - .sign_message(&key_info.ident, &message) - .await - .expect(ERR_SIGN); - - assert_eq!( - verify_signature(&key_info.ident, &message, &sig).expect(ERR_VERIFY), - true - ); - - assert_eq!( - verify_signature(&key_info.ident, b"bad input", &sig).expect(ERR_VERIFY), - false - ); - - assert_eq!( - verify_signature( - &key_info.ident, - // [0u8; 64] - b"xt19s1sp2UZCGhy9rNyb1FtxdKiDGZZPNFnc1KiM9jYYEuHxuwNeFf1oQKsn8zv6yvYBGhXa83288eF4MqN1oDq", - &sig - ).expect(ERR_VERIFY), - false - ); - - assert_eq!( - verify_signature(&key_info.ident, &message, b"bad sig").is_err(), - true - ); - - let err = verify_signature("not a key", &message, &sig).expect_err(ERR_REQ_ERR); - assert_eq!(err.kind(), ErrorKind::Input); -} - -pub async fn db_keypair_pack_unpack_anon(db: &Store) { - let mut conn = db.session(None).await.expect(ERR_SESSION); - - let recip_key = conn - .create_keypair(KeyAlg::Ed25519, None, None, None) - .await - .expect(ERR_CREATE_KEYPAIR); - - let msg = b"message".to_vec(); - - let packed = conn - .pack_message(vec![recip_key.ident.as_str()], None, &msg) - .await - .expect(ERR_PACK); - - let (unpacked, p_recip, p_send) = conn.unpack_message(&packed).await.expect(ERR_UNPACK); - assert_eq!(unpacked, msg); - assert_eq!(p_recip.to_string(), recip_key.ident); - assert_eq!(p_send, None); -} - -pub async fn db_keypair_pack_unpack_auth(db: &Store) { - let mut conn = db.session(None).await.expect(ERR_SESSION); - - let sender_key = conn - .create_keypair(KeyAlg::Ed25519, None, None, None) - .await - .expect(ERR_CREATE_KEYPAIR); - let recip_key = conn - .create_keypair(KeyAlg::Ed25519, None, None, None) - .await - .expect(ERR_CREATE_KEYPAIR); - - let msg = b"message".to_vec(); - - let packed = conn - .pack_message( - vec![recip_key.ident.as_str()], - Some(&sender_key.ident), - &msg, - ) - .await - .expect(ERR_PACK); - - let (unpacked, p_recip, p_send) = conn.unpack_message(&packed).await.expect(ERR_UNPACK); - assert_eq!(unpacked, msg); - assert_eq!(p_recip.to_string(), recip_key.ident); - assert_eq!(p_send.map(|k| k.to_string()), Some(sender_key.ident)); -} +// pub async fn db_keypair_create_fetch(db: &Store) { +// let mut conn = db.session(None).await.expect(ERR_SESSION); + +// let metadata = "meta".to_owned(); +// let key_info = conn +// .create_keypair(KeyAlg::Ed25519, Some(&metadata), None, None) +// .await +// .expect(ERR_CREATE_KEYPAIR); +// assert_eq!(key_info.params.metadata, Some(metadata)); + +// let found = conn +// .fetch_key(key_info.category.clone(), &key_info.ident, false) +// .await +// .expect(ERR_FETCH_KEY); +// assert_eq!(Some(key_info), found); +// } + +// pub async fn db_keypair_sign_verify(db: &Store) { +// let mut conn = db.session(None).await.expect(ERR_SESSION); + +// let key_info = conn +// .create_keypair(KeyAlg::Ed25519, None, None, None) +// .await +// .expect(ERR_CREATE_KEYPAIR); + +// let message = b"message".to_vec(); +// let sig = conn +// .sign_message(&key_info.ident, &message) +// .await +// .expect(ERR_SIGN); + +// assert_eq!( +// verify_signature(&key_info.ident, &message, &sig).expect(ERR_VERIFY), +// true +// ); + +// assert_eq!( +// verify_signature(&key_info.ident, b"bad input", &sig).expect(ERR_VERIFY), +// false +// ); + +// assert_eq!( +// verify_signature( +// &key_info.ident, +// // [0u8; 64] +// b"xt19s1sp2UZCGhy9rNyb1FtxdKiDGZZPNFnc1KiM9jYYEuHxuwNeFf1oQKsn8zv6yvYBGhXa83288eF4MqN1oDq", +// &sig +// ).expect(ERR_VERIFY), +// false +// ); + +// assert_eq!( +// verify_signature(&key_info.ident, &message, b"bad sig").is_err(), +// true +// ); + +// let err = verify_signature("not a key", &message, &sig).expect_err(ERR_REQ_ERR); +// assert_eq!(err.kind(), ErrorKind::Input); +// } + +// pub async fn db_keypair_pack_unpack_anon(db: &Store) { +// let mut conn = db.session(None).await.expect(ERR_SESSION); + +// let recip_key = conn +// .create_keypair(KeyAlg::Ed25519, None, None, None) +// .await +// .expect(ERR_CREATE_KEYPAIR); + +// let msg = b"message".to_vec(); + +// let packed = conn +// .pack_message(vec![recip_key.ident.as_str()], None, &msg) +// .await +// .expect(ERR_PACK); + +// let (unpacked, p_recip, p_send) = conn.unpack_message(&packed).await.expect(ERR_UNPACK); +// assert_eq!(unpacked, msg); +// assert_eq!(p_recip.to_string(), recip_key.ident); +// assert_eq!(p_send, None); +// } + +// pub async fn db_keypair_pack_unpack_auth(db: &Store) { +// let mut conn = db.session(None).await.expect(ERR_SESSION); + +// let sender_key = conn +// .create_keypair(KeyAlg::Ed25519, None, None, None) +// .await +// .expect(ERR_CREATE_KEYPAIR); +// let recip_key = conn +// .create_keypair(KeyAlg::Ed25519, None, None, None) +// .await +// .expect(ERR_CREATE_KEYPAIR); + +// let msg = b"message".to_vec(); + +// let packed = conn +// .pack_message( +// vec![recip_key.ident.as_str()], +// Some(&sender_key.ident), +// &msg, +// ) +// .await +// .expect(ERR_PACK); + +// let (unpacked, p_recip, p_send) = conn.unpack_message(&packed).await.expect(ERR_UNPACK); +// assert_eq!(unpacked, msg); +// assert_eq!(p_recip.to_string(), recip_key.ident); +// assert_eq!(p_send.map(|k| k.to_string()), Some(sender_key.ident)); +// } pub async fn db_txn_rollback(db: &Store) { let test_row = Entry::new("category", "name", "value", None); From 88b0e67a0a0493dde5993118eeb48f6c2e69fb6b Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Thu, 15 Apr 2021 18:16:00 -0700 Subject: [PATCH 030/116] remove unused table Signed-off-by: Andrew Whitehead --- src/backend/sqlite/provision.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/backend/sqlite/provision.rs b/src/backend/sqlite/provision.rs index 9a9c4362..f5fe1350 100644 --- a/src/backend/sqlite/provision.rs +++ b/src/backend/sqlite/provision.rs @@ -253,12 +253,6 @@ async fn init_db( CREATE INDEX ix_items_tags_name_enc ON items_tags (name, SUBSTR(value, 1, 12)) WHERE plaintext=0; CREATE INDEX ix_items_tags_name_plain ON items_tags (name, value) WHERE plaintext=1; - CREATE TABLE items_locks ( - id INTEGER NOT NULL, - expiry DATETIME NOT NULL, - PRIMARY KEY (id) - ); - INSERT INTO profiles (name, store_key) VALUES (?1, ?3); COMMIT; From 122eedf219e293434c10eb5f32aa84221449b71f Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Thu, 15 Apr 2021 18:16:30 -0700 Subject: [PATCH 031/116] update value encryption method Signed-off-by: Andrew Whitehead --- src/backend/db_utils.rs | 2 +- src/backend/sqlite/mod.rs | 13 ++++--- src/protect/hmac_key.rs | 19 ++++++----- src/protect/mod.rs | 28 ++++++++++++--- src/protect/store_key.rs | 71 +++++++++++++++++++++++++-------------- src/protect/wrap_key.rs | 24 ++++++------- src/storage/key.rs | 9 ++--- src/storage/types.rs | 3 +- tests/utils/mod.rs | 10 +++--- 9 files changed, 112 insertions(+), 67 deletions(-) diff --git a/src/backend/db_utils.rs b/src/backend/db_utils.rs index eb45d9b3..0e60784a 100644 --- a/src/backend/db_utils.rs +++ b/src/backend/db_utils.rs @@ -516,7 +516,7 @@ pub fn decrypt_scan_entry( key: &StoreKey, ) -> Result { let name = key.decrypt_entry_name(enc_entry.name)?; - let value = key.decrypt_entry_value(enc_entry.value)?; + let value = key.decrypt_entry_value(category.as_str(), name.as_str(), enc_entry.value)?; let tags = if let Some(enc_tags) = enc_entry.tags { Some(key.decrypt_entry_tags( decode_tags(enc_tags).map_err(|_| err_msg!(Unexpected, "Error decoding tags"))?, diff --git a/src/backend/sqlite/mod.rs b/src/backend/sqlite/mod.rs index cacda82a..082ff960 100644 --- a/src/backend/sqlite/mod.rs +++ b/src/backend/sqlite/mod.rs @@ -303,12 +303,12 @@ impl QueryBackend for DbSession { { let value = row.try_get(1)?; let tags = row.try_get(2)?; - let (value, tags) = unblock(move || { - let value = key.decrypt_entry_value(value)?; + let (category, name, value, tags) = unblock(move || { + let value = key.decrypt_entry_value(category.as_str(), name.as_str(), value)?; let enc_tags = decode_tags(tags) .map_err(|_| err_msg!(Unexpected, "Error decoding entry tags"))?; let tags = Some(key.decrypt_entry_tags(enc_tags)?); - Result::<_, Error>::Ok((value, tags)) + Result::<_, Error>::Ok((category, name, value, tags)) }) .await?; Ok(Some(Entry::new(category, name, value, tags))) @@ -409,10 +409,15 @@ impl QueryBackend for DbSession { Box::pin(async move { let (_, key) = acquire_key(&mut *self).await?; let (enc_category, enc_name, enc_value, enc_tags) = unblock(move || { + let enc_value = key.encrypt_entry_value( + category.as_opt_str().unwrap(), + name.as_opt_str().unwrap(), + value, + )?; Result::<_, Error>::Ok(( key.encrypt_entry_category(category)?, key.encrypt_entry_name(name)?, - key.encrypt_entry_value(value)?, + enc_value, tags.transpose()? .map(|t| key.encrypt_entry_tags(t)) .transpose()?, diff --git a/src/protect/hmac_key.rs b/src/protect/hmac_key.rs index 970138e5..ad4b73c4 100644 --- a/src/protect/hmac_key.rs +++ b/src/protect/hmac_key.rs @@ -66,22 +66,25 @@ impl, H> KeyGen for HmacKey { } pub trait HmacOutput { - fn hmac_to(&self, message: &[u8], output: &mut [u8]) -> Result<(), Error>; + fn hmac_to(&self, messages: &[&[u8]], output: &mut [u8]) -> Result<(), Error>; } -impl, H> HmacOutput for HmacKey +impl HmacOutput for HmacKey where + L: ArrayLength, H: BlockInput + Default + Reset + Update + Clone + FixedOutput, { - fn hmac_to(&self, message: &[u8], output: &mut [u8]) -> Result<(), Error> { + fn hmac_to(&self, messages: &[&[u8]], output: &mut [u8]) -> Result<(), Error> { if output.len() > H::OutputSize::USIZE { return Err(err_msg!(Encryption, "invalid length for hmac output")); } - let mut nonce_hmac = + let mut hmac = Hmac::::new_varkey(self.0.as_ref()).map_err(|e| err_msg!(Encryption, "{}", e))?; - nonce_hmac.update(&message); - let nonce_long = nonce_hmac.finalize().into_bytes(); - output.copy_from_slice(&nonce_long[..output.len()]); + for msg in messages { + hmac.update(msg); + } + let hash = hmac.finalize().into_bytes(); + output.copy_from_slice(&hash[..output.len()]); Ok(()) } } @@ -99,7 +102,7 @@ mod tests { )) .unwrap(); let mut output = [0u8; 12]; - key.hmac_to(b"test message", &mut output).unwrap(); + key.hmac_to(&[b"test message"], &mut output).unwrap(); assert_eq!(output, &hex!("4cecfbf6be721395529be686")[..]); } } diff --git a/src/protect/mod.rs b/src/protect/mod.rs index 4541d621..83232457 100644 --- a/src/protect/mod.rs +++ b/src/protect/mod.rs @@ -74,12 +74,22 @@ pub(crate) trait EntryEncryptor { fn encrypt_entry_category(&self, category: SecretBytes) -> Result, Error>; fn encrypt_entry_name(&self, name: SecretBytes) -> Result, Error>; - fn encrypt_entry_value(&self, value: SecretBytes) -> Result, Error>; + fn encrypt_entry_value( + &self, + category: &str, + name: &str, + value: SecretBytes, + ) -> Result, Error>; fn encrypt_entry_tags(&self, tags: Vec) -> Result, Error>; fn decrypt_entry_category(&self, enc_category: Vec) -> Result; fn decrypt_entry_name(&self, enc_name: Vec) -> Result; - fn decrypt_entry_value(&self, enc_value: Vec) -> Result; + fn decrypt_entry_value( + &self, + category: &str, + name: &str, + enc_value: Vec, + ) -> Result; fn decrypt_entry_tags(&self, enc_tags: Vec) -> Result, Error>; } @@ -92,7 +102,12 @@ impl EntryEncryptor for NullEncryptor { fn encrypt_entry_name(&self, name: SecretBytes) -> Result, Error> { Ok(name.into_vec()) } - fn encrypt_entry_value(&self, value: SecretBytes) -> Result, Error> { + fn encrypt_entry_value( + &self, + _category: &str, + _name: &str, + value: SecretBytes, + ) -> Result, Error> { Ok(value.into_vec()) } fn encrypt_entry_tags(&self, tags: Vec) -> Result, Error> { @@ -119,7 +134,12 @@ impl EntryEncryptor for NullEncryptor { fn decrypt_entry_name(&self, enc_name: Vec) -> Result { Ok(String::from_utf8(enc_name).map_err(err_map!(Encryption))?) } - fn decrypt_entry_value(&self, enc_value: Vec) -> Result { + fn decrypt_entry_value( + &self, + _category: &str, + _name: &str, + enc_value: Vec, + ) -> Result { Ok(enc_value.into()) } fn decrypt_entry_tags(&self, enc_tags: Vec) -> Result, Error> { diff --git a/src/protect/store_key.rs b/src/protect/store_key.rs index ecd3ec78..296e9a7e 100644 --- a/src/protect/store_key.rs +++ b/src/protect/store_key.rs @@ -27,7 +27,6 @@ pub type StoreKey = StoreKeyImpl, super::hmac_key::HmacKey { pub category_key: Key, pub name_key: Key, - pub value_key: Key, pub item_hmac_key: HmacKey, pub tag_name_key: Key, pub tag_value_key: Key, @@ -43,7 +42,6 @@ where Ok(Self { category_key: KeyGen::generate()?, name_key: KeyGen::generate()?, - value_key: KeyGen::generate()?, item_hmac_key: KeyGen::generate()?, tag_name_key: KeyGen::generate()?, tag_value_key: KeyGen::generate()?, @@ -84,7 +82,7 @@ where hmac_key: &HmacKey, ) -> Result, Error> { let mut nonce = ArrayKey::::default(); - hmac_key.hmac_to(buffer.as_ref(), nonce.as_mut())?; + hmac_key.hmac_to(&[buffer.as_ref()], nonce.as_mut())?; enc_key.encrypt_in_place(&mut buffer, nonce.as_ref(), &[])?; buffer.buffer_insert_slice(0, nonce.as_ref())?; Ok(buffer.into_vec()) @@ -109,6 +107,23 @@ where Ok(buffer) } + pub fn derive_value_key( + &self, + category: &str, + name: &str, + out: &mut [u8], + ) -> Result<(), Error> { + self.item_hmac_key.hmac_to( + &[ + &(category.len() as u64).to_be_bytes(), + category.as_bytes(), + &(name.len() as u64).to_be_bytes(), + name.as_bytes(), + ], + out, + ) + } + pub fn encrypt_tag_name(&self, name: SecretBytes) -> Result, Error> { Self::encrypt_searchable(name, &self.tag_name_key, &self.tags_hmac_key) } @@ -130,7 +145,6 @@ impl PartialEq for StoreKeyImpl bool { self.category_key == other.category_key && self.name_key == other.name_key - && self.value_key == other.value_key && self.item_hmac_key == other.item_hmac_key && self.tag_name_key == other.tag_name_key && self.tag_value_key == other.tag_value_key @@ -158,13 +172,16 @@ where Self::encrypt_searchable(name, &self.name_key, &self.item_hmac_key) } - fn encrypt_entry_value(&self, value: SecretBytes) -> Result, Error> { - let value_key = Key::generate()?; - let value = Self::encrypt(value, &value_key)?; - let key_input = value_key.with_secret_bytes(|sk| Self::prepare_input(sk.unwrap())); - let mut result = Self::encrypt(key_input, &self.value_key)?; - result.write_slice(value.as_ref())?; - Ok(result) + fn encrypt_entry_value( + &self, + category: &str, + name: &str, + value: SecretBytes, + ) -> Result, Error> { + let mut value_key = ArrayKey::::default(); + self.derive_value_key(category, name, value_key.as_mut())?; + let value_key = Key::from_secret_bytes(value_key.as_ref())?; + Self::encrypt(value, &value_key) } fn decrypt_entry_category(&self, enc_category: Vec) -> Result { @@ -175,19 +192,16 @@ where decode_utf8(Self::decrypt(enc_name, &self.name_key)?.into_vec()) } - fn decrypt_entry_value(&self, mut enc_value: Vec) -> Result { - let enc_key_size = Self::encrypted_size(Key::KeySize::USIZE); - if enc_value.len() < enc_key_size + Self::encrypted_size(0) { - return Err(err_msg!( - Encryption, - "Buffer is too short to represent an encrypted value", - )); - } - let value = Vec::from(&enc_value[enc_key_size..]); - enc_value.buffer_resize(enc_key_size)?; - let value_key = - Key::from_secret_bytes(Self::decrypt(enc_value, &self.value_key)?.as_ref())?; - Self::decrypt(value, &value_key) + fn decrypt_entry_value( + &self, + category: &str, + name: &str, + enc_value: Vec, + ) -> Result { + let mut value_key = ArrayKey::::default(); + self.derive_value_key(category, name, value_key.as_mut())?; + let value_key = Key::from_secret_bytes(value_key.as_ref())?; + Self::decrypt(enc_value, &value_key) } fn encrypt_entry_tags(&self, tags: Vec) -> Result, Error> { @@ -258,7 +272,11 @@ mod tests { .encrypt_entry_name(test_record.name.clone().into()) .unwrap(); let enc_value = key - .encrypt_entry_value(test_record.value.clone().into()) + .encrypt_entry_value( + &test_record.category, + &test_record.name, + test_record.value.clone().into(), + ) .unwrap(); let enc_tags = key .encrypt_entry_tags(test_record.tags.clone().unwrap()) @@ -270,7 +288,8 @@ mod tests { let cmp_record = Entry::new( key.decrypt_entry_category(enc_category).unwrap(), key.decrypt_entry_name(enc_name).unwrap(), - key.decrypt_entry_value(enc_value).unwrap(), + key.decrypt_entry_value(&test_record.category, &test_record.name, enc_value) + .unwrap(), Some(key.decrypt_entry_tags(enc_tags).unwrap()), ); assert_eq!(test_record, cmp_record); diff --git a/src/protect/wrap_key.rs b/src/protect/wrap_key.rs index 2f7960c2..5991f378 100644 --- a/src/protect/wrap_key.rs +++ b/src/protect/wrap_key.rs @@ -276,13 +276,13 @@ mod tests { #[test] fn derived_key_unwrap_expected() { let input = b"test data"; - let wrapped = vec![ - 194, 156, 102, 253, 229, 11, 48, 184, 160, 119, 218, 30, 169, 188, 244, 223, 235, 95, - 171, 234, 18, 5, 9, 115, 174, 208, 232, 37, 31, 32, 250, 216, 32, 92, 253, 45, 236, - ]; + let wrapped = Vec::from(hex!( + "c29c66fde50b30b8a077da1ea9bcf4dfeb5fabea12050973aed0e8251f20fad8205cfd2dec" + )); let pass = PassKey::from("pass"); - let key_ref = WrapKeyReference::parse_uri("kdf:argon2i:13:mod?salt=MR6B1jrReV2JioaizEaRo6") - .expect("Error parsing derived key ref"); + let key_ref = + WrapKeyReference::parse_uri("kdf:argon2i:13:mod?salt=a553cfb9c558b5c11c78efcfa06f3e29") + .expect("Error parsing derived key ref"); let key = key_ref.resolve(pass).expect("Error deriving existing key"); let unwrapped = key.unwrap_data(wrapped).expect("Error unwrapping data"); assert_eq!(unwrapped, &input[..]); @@ -290,12 +290,12 @@ mod tests { #[test] fn derived_key_check_bad_password() { - let wrapped = vec![ - 194, 156, 102, 253, 229, 11, 48, 184, 160, 119, 218, 30, 169, 188, 244, 223, 235, 95, - 171, 234, 18, 5, 9, 115, 174, 208, 232, 37, 31, 32, 250, 216, 32, 92, 253, 45, 236, - ]; - let key_ref = WrapKeyReference::parse_uri("kdf:argon2i:13:mod?salt=MR6B1jrReV2JioaizEaRo6") - .expect("Error parsing derived key ref"); + let wrapped = Vec::from(hex!( + "c29c66fde50b30b8a077da1ea9bcf4dfeb5fabea12050973aed0e8251f20fad8205cfd2dec" + )); + let key_ref = + WrapKeyReference::parse_uri("kdf:argon2i:13:mod?salt=a553cfb9c558b5c11c78efcfa06f3e29") + .expect("Error parsing derived key ref"); let check_bad_pass = key_ref .resolve("not my pass".into()) .expect("Error deriving comparison key"); diff --git a/src/storage/key.rs b/src/storage/key.rs index 80c7b1a4..d6ded048 100644 --- a/src/storage/key.rs +++ b/src/storage/key.rs @@ -77,13 +77,14 @@ pub struct KeyParams { } impl KeyParams { - pub(crate) fn to_vec(&self) -> Result, Error> { - serde_json::to_vec(self) + pub(crate) fn to_bytes(&self) -> Result { + serde_cbor::to_vec(self) + .map(SecretBytes::from) .map_err(|e| err_msg!(Unexpected, "Error serializing key params: {}", e)) } pub(crate) fn from_slice(params: &[u8]) -> Result { - let result = serde_json::from_slice(params) + let result = serde_cbor::from_slice(params) .map_err(|e| err_msg!(Unexpected, "Error deserializing key params: {}", e)); result } @@ -138,7 +139,7 @@ mod tests { reference: None, data: Some(SecretBytes::from(vec![0, 0, 0, 0])), }; - let enc_params = params.to_vec().unwrap(); + let enc_params = params.to_bytes().unwrap(); let p2 = KeyParams::from_slice(&enc_params).unwrap(); assert_eq!(p2, params); } diff --git a/src/storage/types.rs b/src/storage/types.rs index 13597a7b..03acdba9 100644 --- a/src/storage/types.rs +++ b/src/storage/types.rs @@ -6,7 +6,6 @@ use std::{ }; use futures_lite::stream::{Stream, StreamExt}; -use zeroize::Zeroizing; use super::{ entry::{Entry, EntryKind, EntryOperation, EntryTag, TagFilter}, @@ -493,7 +492,7 @@ impl Session { let mut params = KeyParams::from_slice(&row.value)?; params.metadata = metadata.map(str::to_string); - let value = Zeroizing::new(params.to_vec()?); + let value = params.to_bytes()?; self.0 .update( diff --git a/tests/utils/mod.rs b/tests/utils/mod.rs index 4adca855..4c2ff6a0 100644 --- a/tests/utils/mod.rs +++ b/tests/utils/mod.rs @@ -13,12 +13,10 @@ const ERR_REPLACE: &'static str = "Error replacing test row"; const ERR_REMOVE_ALL: &'static str = "Error removing test rows"; const ERR_SCAN: &'static str = "Error starting scan"; const ERR_SCAN_NEXT: &'static str = "Error fetching scan rows"; -const ERR_CREATE_KEYPAIR: &'static str = "Error creating keypair"; -const ERR_FETCH_KEY: &'static str = "Error fetching key"; -const ERR_SIGN: &'static str = "Error signing message"; -const ERR_VERIFY: &'static str = "Error verifying signature"; -const ERR_PACK: &'static str = "Error packing message"; -const ERR_UNPACK: &'static str = "Error unpacking message"; +// const ERR_CREATE_KEYPAIR: &'static str = "Error creating keypair"; +// const ERR_FETCH_KEY: &'static str = "Error fetching key"; +// const ERR_SIGN: &'static str = "Error signing message"; +// const ERR_VERIFY: &'static str = "Error verifying signature"; pub async fn db_create_remove_profile(db: &Store) { let profile = db.create_profile(None).await.expect(ERR_PROFILE); From 51835ecc9368c8ac30df0ed9e74945be20b1b828 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Thu, 15 Apr 2021 18:41:35 -0700 Subject: [PATCH 032/116] rename StoreKey to ProfileKey, fix tests Signed-off-by: Andrew Whitehead --- src/backend/db_utils.rs | 51 ++++++++++------- src/backend/sqlite/mod.rs | 44 +++++++------- src/backend/sqlite/provision.rs | 18 +++--- src/protect/mod.rs | 18 +++--- src/protect/{store_key.rs => profile_key.rs} | 60 +++++++++++--------- 5 files changed, 105 insertions(+), 86 deletions(-) rename src/protect/{store_key.rs => profile_key.rs} (85%) diff --git a/src/backend/db_utils.rs b/src/backend/db_utils.rs index 0e60784a..d92e85e9 100644 --- a/src/backend/db_utils.rs +++ b/src/backend/db_utils.rs @@ -10,7 +10,7 @@ use sqlx::{ use crate::{ error::Error, future::BoxFuture, - protect::{EntryEncryptor, KeyCache, PassKey, ProfileId, StoreKey, WrapKey, WrapKeyMethod}, + protect::{EntryEncryptor, KeyCache, PassKey, ProfileId, ProfileKey, WrapKey, WrapKeyMethod}, storage::{ entry::{EncEntryTag, Entry, EntryTag, TagFilter}, wql::{ @@ -79,7 +79,7 @@ impl DbSession { } } - pub(crate) fn profile_and_key(&mut self) -> Option<(ProfileId, Arc)> { + pub(crate) fn profile_and_key(&mut self) -> Option<(ProfileId, Arc)> { if let DbSessionKey::Active { profile_id, ref key, @@ -171,7 +171,7 @@ impl<'q, DB: ExtDatabase> Drop for DbSession { } pub(crate) trait GetProfileKey<'a, DB: Database> { - type Fut: Future), Error>>; + type Fut: Future), Error>>; fn call_once( self, conn: &'a mut PoolConnection, @@ -183,7 +183,7 @@ pub(crate) trait GetProfileKey<'a, DB: Database> { impl<'a, DB: Database, F, Fut> GetProfileKey<'a, DB> for F where F: FnOnce(&'a mut PoolConnection, Arc, String) -> Fut, - Fut: Future), Error>> + 'a, + Fut: Future), Error>> + 'a, { type Fut = Fut; fn call_once( @@ -200,7 +200,7 @@ where pub(crate) enum DbSessionKey { Active { profile_id: ProfileId, - key: Arc, + key: Arc, }, Pending { cache: Arc, @@ -501,7 +501,7 @@ pub(crate) fn decode_tags(tags: Vec) -> Result, ()> { pub fn decrypt_scan_batch( category: String, enc_rows: Vec, - key: &StoreKey, + key: &ProfileKey, ) -> Result, Error> { let mut batch = Vec::with_capacity(enc_rows.len()); for enc_entry in enc_rows { @@ -513,7 +513,7 @@ pub fn decrypt_scan_batch( pub fn decrypt_scan_entry( category: String, enc_entry: EncScanEntry, - key: &StoreKey, + key: &ProfileKey, ) -> Result { let name = key.decrypt_entry_name(enc_entry.name)?; let value = key.decrypt_entry_value(category.as_str(), name.as_str(), enc_entry.value)?; @@ -535,14 +535,14 @@ pub fn expiry_timestamp(expire_ms: i64) -> Result { pub fn encode_tag_filter( tag_filter: Option, - key: &StoreKey, + key: &ProfileKey, offset: usize, ) -> Result>)>, Error> { if let Some(tag_filter) = tag_filter { let tag_query = tag_query(tag_filter.query)?; let mut enc = TagSqlEncoder::new( - |name| Ok(key.encrypt_tag_name(StoreKey::prepare_input(name.as_bytes()))?), - |value| Ok(key.encrypt_tag_value(StoreKey::prepare_input(value.as_bytes()))?), + |name| Ok(key.encrypt_tag_name(ProfileKey::prepare_input(name.as_bytes()))?), + |value| Ok(key.encrypt_tag_value(ProfileKey::prepare_input(value.as_bytes()))?), ); if let Some(filter) = enc.encode_query(&tag_query)? { let filter = replace_arg_placeholders::(&filter, (offset as i64) + 1); @@ -563,17 +563,21 @@ pub fn prepare_tags(tags: &[EntryTag]) -> Result, Error> { result.push(match tag { EntryTag::Plaintext(name, value) => EntryTag::Plaintext( unsafe { - String::from_utf8_unchecked(StoreKey::prepare_input(name.as_bytes()).into_vec()) + String::from_utf8_unchecked( + ProfileKey::prepare_input(name.as_bytes()).into_vec(), + ) }, value.clone(), ), EntryTag::Encrypted(name, value) => EntryTag::Encrypted( unsafe { - String::from_utf8_unchecked(StoreKey::prepare_input(name.as_bytes()).into_vec()) + String::from_utf8_unchecked( + ProfileKey::prepare_input(name.as_bytes()).into_vec(), + ) }, unsafe { String::from_utf8_unchecked( - StoreKey::prepare_input(value.as_bytes()).into_vec(), + ProfileKey::prepare_input(value.as_bytes()).into_vec(), ) }, ), @@ -608,15 +612,20 @@ where pub fn init_keys<'a>( method: WrapKeyMethod, pass_key: PassKey<'a>, -) -> Result<(StoreKey, Vec, WrapKey, String), Error> { +) -> Result<(ProfileKey, Vec, WrapKey, String), Error> { let (wrap_key, wrap_key_ref) = method.resolve(pass_key)?; - let store_key = StoreKey::new()?; - let enc_store_key = encode_store_key(&store_key, &wrap_key)?; - Ok((store_key, enc_store_key, wrap_key, wrap_key_ref.into_uri())) -} - -pub fn encode_store_key(store_key: &StoreKey, wrap_key: &WrapKey) -> Result, Error> { - wrap_key.wrap_data(store_key.to_bytes()?) + let profile_key = ProfileKey::new()?; + let enc_profile_key = encode_profile_key(&profile_key, &wrap_key)?; + Ok(( + profile_key, + enc_profile_key, + wrap_key, + wrap_key_ref.into_uri(), + )) +} + +pub fn encode_profile_key(profile_key: &ProfileKey, wrap_key: &WrapKey) -> Result, Error> { + wrap_key.wrap_data(profile_key.to_bytes()?) } #[inline] diff --git a/src/backend/sqlite/mod.rs b/src/backend/sqlite/mod.rs index 082ff960..790a6b73 100644 --- a/src/backend/sqlite/mod.rs +++ b/src/backend/sqlite/mod.rs @@ -16,13 +16,13 @@ use sqlx::{ use crate::{ backend::db_utils::{ - decode_tags, decrypt_scan_batch, encode_store_key, encode_tag_filter, expiry_timestamp, + decode_tags, decrypt_scan_batch, encode_profile_key, encode_tag_filter, expiry_timestamp, extend_query, prepare_tags, random_profile_name, DbSession, DbSessionActive, DbSessionRef, EncScanEntry, ExtDatabase, QueryParams, QueryPrepare, PAGE_SIZE, }, error::Error, future::{unblock, BoxFuture}, - protect::{EntryEncryptor, KeyCache, PassKey, ProfileId, StoreKey, WrapKeyMethod}, + protect::{EntryEncryptor, KeyCache, PassKey, ProfileId, ProfileKey, WrapKeyMethod}, storage::entry::{EncEntryTag, Entry, EntryKind, EntryOperation, EntryTag, TagFilter}, storage::types::{Backend, QueryBackend, Scan}, }; @@ -97,11 +97,11 @@ impl Backend for SqliteStore { fn create_profile(&self, name: Option) -> BoxFuture<'_, Result> { let name = name.unwrap_or_else(random_profile_name); Box::pin(async move { - let key = StoreKey::new()?; + let key = ProfileKey::new()?; let enc_key = key.to_bytes()?; let mut conn = self.conn_pool.acquire().await?; let done = - sqlx::query("INSERT OR IGNORE INTO profiles (name, store_key) VALUES (?1, ?2)") + sqlx::query("INSERT OR IGNORE INTO profiles (name, profile_key) VALUES (?1, ?2)") .bind(&name) .bind(enc_key.into_vec()) .execute(&mut conn) @@ -142,23 +142,23 @@ impl Backend for SqliteStore { let (wrap_key, wrap_key_ref) = unblock(move || method.resolve(pass_key)).await?; let wrap_key = Arc::new(wrap_key); let mut txn = self.conn_pool.begin().await?; - let mut rows = sqlx::query("SELECT id, store_key FROM profiles").fetch(&mut txn); + let mut rows = sqlx::query("SELECT id, profile_key FROM profiles").fetch(&mut txn); let mut upd_keys = BTreeMap::>::new(); while let Some(row) = rows.next().await { let row = row?; let pid = row.try_get(0)?; let enc_key = row.try_get(1)?; - let store_key = self.key_cache.load_key(enc_key).await?; + let profile_key = self.key_cache.load_key(enc_key).await?; let upd_key = unblock({ let wrap_key = wrap_key.clone(); - move || encode_store_key(&store_key, &wrap_key) + move || encode_profile_key(&profile_key, &wrap_key) }) .await?; upd_keys.insert(pid, upd_key); } drop(rows); for (pid, key) in upd_keys { - if sqlx::query("UPDATE profiles SET store_key=?1 WHERE id=?2") + if sqlx::query("UPDATE profiles SET profile_key=?1 WHERE id=?2") .bind(key) .bind(pid) .execute(&mut txn) @@ -166,7 +166,7 @@ impl Backend for SqliteStore { .rows_affected() != 1 { - return Err(err_msg!(Backend, "Error updating profile store key")); + return Err(err_msg!(Backend, "Error updating profile key")); } } if sqlx::query("UPDATE config SET value=?1 WHERE name='wrap_key'") @@ -240,7 +240,7 @@ impl QueryBackend for DbSession { category: &'q str, tag_filter: Option, ) -> BoxFuture<'q, Result> { - let category = StoreKey::prepare_input(category.as_bytes()); + let category = ProfileKey::prepare_input(category.as_bytes()); Box::pin(async move { let (profile_id, key) = acquire_key(&mut *self).await?; @@ -282,8 +282,8 @@ impl QueryBackend for DbSession { let (profile_id, key) = acquire_key(&mut *self).await?; let (enc_category, enc_name) = unblock({ let key = key.clone(); - let category = StoreKey::prepare_input(category.as_bytes()); - let name = StoreKey::prepare_input(name.as_bytes()); + let category = ProfileKey::prepare_input(category.as_bytes()); + let name = ProfileKey::prepare_input(name.as_bytes()); move || { Result::<_, Error>::Ok(( key.encrypt_entry_category(category)?, @@ -359,7 +359,7 @@ impl QueryBackend for DbSession { category: &'q str, tag_filter: Option, ) -> BoxFuture<'q, Result> { - let category = StoreKey::prepare_input(category.as_bytes()); + let category = ProfileKey::prepare_input(category.as_bytes()); Box::pin(async move { let (profile_id, key) = acquire_key(&mut *self).await?; @@ -399,12 +399,12 @@ impl QueryBackend for DbSession { tags: Option<&'q [EntryTag]>, expiry_ms: Option, ) -> BoxFuture<'q, Result<(), Error>> { - let category = StoreKey::prepare_input(category.as_bytes()); - let name = StoreKey::prepare_input(name.as_bytes()); + let category = ProfileKey::prepare_input(category.as_bytes()); + let name = ProfileKey::prepare_input(name.as_bytes()); match operation { op @ EntryOperation::Insert | op @ EntryOperation::Replace => { - let value = StoreKey::prepare_input(value.unwrap()); + let value = ProfileKey::prepare_input(value.unwrap()); let tags = tags.map(prepare_tags); Box::pin(async move { let (_, key) = acquire_key(&mut *self).await?; @@ -483,7 +483,9 @@ impl ExtDatabase for Sqlite { } } -async fn acquire_key(session: &mut DbSession) -> Result<(ProfileId, Arc), Error> { +async fn acquire_key( + session: &mut DbSession, +) -> Result<(ProfileId, Arc), Error> { if let Some(ret) = session.profile_and_key() { Ok(ret) } else { @@ -502,11 +504,11 @@ async fn resolve_profile_key( conn: &mut PoolConnection, cache: Arc, profile: String, -) -> Result<(ProfileId, Arc), Error> { +) -> Result<(ProfileId, Arc), Error> { if let Some((pid, key)) = cache.get_profile(profile.as_str()).await { Ok((pid, key)) } else { - if let Some(row) = sqlx::query("SELECT id, store_key FROM profiles WHERE name=?1") + if let Some(row) = sqlx::query("SELECT id, profile_key FROM profiles WHERE name=?1") .bind(profile.as_str()) .fetch_optional(conn) .await? @@ -583,7 +585,7 @@ async fn perform_remove<'q>( fn perform_scan<'q>( mut active: DbSessionRef<'q, Sqlite>, profile_id: ProfileId, - key: Arc, + key: Arc, kind: EntryKind, category: String, tag_filter: Option, @@ -596,7 +598,7 @@ fn perform_scan<'q>( params.push(kind as i16); let (enc_category, tag_filter) = unblock({ let key = key.clone(); - let category = StoreKey::prepare_input(category.as_bytes()); + let category = ProfileKey::prepare_input(category.as_bytes()); let params_len = params.len() + 1; // plus category move || { Result::<_, Error>::Ok(( diff --git a/src/backend/sqlite/provision.rs b/src/backend/sqlite/provision.rs index f5fe1350..6574c984 100644 --- a/src/backend/sqlite/provision.rs +++ b/src/backend/sqlite/provision.rs @@ -194,7 +194,7 @@ async fn init_db( method: WrapKeyMethod, pass_key: PassKey<'_>, ) -> Result { - let (store_key, enc_store_key, wrap_key, wrap_key_ref) = unblock({ + let (profile_key, enc_profile_key, wrap_key, wrap_key_ref) = unblock({ let pass_key = pass_key.into_owned(); move || init_keys(method, pass_key) }) @@ -220,7 +220,7 @@ async fn init_db( id INTEGER NOT NULL, name TEXT NOT NULL, reference TEXT NULL, - store_key BLOB NULL, + profile_key BLOB NULL, PRIMARY KEY(id) ); CREATE UNIQUE INDEX ix_profile_name ON profiles (name); @@ -253,7 +253,7 @@ async fn init_db( CREATE INDEX ix_items_tags_name_enc ON items_tags (name, SUBSTR(value, 1, 12)) WHERE plaintext=0; CREATE INDEX ix_items_tags_name_plain ON items_tags (name, value) WHERE plaintext=1; - INSERT INTO profiles (name, store_key) VALUES (?1, ?3); + INSERT INTO profiles (name, profile_key) VALUES (?1, ?3); COMMIT; "#, @@ -261,7 +261,7 @@ async fn init_db( .persistent(false) .bind(profile_name) .bind(wrap_key_ref) - .bind(enc_store_key) + .bind(enc_profile_key) .execute(&mut conn) .await?; @@ -272,7 +272,7 @@ async fn init_db( .bind(profile_name) .fetch_one(&mut conn) .await?; - key_cache.add_profile_mut(profile_name.to_string(), row.try_get(0)?, store_key); + key_cache.add_profile_mut(profile_name.to_string(), row.try_get(0)?, profile_key); Ok(key_cache) } @@ -323,7 +323,7 @@ async fn open_db( let wrap_ref = WrapKeyReference::parse_uri(&wrap_key_ref)?; if let Some(method) = method { if !wrap_ref.compare_method(&method) { - return Err(err_msg!(Input, "Store key wrap method mismatch")); + return Err(err_msg!(Input, "Store wrap key method mismatch")); } } unblock({ @@ -336,13 +336,13 @@ async fn open_db( }; let mut key_cache = KeyCache::new(wrap_key); - let row = sqlx::query("SELECT id, store_key FROM profiles WHERE name = ?1") + let row = sqlx::query("SELECT id, profile_key FROM profiles WHERE name = ?1") .bind(&profile) .fetch_one(&mut conn) .await?; let profile_id = row.try_get(0)?; - let store_key = key_cache.load_key(row.try_get(1)?).await?; - key_cache.add_profile_mut(profile.clone(), profile_id, store_key); + let profile_key = key_cache.load_key(row.try_get(1)?).await?; + key_cache.add_profile_mut(profile.clone(), profile_id, profile_key); Ok(Store::new(SqliteStore::new( conn_pool, profile, key_cache, path, diff --git a/src/protect/mod.rs b/src/protect/mod.rs index 83232457..23970315 100644 --- a/src/protect/mod.rs +++ b/src/protect/mod.rs @@ -10,8 +10,8 @@ mod hmac_key; mod pass_key; pub use self::pass_key::PassKey; -mod store_key; -pub use self::store_key::StoreKey; +mod profile_key; +pub use self::profile_key::ProfileKey; mod wrap_key; pub use self::wrap_key::{generate_raw_wrap_key, WrapKey, WrapKeyMethod, WrapKeyReference}; @@ -27,7 +27,7 @@ pub type ProfileId = i64; #[derive(Debug)] pub struct KeyCache { - profile_info: Mutex)>>, + profile_info: Mutex)>>, pub(crate) wrap_key: Arc, } @@ -39,30 +39,30 @@ impl KeyCache { } } - pub async fn load_key(&self, ciphertext: Vec) -> Result { + pub async fn load_key(&self, ciphertext: Vec) -> Result { let wrap_key = self.wrap_key.clone(); unblock(move || { let mut data = wrap_key .unwrap_data(ciphertext) - .map_err(err_map!(Encryption, "Error decrypting store key"))?; - let key = StoreKey::from_slice(&data)?; + .map_err(err_map!(Encryption, "Error decrypting profile key"))?; + let key = ProfileKey::from_slice(&data)?; data.zeroize(); Ok(key) }) .await } - pub fn add_profile_mut(&mut self, ident: String, pid: ProfileId, key: StoreKey) { + pub fn add_profile_mut(&mut self, ident: String, pid: ProfileId, key: ProfileKey) { self.profile_info .get_mut() .insert(ident, (pid, Arc::new(key))); } - pub async fn add_profile(&self, ident: String, pid: ProfileId, key: Arc) { + pub async fn add_profile(&self, ident: String, pid: ProfileId, key: Arc) { self.profile_info.lock().await.insert(ident, (pid, key)); } - pub async fn get_profile(&self, name: &str) -> Option<(ProfileId, Arc)> { + pub async fn get_profile(&self, name: &str) -> Option<(ProfileId, Arc)> { self.profile_info.lock().await.get(name).cloned() } } diff --git a/src/protect/store_key.rs b/src/protect/profile_key.rs similarity index 85% rename from src/protect/store_key.rs rename to src/protect/profile_key.rs index 296e9a7e..fff77ec5 100644 --- a/src/protect/store_key.rs +++ b/src/protect/profile_key.rs @@ -15,25 +15,30 @@ use crate::{ storage::entry::{EncEntryTag, EntryTag}, }; -pub type StoreKey = StoreKeyImpl, super::hmac_key::HmacKey>; +pub type ProfileKey = ProfileKeyImpl, super::hmac_key::HmacKey>; -/// A store key combining the keys required to encrypt -/// and decrypt storage records +/// A record combining the keys required to encrypt and decrypt storage entries #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(bound( deserialize = "Key: for<'a> Deserialize<'a>, HmacKey: for<'a> Deserialize<'a>", serialize = "Key: Serialize, HmacKey: Serialize" ))] -pub struct StoreKeyImpl { +pub struct ProfileKeyImpl { + #[serde(rename = "ick")] pub category_key: Key, + #[serde(rename = "ink")] pub name_key: Key, + #[serde(rename = "ihk")] pub item_hmac_key: HmacKey, + #[serde(rename = "tnk")] pub tag_name_key: Key, + #[serde(rename = "tvk")] pub tag_value_key: Key, + #[serde(rename = "thk")] pub tags_hmac_key: HmacKey, } -impl StoreKeyImpl +impl ProfileKeyImpl where Key: KeyGen, HmacKey: KeyGen, @@ -50,7 +55,7 @@ where } } -impl StoreKeyImpl +impl ProfileKeyImpl where Key: Serialize + for<'de> Deserialize<'de>, HmacKey: Serialize + for<'de> Deserialize<'de>, @@ -58,15 +63,15 @@ where pub fn to_bytes(&self) -> Result { serde_cbor::to_vec(self) .map(SecretBytes::from) - .map_err(err_map!(Unexpected, "Error serializing store key")) + .map_err(err_map!(Unexpected, "Error serializing profile key")) } pub fn from_slice(input: &[u8]) -> Result { - serde_cbor::from_slice(input).map_err(err_map!(Unsupported, "Invalid store key")) + serde_cbor::from_slice(input).map_err(err_map!(Unsupported, "Invalid profile key")) } } -impl StoreKeyImpl +impl ProfileKeyImpl where Key: KeyGen + KeyMeta + KeyAeadInPlace + KeyAeadMeta + KeySecretBytes, HmacKey: KeyGen + HmacOutput, @@ -141,7 +146,7 @@ where } } -impl PartialEq for StoreKeyImpl { +impl PartialEq for ProfileKeyImpl { fn eq(&self, other: &Self) -> bool { self.category_key == other.category_key && self.name_key == other.name_key @@ -151,9 +156,9 @@ impl PartialEq for StoreKeyImpl Eq for StoreKeyImpl {} +impl Eq for ProfileKeyImpl {} -impl EntryEncryptor for StoreKeyImpl +impl EntryEncryptor for ProfileKeyImpl where Key: KeyGen + KeyMeta + KeyAeadInPlace + KeyAeadMeta + KeySecretBytes, HmacKey: KeyGen + HmacOutput, @@ -255,7 +260,7 @@ mod tests { #[test] fn encrypt_entry_round_trip() { - let key = StoreKey::new().unwrap(); + let key = ProfileKey::new().unwrap(); let test_record = Entry::new( "category", "name", @@ -295,24 +300,27 @@ mod tests { assert_eq!(test_record, cmp_record); } - // #[test] - // fn store_key_searchable() { - // const NONCE_SIZE: usize = 12; - // let input = SecretBytes::from(&b"hello"[..]); - // let key = EncKey::::random_key(); - // let hmac_key = EncKey::::random(); - // let enc1 = encrypt_searchable::(input.clone(), &key, &hmac_key).unwrap(); - // let enc2 = encrypt_searchable::(input.clone(), &key, &hmac_key).unwrap(); - // assert_eq!(&enc1[0..NONCE_SIZE], &enc2[0..NONCE_SIZE]); - // let dec = ChaChaEncrypt::decrypt(enc1, &key).unwrap(); - // assert_eq!(dec, input); - // } + #[test] + fn check_encrypt_searchable() { + let input = SecretBytes::from(&b"hello"[..]); + let key = Chacha20Key::::generate().unwrap(); + let hmac_key = crate::protect::hmac_key::HmacKey::::generate().unwrap(); + let enc1 = ProfileKey::encrypt_searchable(input.clone(), &key, &hmac_key).unwrap(); + let enc2 = ProfileKey::encrypt_searchable(input.clone(), &key, &hmac_key).unwrap(); + let enc3 = ProfileKey::encrypt(input.clone(), &key).unwrap(); + assert_eq!(&enc1, &enc2); + assert_ne!(&enc1, &enc3); + let dec = ProfileKey::decrypt(enc1, &key).unwrap(); + assert_eq!(dec, input); + } #[test] fn serialize_round_trip() { - let key = StoreKey::new().unwrap(); + let key = ProfileKey::new().unwrap(); let key_cbor = serde_cbor::to_vec(&key).unwrap(); let key_cmp = serde_cbor::from_slice(&key_cbor).unwrap(); + println!("len: {}", key_cbor.len()); + assert!(false); assert_eq!(key, key_cmp); } } From 1e1f3a1beb75f3bbda7dd93aff8f8ae6fd382ee5 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Thu, 15 Apr 2021 19:56:34 -0700 Subject: [PATCH 033/116] update postgres backend Signed-off-by: Andrew Whitehead --- Cargo.toml | 4 +- src/backend/db_utils.rs | 2 +- src/backend/mod.rs | 2 +- src/backend/postgres/mod.rs | 100 ++++++++++++++++-------------- src/backend/postgres/provision.rs | 66 ++++++++++---------- src/backend/postgres/test_db.rs | 28 +++++---- src/backend/sqlite/mod.rs | 11 ++-- src/error.rs | 2 - src/ffi/error.rs | 6 +- src/ffi/macros.rs | 2 +- src/ffi/store.rs | 30 ++++----- src/protect/mod.rs | 16 ++--- src/protect/profile_key.rs | 34 +++++----- src/storage/wql/sql.rs | 22 +++---- src/storage/wql/tags.rs | 46 +++++++------- tests/backends.rs | 2 +- wrappers/python/demo/perf.py | 2 +- wrappers/python/demo/test.py | 2 +- 18 files changed, 193 insertions(+), 184 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c11de114..480fbfe4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,8 +24,8 @@ no-default-features = true rustdoc-args = ["--cfg", "docsrs"] [features] -default = ["all", "ffi", "logger"] -all = ["any", "sqlite"] # "postgres" +default = ["all_backends", "ffi", "logger"] +all_backends = ["any", "postgres", "sqlite"] any = [] ffi = ["any", "ffi-support", "logger"] indy_compat = ["sqlx", "sqlx/sqlite"] diff --git a/src/backend/db_utils.rs b/src/backend/db_utils.rs index d92e85e9..2fa7aa7f 100644 --- a/src/backend/db_utils.rs +++ b/src/backend/db_utils.rs @@ -516,7 +516,7 @@ pub fn decrypt_scan_entry( key: &ProfileKey, ) -> Result { let name = key.decrypt_entry_name(enc_entry.name)?; - let value = key.decrypt_entry_value(category.as_str(), name.as_str(), enc_entry.value)?; + let value = key.decrypt_entry_value(category.as_bytes(), name.as_bytes(), enc_entry.value)?; let tags = if let Some(enc_tags) = enc_entry.tags { Some(key.decrypt_entry_tags( decode_tags(enc_tags).map_err(|_| err_msg!(Unexpected, "Error decoding tags"))?, diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 7721cae8..36b85f47 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -6,7 +6,7 @@ pub mod any; #[cfg(any(feature = "postgres", feature = "sqlite"))] -mod db_utils; +pub(crate) mod db_utils; #[cfg(feature = "postgres")] #[cfg_attr(docsrs, doc(cfg(feature = "postgres")))] diff --git a/src/backend/postgres/mod.rs b/src/backend/postgres/mod.rs index a83209ea..bac65522 100644 --- a/src/backend/postgres/mod.rs +++ b/src/backend/postgres/mod.rs @@ -16,17 +16,17 @@ use sqlx::{ }; use crate::{ + backend::db_utils::{ + decode_tags, decrypt_scan_batch, encode_profile_key, encode_tag_filter, expiry_timestamp, + extend_query, prepare_tags, random_profile_name, replace_arg_placeholders, DbSession, + DbSessionActive, DbSessionRef, EncScanEntry, ExtDatabase, QueryParams, QueryPrepare, + PAGE_SIZE, + }, error::Error, future::{unblock, BoxFuture}, - keys::{store::StoreKey, wrap::WrapKeyMethod, EntryEncryptor, KeyCache, PassKey}, + protect::{EntryEncryptor, KeyCache, PassKey, ProfileId, ProfileKey, WrapKeyMethod}, storage::{ - db_utils::{ - decode_tags, decrypt_scan_batch, encode_store_key, encode_tag_filter, expiry_timestamp, - extend_query, prepare_tags, random_profile_name, replace_arg_placeholders, DbSession, - DbSessionActive, DbSessionRef, EncScanEntry, ExtDatabase, QueryParams, QueryPrepare, - PAGE_SIZE, - }, - entry::{EncEntryTag, Entry, EntryKind, EntryOperation, EntryTag, ProfileId, TagFilter}, + entry::{EncEntryTag, Entry, EntryKind, EntryOperation, EntryTag, TagFilter}, types::{Backend, QueryBackend, Scan}, }, }; @@ -68,7 +68,7 @@ const TAG_INSERT_QUERY: &'static str = "INSERT INTO items_tags mod provision; pub use provision::PostgresStoreOptions; -#[cfg(feature = "pg_test")] +#[cfg(any(test, feature = "pg_test"))] pub mod test_db; /// A PostgreSQL database store @@ -104,15 +104,15 @@ impl Backend for PostgresStore { fn create_profile(&self, name: Option) -> BoxFuture<'_, Result> { let name = name.unwrap_or_else(random_profile_name); Box::pin(async move { - let key = StoreKey::new()?; - let enc_key = key.to_string()?; + let key = ProfileKey::new()?; + let enc_key = key.to_bytes()?; let mut conn = self.conn_pool.acquire().await?; if let Some(pid) = sqlx::query_scalar( - "INSERT INTO profiles (name, store_key) VALUES ($1, $2) + "INSERT INTO profiles (name, profile_key) VALUES ($1, $2) ON CONFLICT DO NOTHING RETURNING id", ) .bind(&name) - .bind(enc_key.as_bytes()) + .bind(enc_key.as_ref()) .fetch_optional(&mut conn) .await? { @@ -152,23 +152,23 @@ impl Backend for PostgresStore { let (wrap_key, wrap_key_ref) = unblock(move || method.resolve(pass_key)).await?; let wrap_key = Arc::new(wrap_key); let mut txn = self.conn_pool.begin().await?; - let mut rows = sqlx::query("SELECT id, store_key FROM profiles").fetch(&mut txn); + let mut rows = sqlx::query("SELECT id, profile_key FROM profiles").fetch(&mut txn); let mut upd_keys = BTreeMap::>::new(); while let Some(row) = rows.next().await { let row = row?; let pid = row.try_get(0)?; let enc_key = row.try_get(1)?; - let store_key = self.key_cache.load_key(enc_key).await?; + let profile_key = self.key_cache.load_key(enc_key).await?; let upd_key = unblock({ let wrap_key = wrap_key.clone(); - move || encode_store_key(&store_key, &wrap_key) + move || encode_profile_key(&profile_key, &wrap_key) }) .await?; upd_keys.insert(pid, upd_key); } drop(rows); for (pid, key) in upd_keys { - if sqlx::query("UPDATE profiles SET store_key=$1 WHERE id=$2") + if sqlx::query("UPDATE profiles SET profile_key=$1 WHERE id=$2") .bind(key) .bind(pid) .execute(&mut txn) @@ -176,7 +176,7 @@ impl Backend for PostgresStore { .rows_affected() != 1 { - return Err(err_msg!(Backend, "Error updating profile store key")); + return Err(err_msg!(Backend, "Error updating profile key")); } } if sqlx::query("UPDATE config SET value=$1 WHERE name='wrap_key'") @@ -261,7 +261,7 @@ impl QueryBackend for DbSession { category: &'q str, tag_filter: Option, ) -> BoxFuture<'q, Result> { - let category = StoreKey::prepare_input(category.as_bytes()); + let category = ProfileKey::prepare_input(category.as_bytes()); Box::pin(async move { let (profile_id, key) = acquire_key(&mut *self).await?; @@ -271,7 +271,7 @@ impl QueryBackend for DbSession { let (enc_category, tag_filter) = unblock({ let params_len = params.len() + 1; // plus category move || { - Result::Ok(( + Result::<_, Error>::Ok(( key.encrypt_entry_category(category)?, encode_tag_filter::(tag_filter, &key, params_len)?, )) @@ -303,10 +303,10 @@ impl QueryBackend for DbSession { let (profile_id, key) = acquire_key(&mut *self).await?; let (enc_category, enc_name) = unblock({ let key = key.clone(); - let category = StoreKey::prepare_input(category.as_bytes()); - let name = StoreKey::prepare_input(name.as_bytes()); + let category = ProfileKey::prepare_input(category.as_bytes()); + let name = ProfileKey::prepare_input(name.as_bytes()); move || { - Result::Ok(( + Result::<_, Error>::Ok(( key.encrypt_entry_category(category)?, key.encrypt_entry_name(name)?, )) @@ -328,8 +328,8 @@ impl QueryBackend for DbSession { { let value = row.try_get(1)?; let tags = row.try_get::, _>(2)?.map(String::into_bytes); - let (value, tags) = unblock(move || { - let value = key.decrypt_entry_value(value)?; + let (category, name, value, tags) = unblock(move || { + let value = key.decrypt_entry_value(category.as_ref(), name.as_ref(), value)?; let tags = if let Some(enc_tags) = tags { Some( key.decrypt_entry_tags( @@ -340,7 +340,7 @@ impl QueryBackend for DbSession { } else { None }; - Result::Ok((value, tags)) + Result::<_, Error>::Ok((category, name, value, tags)) }) .await?; Ok(Some(Entry::new(category, name, value, tags))) @@ -393,7 +393,7 @@ impl QueryBackend for DbSession { category: &'q str, tag_filter: Option, ) -> BoxFuture<'q, Result> { - let category = StoreKey::prepare_input(category.as_bytes()); + let category = ProfileKey::prepare_input(category.as_bytes()); Box::pin(async move { let (profile_id, key) = acquire_key(&mut *self).await?; @@ -403,7 +403,7 @@ impl QueryBackend for DbSession { let (enc_category, tag_filter) = unblock({ let params_len = params.len() + 1; // plus category move || { - Result::Ok(( + Result::<_, Error>::Ok(( key.encrypt_entry_category(category)?, encode_tag_filter::(tag_filter, &key, params_len)?, )) @@ -438,21 +438,25 @@ impl QueryBackend for DbSession { tags: Option<&'q [EntryTag]>, expiry_ms: Option, ) -> BoxFuture<'q, Result<(), Error>> { - let category = StoreKey::prepare_input(category.as_bytes()); - let name = StoreKey::prepare_input(name.as_bytes()); + let category = ProfileKey::prepare_input(category.as_bytes()); + let name = ProfileKey::prepare_input(name.as_bytes()); match operation { EntryOperation::Insert => { - let value = StoreKey::prepare_input(value.unwrap()); + let value = ProfileKey::prepare_input(value.unwrap()); let tags = tags.map(prepare_tags); Box::pin(async move { let (_, key) = acquire_key(&mut *self).await?; let (enc_category, enc_name, enc_value, enc_tags) = unblock(move || { - Result::Ok(( + let enc_value = + key.encrypt_entry_value(category.as_ref(), name.as_ref(), value)?; + Result::<_, Error>::Ok(( key.encrypt_entry_category(category)?, key.encrypt_entry_name(name)?, - key.encrypt_entry_value(value)?, - tags.map(|t| key.encrypt_entry_tags(t)).transpose()?, + enc_value, + tags.transpose()? + .map(|t| key.encrypt_entry_tags(t)) + .transpose()?, )) }) .await?; @@ -473,16 +477,20 @@ impl QueryBackend for DbSession { }) } EntryOperation::Replace => { - let value = StoreKey::prepare_input(value.unwrap()); + let value = ProfileKey::prepare_input(value.unwrap()); let tags = tags.map(prepare_tags); Box::pin(async move { let (_, key) = acquire_key(&mut *self).await?; let (enc_category, enc_name, enc_value, enc_tags) = unblock(move || { - Result::Ok(( + let enc_value = + key.encrypt_entry_value(category.as_ref(), name.as_ref(), value)?; + Result::<_, Error>::Ok(( key.encrypt_entry_category(category)?, key.encrypt_entry_name(name)?, - key.encrypt_entry_value(value)?, - tags.map(|t| key.encrypt_entry_tags(t)).transpose()?, + enc_value, + tags.transpose()? + .map(|t| key.encrypt_entry_tags(t)) + .transpose()?, )) }) .await?; @@ -508,7 +516,7 @@ impl QueryBackend for DbSession { EntryOperation::Remove => Box::pin(async move { let (_, key) = acquire_key(&mut *self).await?; let (enc_category, enc_name) = unblock(move || { - Result::Ok(( + Result::<_, Error>::Ok(( key.encrypt_entry_category(category)?, key.encrypt_entry_name(name)?, )) @@ -556,7 +564,7 @@ impl QueryPrepare for PostgresStore { async fn acquire_key( session: &mut DbSession, -) -> Result<(ProfileId, Arc), Error> { +) -> Result<(ProfileId, Arc), Error> { if let Some(ret) = session.profile_and_key() { Ok(ret) } else { @@ -575,11 +583,11 @@ async fn resolve_profile_key( conn: &mut PoolConnection, cache: Arc, profile: String, -) -> Result<(ProfileId, Arc), Error> { +) -> Result<(ProfileId, Arc), Error> { if let Some((pid, key)) = cache.get_profile(profile.as_str()).await { Ok((pid, key)) } else { - if let Some(row) = sqlx::query("SELECT id, store_key FROM profiles WHERE name=?1") + if let Some(row) = sqlx::query("SELECT id, profile_key FROM profiles WHERE name=?1") .bind(profile.as_str()) .fetch_optional(conn) .await? @@ -653,7 +661,7 @@ async fn perform_remove<'q>( fn perform_scan<'q>( mut active: DbSessionRef<'q, Postgres>, profile_id: ProfileId, - key: Arc, + key: Arc, kind: EntryKind, category: String, tag_filter: Option, @@ -667,10 +675,10 @@ fn perform_scan<'q>( params.push(kind as i16); let (enc_category, tag_filter) = unblock({ let key = key.clone(); - let category = StoreKey::prepare_input(category.as_bytes()); + let category = ProfileKey::prepare_input(category.as_bytes()); let params_len = params.len() + 1; // plus category move || { - Result::Ok(( + Result::<_, Error>::Ok(( key.encrypt_entry_category(category)?, encode_tag_filter::(tag_filter, &key, params_len)? )) @@ -706,7 +714,7 @@ fn perform_scan<'q>( #[cfg(test)] mod tests { use super::*; - use crate::db_utils::replace_arg_placeholders; + use crate::backend::db_utils::replace_arg_placeholders; #[test] fn postgres_simple_and_convert_args_works() { diff --git a/src/backend/postgres/provision.rs b/src/backend/postgres/provision.rs index e0732803..7fe48619 100644 --- a/src/backend/postgres/provision.rs +++ b/src/backend/postgres/provision.rs @@ -7,16 +7,16 @@ use sqlx::{ ConnectOptions, Connection, Error as SqlxError, Executor, Row, Transaction, }; -use crate::db_utils::{init_keys, random_profile_name}; -use crate::error::Result; -use crate::future::{unblock, BoxFuture}; -use crate::keys::{ - wrap::{WrapKeyMethod, WrapKeyReference}, - KeyCache, PassKey, +use crate::{ + backend::db_utils::{init_keys, random_profile_name}, + error::Error, + future::{unblock, BoxFuture}, + protect::{KeyCache, PassKey, ProfileId, WrapKeyMethod, WrapKeyReference}, + storage::{ + options::IntoOptions, + types::{ManageBackend, Store}, + }, }; -use crate::options::IntoOptions; -use crate::store::{ManageBackend, Store}; -use crate::types::ProfileId; use super::PostgresStore; @@ -40,7 +40,7 @@ pub struct PostgresStoreOptions { impl PostgresStoreOptions { /// Initialize `PostgresStoreOptions` from a generic set of options - pub fn new<'a, O>(options: O) -> Result + pub fn new<'a, O>(options: O) -> Result where O: IntoOptions<'a>, { @@ -110,7 +110,7 @@ impl PostgresStoreOptions { }) } - async fn pool(&self) -> std::result::Result { + async fn pool(&self) -> Result { #[allow(unused_mut)] let mut conn_opts = PgConnectOptions::from_str(self.uri.as_str())?; #[cfg(feature = "log")] @@ -128,7 +128,7 @@ impl PostgresStoreOptions { .await } - pub(crate) async fn create_db_pool(&self) -> Result { + pub(crate) async fn create_db_pool(&self) -> Result { // try connecting normally in case the database exists match self.pool().await { Ok(pool) => Ok(pool), @@ -173,7 +173,7 @@ impl PostgresStoreOptions { pass_key: PassKey<'_>, profile: Option<&str>, recreate: bool, - ) -> Result> { + ) -> Result, Error> { let conn_pool = self.create_db_pool().await?; let mut txn = conn_pool.begin().await?; @@ -203,7 +203,7 @@ impl PostgresStoreOptions { // no 'config' table, assume empty database } - let (store_key, enc_store_key, wrap_key, wrap_key_ref) = unblock({ + let (profile_key, enc_profile_key, wrap_key, wrap_key_ref) = unblock({ let pass_key = pass_key.into_owned(); move || init_keys(method, pass_key) }) @@ -211,9 +211,9 @@ impl PostgresStoreOptions { let default_profile = profile .map(str::to_string) .unwrap_or_else(random_profile_name); - let profile_id = init_db(txn, &default_profile, wrap_key_ref, enc_store_key).await?; + let profile_id = init_db(txn, &default_profile, wrap_key_ref, enc_profile_key).await?; let mut key_cache = KeyCache::new(wrap_key); - key_cache.add_profile_mut(default_profile.clone(), profile_id, store_key); + key_cache.add_profile_mut(default_profile.clone(), profile_id, profile_key); Ok(Store::new(PostgresStore::new( conn_pool, @@ -230,7 +230,7 @@ impl PostgresStoreOptions { method: Option, pass_key: PassKey<'_>, profile: Option<&str>, - ) -> Result> { + ) -> Result, Error> { let pool = match self.pool().await { Ok(p) => Ok(p), Err(SqlxError::Database(db_err)) if db_err.code() == Some(Cow::Borrowed("3D000")) => { @@ -244,7 +244,7 @@ impl PostgresStoreOptions { } /// Remove an existing Postgres store defined by these configuration options - pub async fn remove(self) -> Result { + pub async fn remove(self) -> Result { let mut admin_conn = PgConnection::connect(self.admin_uri.as_ref()).await?; // any character except NUL is allowed in an identifier. // double quotes must be escaped, but we just disallow those @@ -272,7 +272,7 @@ impl<'a> ManageBackend<'a> for PostgresStoreOptions { method: Option, pass_key: PassKey<'_>, profile: Option<&'a str>, - ) -> BoxFuture<'a, Result>> { + ) -> BoxFuture<'a, Result, Error>> { let pass_key = pass_key.into_owned(); Box::pin(self.open(method, pass_key, profile)) } @@ -283,12 +283,12 @@ impl<'a> ManageBackend<'a> for PostgresStoreOptions { pass_key: PassKey<'_>, profile: Option<&'a str>, recreate: bool, - ) -> BoxFuture<'a, Result>> { + ) -> BoxFuture<'a, Result, Error>> { let pass_key = pass_key.into_owned(); Box::pin(self.provision(method, pass_key, profile, recreate)) } - fn remove_backend(self) -> BoxFuture<'a, Result> { + fn remove_backend(self) -> BoxFuture<'a, Result> { Box::pin(self.remove()) } } @@ -297,8 +297,8 @@ pub(crate) async fn init_db<'t>( mut txn: Transaction<'t, Postgres>, profile_name: &str, wrap_key_ref: String, - enc_store_key: Vec, -) -> Result { + enc_profile_key: Vec, +) -> Result { txn.execute( " CREATE TABLE config ( @@ -311,7 +311,7 @@ pub(crate) async fn init_db<'t>( id BIGSERIAL, name TEXT NOT NULL, reference TEXT NULL, - store_key BYTEA NULL, + profile_key BYTEA NULL, PRIMARY KEY(id) ); CREATE UNIQUE INDEX ix_profile_name ON profiles(name); @@ -360,9 +360,9 @@ pub(crate) async fn init_db<'t>( .await?; let profile_id = - sqlx::query_scalar("INSERT INTO profiles (name, store_key) VALUES ($1, $2) RETURNING id") + sqlx::query_scalar("INSERT INTO profiles (name, profile_key) VALUES ($1, $2) RETURNING id") .bind(profile_name) - .bind(enc_store_key) + .bind(enc_profile_key) .fetch_one(&mut txn) .await?; @@ -371,12 +371,12 @@ pub(crate) async fn init_db<'t>( Ok(profile_id) } -pub(crate) async fn reset_db(conn: &mut PgConnection) -> Result<()> { +pub(crate) async fn reset_db(conn: &mut PgConnection) -> Result<(), Error> { conn.execute( " DROP TABLE IF EXISTS config, profiles, - store_keys, keys, + profile_keys, keys, items, items_tags; ", ) @@ -391,7 +391,7 @@ pub(crate) async fn open_db( profile: Option<&str>, host: String, name: String, -) -> Result> { +) -> Result, Error> { let mut conn = conn_pool.acquire().await?; let mut ver_ok = false; let mut default_profile: Option = None; @@ -431,7 +431,7 @@ pub(crate) async fn open_db( let wrap_ref = WrapKeyReference::parse_uri(&wrap_key_ref)?; if let Some(method) = method { if !wrap_ref.compare_method(&method) { - return Err(err_msg!(Input, "Store key wrap method mismatch")); + return Err(err_msg!(Input, "Store wrap key method mismatch")); } } unblock({ @@ -444,13 +444,13 @@ pub(crate) async fn open_db( }; let mut key_cache = KeyCache::new(wrap_key); - let row = sqlx::query("SELECT id, store_key FROM profiles WHERE name = $1") + let row = sqlx::query("SELECT id, profile_key FROM profiles WHERE name = $1") .bind(&profile) .fetch_one(&mut conn) .await?; let profile_id = row.try_get(0)?; - let store_key = key_cache.load_key(row.try_get(1)?).await?; - key_cache.add_profile_mut(profile.clone(), profile_id, store_key); + let profile_key = key_cache.load_key(row.try_get(1)?).await?; + key_cache.add_profile_mut(profile.clone(), profile_id, profile_key); Ok(Store::new(PostgresStore::new( conn_pool, profile, key_cache, host, name, diff --git a/src/backend/postgres/test_db.rs b/src/backend/postgres/test_db.rs index 3dd494b6..2546991f 100644 --- a/src/backend/postgres/test_db.rs +++ b/src/backend/postgres/test_db.rs @@ -1,3 +1,5 @@ +//! Store wrapper for running tests against a postgres database + use sqlx::{ postgres::{PgConnection, Postgres}, Connection, Database, TransactionManager, @@ -6,30 +8,32 @@ use std::time::Duration; use super::provision::{init_db, reset_db, PostgresStoreOptions}; use super::PostgresStore; -use crate::db_utils::{init_keys, random_profile_name}; -use crate::error::Result; -use crate::future::{block_on, unblock}; -use crate::keys::{ - wrap::{generate_raw_wrap_key, WrapKeyMethod}, - KeyCache, +use crate::{ + backend::db_utils::{init_keys, random_profile_name}, + error::Error, + future::{block_on, unblock}, + protect::{generate_raw_wrap_key, KeyCache, WrapKeyMethod}, + storage::types::Store, }; -use crate::store::Store; +#[derive(Debug)] +/// Postgres test database wrapper instance pub struct TestDB { inst: Option>, lock_txn: Option, } impl TestDB { - #[allow(unused)] - pub async fn provision() -> Result { + /// Provision a new instance of the test database. + /// This method blocks until the database lock can be acquired. + pub async fn provision() -> Result { let path = match std::env::var("POSTGRES_URL") { Ok(p) if !p.is_empty() => p, _ => panic!("'POSTGRES_URL' must be defined"), }; let key = generate_raw_wrap_key(None)?; - let (store_key, enc_store_key, wrap_key, wrap_key_ref) = + let (profile_key, enc_profile_key, wrap_key, wrap_key_ref) = unblock(|| init_keys(WrapKeyMethod::RawKey, key)).await?; let default_profile = random_profile_name(); @@ -59,10 +63,10 @@ impl TestDB { reset_db(&mut *init_txn).await?; // create tables and add default profile - let profile_id = init_db(init_txn, &default_profile, wrap_key_ref, enc_store_key).await?; + let profile_id = init_db(init_txn, &default_profile, wrap_key_ref, enc_profile_key).await?; let mut key_cache = KeyCache::new(wrap_key); - key_cache.add_profile_mut(default_profile.clone(), profile_id, store_key); + key_cache.add_profile_mut(default_profile.clone(), profile_id, profile_key); let inst = Store::new(PostgresStore::new( conn_pool, default_profile, diff --git a/src/backend/sqlite/mod.rs b/src/backend/sqlite/mod.rs index 790a6b73..aa015b30 100644 --- a/src/backend/sqlite/mod.rs +++ b/src/backend/sqlite/mod.rs @@ -103,7 +103,7 @@ impl Backend for SqliteStore { let done = sqlx::query("INSERT OR IGNORE INTO profiles (name, profile_key) VALUES (?1, ?2)") .bind(&name) - .bind(enc_key.into_vec()) + .bind(enc_key.as_ref()) .execute(&mut conn) .await?; if done.rows_affected() == 0 { @@ -304,7 +304,7 @@ impl QueryBackend for DbSession { let value = row.try_get(1)?; let tags = row.try_get(2)?; let (category, name, value, tags) = unblock(move || { - let value = key.decrypt_entry_value(category.as_str(), name.as_str(), value)?; + let value = key.decrypt_entry_value(category.as_ref(), name.as_ref(), value)?; let enc_tags = decode_tags(tags) .map_err(|_| err_msg!(Unexpected, "Error decoding entry tags"))?; let tags = Some(key.decrypt_entry_tags(enc_tags)?); @@ -409,11 +409,8 @@ impl QueryBackend for DbSession { Box::pin(async move { let (_, key) = acquire_key(&mut *self).await?; let (enc_category, enc_name, enc_value, enc_tags) = unblock(move || { - let enc_value = key.encrypt_entry_value( - category.as_opt_str().unwrap(), - name.as_opt_str().unwrap(), - value, - )?; + let enc_value = + key.encrypt_entry_value(category.as_ref(), name.as_ref(), value)?; Result::<_, Error>::Ok(( key.encrypt_entry_category(category)?, key.encrypt_entry_name(name)?, diff --git a/src/error.rs b/src/error.rs index 659cbd5f..bb95f6c1 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,8 +1,6 @@ use std::error::Error as StdError; use std::fmt::{self, Display, Formatter}; -pub type Result = std::result::Result; - /// The possible kinds of error produced by the crate #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum ErrorKind { diff --git a/src/ffi/error.rs b/src/ffi/error.rs index a9e3f665..5c595236 100644 --- a/src/ffi/error.rs +++ b/src/ffi/error.rs @@ -1,4 +1,4 @@ -use crate::error::{Error, ErrorKind, Result}; +use crate::error::{Error, ErrorKind}; use std::os::raw::c_char; use std::sync::RwLock; @@ -38,8 +38,8 @@ impl From for ErrorCode { } } -impl From> for ErrorCode { - fn from(result: Result) -> ErrorCode { +impl From> for ErrorCode { + fn from(result: Result) -> ErrorCode { match result { Ok(_) => ErrorCode::Success, Err(err) => ErrorCode::from(err.kind()), diff --git a/src/ffi/macros.rs b/src/ffi/macros.rs index f9b87915..0f0410e7 100644 --- a/src/ffi/macros.rs +++ b/src/ffi/macros.rs @@ -1,6 +1,6 @@ macro_rules! catch_err { ($($e:tt)*) => { - match std::panic::catch_unwind(move || -> $crate::error::Result<_> {$($e)*}) { + match std::panic::catch_unwind(move || -> Result<_, $crate::error::Error> {$($e)*}) { Ok(Ok(a)) => a, Ok(Err(err)) => { // lib error $crate::ffi::error::set_last_error(Some(err)) diff --git a/src/ffi/store.rs b/src/ffi/store.rs index e12abe07..9be38c22 100644 --- a/src/ffi/store.rs +++ b/src/ffi/store.rs @@ -20,7 +20,7 @@ use zeroize::Zeroize; use super::{error::set_last_error, CallbackId, EnsureCallback, ErrorCode}; use crate::{ backend::any::{AnySession, AnyStore}, - error::Result as KvResult, + error::Error, future::spawn_ok, protect::{PassKey, WrapKeyMethod}, storage::{ @@ -49,7 +49,7 @@ impl StoreHandle { handle } - pub async fn load(&self) -> KvResult> { + pub async fn load(&self) -> Result, Error> { FFI_STORES .lock() .await @@ -58,7 +58,7 @@ impl StoreHandle { .ok_or_else(|| err_msg!("Invalid store handle")) } - pub async fn remove(&self) -> KvResult> { + pub async fn remove(&self) -> Result, Error> { FFI_STORES .lock() .await @@ -79,7 +79,7 @@ impl SessionHandle { handle } - pub async fn load(&self) -> KvResult> { + pub async fn load(&self) -> Result, Error> { Ok(Mutex::lock_arc( FFI_SESSIONS .lock() @@ -90,7 +90,7 @@ impl SessionHandle { .await) } - pub async fn remove(&self) -> KvResult>> { + pub async fn remove(&self) -> Result>, Error> { FFI_SESSIONS .lock() .await @@ -107,7 +107,7 @@ impl ScanHandle { handle } - pub async fn borrow(&self) -> KvResult> { + pub async fn borrow(&self) -> Result, Error> { FFI_SCANS .lock() .await @@ -117,7 +117,7 @@ impl ScanHandle { .ok_or_else(|| err_msg!(Busy, "Scan handle in use")) } - pub async fn release(&self, value: Scan<'static, Entry>) -> KvResult<()> { + pub async fn release(&self, value: Scan<'static, Entry>) -> Result<(), Error> { FFI_SCANS .lock() .await @@ -127,7 +127,7 @@ impl ScanHandle { Ok(()) } - pub async fn remove(&self) -> KvResult> { + pub async fn remove(&self) -> Result, Error> { FFI_SCANS .lock() .await @@ -368,7 +368,7 @@ pub extern "C" fn askar_store_remove( trace!("Remove store"); let cb = cb.ok_or_else(|| err_msg!("No callback provided"))?; let spec_uri = spec_uri.into_opt_string().ok_or_else(|| err_msg!("No store URI provided"))?; - let cb = EnsureCallback::new(move |result: KvResult| + let cb = EnsureCallback::new(move |result: Result| match result { Ok(removed) => cb(cb_id, ErrorCode::Success, removed as i8), Err(err) => cb(cb_id, set_last_error(Some(err)), 0), @@ -562,7 +562,7 @@ pub extern "C" fn askar_scan_start( let profile = profile.into_opt_string(); let category = category.into_opt_string().ok_or_else(|| err_msg!("Category not provided"))?; let tag_filter = tag_filter.as_opt_str().map(TagFilter::from_str).transpose()?; - let cb = EnsureCallback::new(move |result: KvResult| + let cb = EnsureCallback::new(move |result: Result| match result { Ok(scan_handle) => { info!("Started scan {} on store {}", scan_handle, handle); @@ -592,7 +592,7 @@ pub extern "C" fn askar_scan_next( catch_err! { trace!("Scan store next"); let cb = cb.ok_or_else(|| err_msg!("No callback provided"))?; - let cb = EnsureCallback::new(move |result: KvResult>>| + let cb = EnsureCallback::new(move |result: Result>,Error>| match result { Ok(Some(entries)) => { let results = EntrySetHandle::create(FfiEntrySet::from(entries)); @@ -665,7 +665,7 @@ pub extern "C" fn askar_session_start( trace!("Session start"); let profile = profile.into_opt_string(); let cb = cb.ok_or_else(|| err_msg!("No callback provided"))?; - let cb = EnsureCallback::new(move |result: KvResult| + let cb = EnsureCallback::new(move |result: Result| match result { Ok(sess_handle) => { info!("Started session {} on store {} (txn: {})", sess_handle, handle, as_transaction != 0); @@ -703,7 +703,7 @@ pub extern "C" fn askar_session_count( let cb = cb.ok_or_else(|| err_msg!("No callback provided"))?; let category = category.into_opt_string().ok_or_else(|| err_msg!("Category not provided"))?; let tag_filter = tag_filter.as_opt_str().map(TagFilter::from_str).transpose()?; - let cb = EnsureCallback::new(move |result: KvResult| + let cb = EnsureCallback::new(move |result: Result| match result { Ok(count) => cb(cb_id, ErrorCode::Success, count), Err(err) => cb(cb_id, set_last_error(Some(err)), 0), @@ -734,7 +734,7 @@ pub extern "C" fn askar_session_fetch( let cb = cb.ok_or_else(|| err_msg!("No callback provided"))?; let category = category.into_opt_string().ok_or_else(|| err_msg!("Category not provided"))?; let name = name.into_opt_string().ok_or_else(|| err_msg!("Name not provided"))?; - let cb = EnsureCallback::new(move |result: KvResult>| + let cb = EnsureCallback::new(move |result: Result,Error>| match result { Ok(Some(entry)) => { let results = Box::into_raw(Box::new(FfiEntrySet::from(entry))); @@ -1018,7 +1018,7 @@ pub extern "C" fn askar_session_close( } } -fn export_key_entry(key_entry: KeyEntry) -> KvResult { +fn export_key_entry(key_entry: KeyEntry) -> Result { let KeyEntry { category, ident, diff --git a/src/protect/mod.rs b/src/protect/mod.rs index 23970315..6055fd28 100644 --- a/src/protect/mod.rs +++ b/src/protect/mod.rs @@ -76,8 +76,8 @@ pub(crate) trait EntryEncryptor { fn encrypt_entry_name(&self, name: SecretBytes) -> Result, Error>; fn encrypt_entry_value( &self, - category: &str, - name: &str, + category: &[u8], + name: &[u8], value: SecretBytes, ) -> Result, Error>; fn encrypt_entry_tags(&self, tags: Vec) -> Result, Error>; @@ -86,8 +86,8 @@ pub(crate) trait EntryEncryptor { fn decrypt_entry_name(&self, enc_name: Vec) -> Result; fn decrypt_entry_value( &self, - category: &str, - name: &str, + category: &[u8], + name: &[u8], enc_value: Vec, ) -> Result; fn decrypt_entry_tags(&self, enc_tags: Vec) -> Result, Error>; @@ -104,8 +104,8 @@ impl EntryEncryptor for NullEncryptor { } fn encrypt_entry_value( &self, - _category: &str, - _name: &str, + _category: &[u8], + _name: &[u8], value: SecretBytes, ) -> Result, Error> { Ok(value.into_vec()) @@ -136,8 +136,8 @@ impl EntryEncryptor for NullEncryptor { } fn decrypt_entry_value( &self, - _category: &str, - _name: &str, + _category: &[u8], + _name: &[u8], enc_value: Vec, ) -> Result { Ok(enc_value.into()) diff --git a/src/protect/profile_key.rs b/src/protect/profile_key.rs index fff77ec5..b67070de 100644 --- a/src/protect/profile_key.rs +++ b/src/protect/profile_key.rs @@ -114,16 +114,16 @@ where pub fn derive_value_key( &self, - category: &str, - name: &str, + category: &[u8], + name: &[u8], out: &mut [u8], ) -> Result<(), Error> { self.item_hmac_key.hmac_to( &[ - &(category.len() as u64).to_be_bytes(), - category.as_bytes(), - &(name.len() as u64).to_be_bytes(), - name.as_bytes(), + &(category.len() as u32).to_be_bytes(), + category, + &(name.len() as u32).to_be_bytes(), + name, ], out, ) @@ -179,8 +179,8 @@ where fn encrypt_entry_value( &self, - category: &str, - name: &str, + category: &[u8], + name: &[u8], value: SecretBytes, ) -> Result, Error> { let mut value_key = ArrayKey::::default(); @@ -199,8 +199,8 @@ where fn decrypt_entry_value( &self, - category: &str, - name: &str, + category: &[u8], + name: &[u8], enc_value: Vec, ) -> Result { let mut value_key = ArrayKey::::default(); @@ -278,8 +278,8 @@ mod tests { .unwrap(); let enc_value = key .encrypt_entry_value( - &test_record.category, - &test_record.name, + test_record.category.as_bytes(), + test_record.name.as_bytes(), test_record.value.clone().into(), ) .unwrap(); @@ -293,8 +293,12 @@ mod tests { let cmp_record = Entry::new( key.decrypt_entry_category(enc_category).unwrap(), key.decrypt_entry_name(enc_name).unwrap(), - key.decrypt_entry_value(&test_record.category, &test_record.name, enc_value) - .unwrap(), + key.decrypt_entry_value( + test_record.category.as_bytes(), + test_record.name.as_bytes(), + enc_value, + ) + .unwrap(), Some(key.decrypt_entry_tags(enc_tags).unwrap()), ); assert_eq!(test_record, cmp_record); @@ -319,8 +323,6 @@ mod tests { let key = ProfileKey::new().unwrap(); let key_cbor = serde_cbor::to_vec(&key).unwrap(); let key_cmp = serde_cbor::from_slice(&key_cbor).unwrap(); - println!("len: {}", key_cbor.len()); - assert!(false); assert_eq!(key, key_cmp); } } diff --git a/src/storage/wql/sql.rs b/src/storage/wql/sql.rs index 2fdb3a4c..e429019e 100644 --- a/src/storage/wql/sql.rs +++ b/src/storage/wql/sql.rs @@ -3,7 +3,7 @@ use std::marker::PhantomData; use itertools::Itertools; use super::tags::{CompareOp, ConjunctionOp, TagName, TagQueryEncoder}; -use crate::error::Result; +use crate::error::Error; pub struct TagSqlEncoder<'e, EN, EV> { pub enc_name: EN, @@ -14,8 +14,8 @@ pub struct TagSqlEncoder<'e, EN, EV> { impl<'e, EN, EV> TagSqlEncoder<'e, EN, EV> where - EN: Fn(&str) -> Result> + 'e, - EV: Fn(&str) -> Result> + 'e, + EN: Fn(&str) -> Result, Error> + 'e, + EV: Fn(&str) -> Result, Error> + 'e, { pub fn new(enc_name: EN, enc_value: EV) -> Self { Self { @@ -29,19 +29,19 @@ where impl<'e, EN, EV> TagQueryEncoder for TagSqlEncoder<'e, EN, EV> where - EN: Fn(&str) -> Result> + 'e, - EV: Fn(&str) -> Result> + 'e, + EN: Fn(&str) -> Result, Error> + 'e, + EV: Fn(&str) -> Result, Error> + 'e, { type Arg = Vec; type Clause = String; - fn encode_name(&mut self, name: &TagName) -> Result { + fn encode_name(&mut self, name: &TagName) -> Result { Ok(match name { TagName::Encrypted(name) | TagName::Plaintext(name) => (&self.enc_name)(name)?, }) } - fn encode_value(&mut self, value: &String, is_plaintext: bool) -> Result { + fn encode_value(&mut self, value: &String, is_plaintext: bool) -> Result { Ok(if is_plaintext { value.as_bytes().to_vec() } else { @@ -55,7 +55,7 @@ where enc_name: Self::Arg, enc_value: Self::Arg, is_plaintext: bool, - ) -> Result> { + ) -> Result, Error> { let idx = self.arguments.len(); let (op_prefix, match_prefix) = match (is_plaintext, op.as_sql_str_for_prefix()) { (false, Some(pfx_op)) if enc_value.len() > 12 => { @@ -93,7 +93,7 @@ where enc_values: Vec, is_plaintext: bool, negate: bool, - ) -> Result> { + ) -> Result, Error> { let args_in = std::iter::repeat("$$") .take(enc_values.len()) .intersperse(", ") @@ -114,7 +114,7 @@ where enc_name: Self::Arg, is_plaintext: bool, negate: bool, - ) -> Result> { + ) -> Result, Error> { let query = format!( "i.id {} (SELECT item_id FROM items_tags WHERE name = $$ AND plaintext = {})", if negate { "NOT IN" } else { "IN" }, @@ -128,7 +128,7 @@ where &mut self, op: ConjunctionOp, clauses: Vec, - ) -> Result> { + ) -> Result, Error> { let qc = clauses.len(); if qc == 0 { if op == ConjunctionOp::Or { diff --git a/src/storage/wql/tags.rs b/src/storage/wql/tags.rs index d4ecd3a3..43909124 100644 --- a/src/storage/wql/tags.rs +++ b/src/storage/wql/tags.rs @@ -1,9 +1,9 @@ use super::{AbstractQuery, Query}; -use crate::error::Result; +use crate::error::Error; pub type TagQuery = AbstractQuery; -pub fn tag_query(query: Query) -> Result { +pub fn tag_query(query: Query) -> Result { let result = query .map_names(&mut |k| { if k.starts_with("~") { @@ -17,7 +17,7 @@ pub fn tag_query(query: Query) -> Result { Ok(result) } -pub fn validate_tag_query(_query: &TagQuery) -> Result<()> { +pub fn validate_tag_query(_query: &TagQuery) -> Result<(), Error> { // FIXME only equality comparison supported for encrypted keys Ok(()) } @@ -47,16 +47,16 @@ pub trait TagQueryEncoder { type Arg; type Clause; - fn encode_query(&mut self, query: &TagQuery) -> Result> + fn encode_query(&mut self, query: &TagQuery) -> Result, Error> where Self: Sized, { encode_tag_query(query, self, false) } - fn encode_name(&mut self, name: &TagName) -> Result; + fn encode_name(&mut self, name: &TagName) -> Result; - fn encode_value(&mut self, value: &String, is_plaintext: bool) -> Result; + fn encode_value(&mut self, value: &String, is_plaintext: bool) -> Result; fn encode_op_clause( &mut self, @@ -64,7 +64,7 @@ pub trait TagQueryEncoder { enc_name: Self::Arg, enc_value: Self::Arg, is_plaintext: bool, - ) -> Result>; + ) -> Result, Error>; fn encode_in_clause( &mut self, @@ -72,20 +72,20 @@ pub trait TagQueryEncoder { enc_values: Vec, is_plaintext: bool, negate: bool, - ) -> Result>; + ) -> Result, Error>; fn encode_exist_clause( &mut self, enc_name: Self::Arg, is_plaintext: bool, negate: bool, - ) -> Result>; + ) -> Result, Error>; fn encode_conj_clause( &mut self, op: ConjunctionOp, clauses: Vec, - ) -> Result>; + ) -> Result, Error>; } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -160,7 +160,7 @@ impl ConjunctionOp { } } -fn encode_tag_query(query: &TagQuery, enc: &mut E, negate: bool) -> Result> +fn encode_tag_query(query: &TagQuery, enc: &mut E, negate: bool) -> Result, Error> where E: TagQueryEncoder, { @@ -202,7 +202,7 @@ fn encode_tag_op( value: &String, enc: &mut E, negate: bool, -) -> Result> +) -> Result, Error> where E: TagQueryEncoder, { @@ -222,7 +222,7 @@ fn encode_tag_in( values: &Vec, enc: &mut E, negate: bool, -) -> Result> +) -> Result, Error> where E: TagQueryEncoder, { @@ -234,12 +234,12 @@ where let enc_values = values .into_iter() .map(|val| enc.encode_value(val, is_plaintext)) - .collect::>>()?; + .collect::, Error>>()?; enc.encode_in_clause(enc_name, enc_values, is_plaintext, negate) } -fn encode_tag_exist(names: &[TagName], enc: &mut E, negate: bool) -> Result> +fn encode_tag_exist(names: &[TagName], enc: &mut E, negate: bool) -> Result, Error> where E: TagQueryEncoder, { @@ -270,7 +270,7 @@ fn encode_tag_conj( subqueries: &Vec, enc: &mut E, negate: bool, -) -> Result> +) -> Result, Error> where E: TagQueryEncoder, { @@ -278,7 +278,7 @@ where let clauses = subqueries .into_iter() .flat_map(|q| encode_tag_query(q, enc, negate).transpose()) - .collect::>>()?; + .collect::, Error>>()?; enc.encode_conj_clause(op, clauses) } @@ -295,11 +295,11 @@ mod tests { type Arg = String; type Clause = String; - fn encode_name(&mut self, name: &TagName) -> Result { + fn encode_name(&mut self, name: &TagName) -> Result { Ok(name.to_string()) } - fn encode_value(&mut self, value: &String, _is_plaintext: bool) -> Result { + fn encode_value(&mut self, value: &String, _is_plaintext: bool) -> Result { Ok(value.clone()) } @@ -309,7 +309,7 @@ mod tests { name: Self::Arg, value: Self::Arg, _is_plaintext: bool, - ) -> Result> { + ) -> Result, Error> { Ok(Some(format!("{} {} {}", name, op.as_sql_str(), value))) } @@ -318,7 +318,7 @@ mod tests { name: Self::Arg, _is_plaintext: bool, negate: bool, - ) -> Result> { + ) -> Result, Error> { let op = if negate { "NOT EXIST" } else { "EXIST" }; Ok(Some(format!("{}({})", op, name))) } @@ -329,7 +329,7 @@ mod tests { values: Vec, _is_plaintext: bool, negate: bool, - ) -> Result> { + ) -> Result, Error> { let op = if negate { "NOT IN" } else { "IN" }; let value = values .iter() @@ -343,7 +343,7 @@ mod tests { &mut self, op: ConjunctionOp, clauses: Vec, - ) -> Result> { + ) -> Result, Error> { let mut r = String::new(); r.push_str("("); r.extend( diff --git a/tests/backends.rs b/tests/backends.rs index de2693a0..fbb2f2e7 100644 --- a/tests/backends.rs +++ b/tests/backends.rs @@ -309,7 +309,7 @@ mod sqlite { #[cfg(feature = "pg_test")] mod postgres { - use aries_askar::postgres::test_db::TestDB; + use aries_askar::backend::postgres::test_db::TestDB; async fn init_db() -> TestDB { env_logger::builder().is_test(true).try_init().unwrap_or(()); diff --git a/wrappers/python/demo/perf.py b/wrappers/python/demo/perf.py index fee8ca83..10bd20dc 100644 --- a/wrappers/python/demo/perf.py +++ b/wrappers/python/demo/perf.py @@ -15,7 +15,7 @@ if len(sys.argv) > 1: REPO_URI = sys.argv[1] if REPO_URI == "postgres": - REPO_URI = "postgres://postgres:pgpass@localhost:5432/askar-test" + REPO_URI = "postgres://postgres:mysecretpassword@localhost:5432/askar-test" else: REPO_URI = "sqlite://:memory:" diff --git a/wrappers/python/demo/test.py b/wrappers/python/demo/test.py index c7522cd0..4d299732 100644 --- a/wrappers/python/demo/test.py +++ b/wrappers/python/demo/test.py @@ -16,7 +16,7 @@ if len(sys.argv) > 1: REPO_URI = sys.argv[1] if REPO_URI == "postgres": - REPO_URI = "postgres://postgres:pgpass@localhost:5432/askar-test" + REPO_URI = "postgres://postgres:mysecretpassword@localhost:5432/askar-test" else: REPO_URI = "sqlite://:memory:" From 2b96f6eb43e2ba96a8acefabf66aa41c636ddb79 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Thu, 15 Apr 2021 20:01:54 -0700 Subject: [PATCH 034/116] remove unsupported methods from python wrapper Signed-off-by: Andrew Whitehead --- wrappers/python/aries_askar/__init__.py | 4 +- wrappers/python/aries_askar/bindings.py | 98 +------------------------ wrappers/python/demo/test.py | 46 ++++-------- 3 files changed, 19 insertions(+), 129 deletions(-) diff --git a/wrappers/python/aries_askar/__init__.py b/wrappers/python/aries_askar/__init__.py index 111b15f1..bddb50a7 100644 --- a/wrappers/python/aries_askar/__init__.py +++ b/wrappers/python/aries_askar/__init__.py @@ -1,14 +1,12 @@ """aries-askar Python wrapper library""" -from .bindings import derive_verkey, generate_raw_key, verify_signature, version +from .bindings import generate_raw_key, version from .error import StoreError, StoreErrorCode from .store import Session, Store from .types import Entry, KeyAlg __all__ = ( - "derive_verkey", "generate_raw_key", - "verify_signature", "version", "Entry", "KeyAlg", diff --git a/wrappers/python/aries_askar/bindings.py b/wrappers/python/aries_askar/bindings.py index 6af4f018..366005a2 100644 --- a/wrappers/python/aries_askar/bindings.py +++ b/wrappers/python/aries_askar/bindings.py @@ -20,10 +20,10 @@ c_ubyte, ) from ctypes.util import find_library -from typing import Optional, Sequence, Tuple, Union +from typing import Optional, Union from .error import StoreError, StoreErrorCode -from .types import Entry, EntryOperation, KeyAlg +from .types import Entry, EntryOperation CALLBACKS = {} @@ -430,18 +430,6 @@ def get_current_error(expect: bool = False) -> Optional[StoreError]: return StoreError(StoreErrorCode.WRAPPER, "Unknown error") -async def derive_verkey(key_alg: KeyAlg, seed: Union[str, bytes]) -> str: - """Derive a verification key from a seed.""" - return str( - await do_call_async( - "askar_derive_verkey", - encode_str(key_alg.value), - encode_bytes(seed), - return_type=StrBuffer, - ) - ) - - async def generate_raw_key(seed: Union[str, bytes] = None) -> str: """Generate a new raw store wrapping key.""" return str( @@ -451,24 +439,6 @@ async def generate_raw_key(seed: Union[str, bytes] = None) -> str: ) -async def verify_signature( - signer_vk: str, - message: Union[str, bytes], - signature: Union[str, bytes], -) -> bool: - """Verify a message signature.""" - return ( - await do_call_async( - "askar_verify_signature", - encode_str(signer_vk), - encode_bytes(message), - encode_bytes(signature), - return_type=c_int8, - ) - != 0 - ) - - def version() -> str: """Get the version of the installed aries-askar library.""" lib = get_library() @@ -682,26 +652,6 @@ async def session_update( ) -async def session_create_keypair( - handle: SessionHandle, - alg: str, - metadata: str = None, - tags: dict = None, - seed: Union[str, bytes] = None, -) -> str: - return str( - await do_call_async( - "askar_session_create_keypair", - handle, - encode_str(alg), - encode_str(metadata), - encode_str(None if tags is None else json.dumps(tags)), - encode_bytes(seed), - return_type=StrBuffer, - ) - ) - - async def session_fetch_keypair( handle: SessionHandle, ident: str, for_update: bool = False ) -> Optional[EntrySetHandle]: @@ -728,50 +678,6 @@ async def session_update_keypair( ) -async def session_sign_message( - handle: SessionHandle, - key_ident: str, - message: Union[str, bytes], -) -> ByteBuffer: - return await do_call_async( - "askar_session_sign_message", - handle, - encode_str(key_ident), - encode_bytes(message), - return_type=ByteBuffer, - ) - - -async def session_pack_message( - handle: SessionHandle, - recipient_vks: Sequence[str], - from_key_ident: Optional[str], - message: Union[str, bytes], -) -> ByteBuffer: - recipient_vks = encode_str(",".join(recipient_vks)) - from_key_ident = encode_str(from_key_ident) - message = encode_bytes(message) - return await do_call_async( - "askar_session_pack_message", - handle, - recipient_vks, - from_key_ident, - message, - return_type=ByteBuffer, - ) - - -async def session_unpack_message( - handle: SessionHandle, - message: Union[str, bytes], -) -> Tuple[ByteBuffer, str, Optional[str]]: - message = encode_bytes(message) - result = await do_call_async( - "askar_session_unpack_message", handle, message, return_type=lib_unpack_result - ) - return (result.unpacked, str(result.recipient), result.sender.opt_str()) - - async def scan_start( handle: StoreHandle, profile: Optional[str], diff --git a/wrappers/python/demo/test.py b/wrappers/python/demo/test.py index 4d299732..c3312242 100644 --- a/wrappers/python/demo/test.py +++ b/wrappers/python/demo/test.py @@ -4,12 +4,10 @@ import sys from aries_askar.bindings import ( - derive_verkey, generate_raw_key, - verify_signature, version, ) -from aries_askar import KeyAlg, Store +from aries_askar import Store logging.basicConfig(level=os.getenv("LOG_LEVEL", "").upper() or None) @@ -36,10 +34,6 @@ async def basic_test(): key = None key_method = "none" - # Derive a verkey - verkey = await derive_verkey(KeyAlg.ED25519, b"testseedtestseedtestseedtestseed") - log("Derive verkey:", verkey) - # Provision the store store = await Store.provision(REPO_URI, key_method, key, recreate=True) log("Provisioned store:", store) @@ -77,33 +71,25 @@ async def basic_test(): # test key operations in a new session async with store as session: - # Create a new keypair - key_ident = await session.create_keypair(KeyAlg.ED25519, metadata="metadata") - log("Created key:", key_ident) - - # Update keypair - await session.update_keypair(key_ident, metadata="updated metadata") - log("Updated key") - - # Fetch keypair - key = await session.fetch_keypair(key_ident) - log("Fetch key:", key, "\nKey params:", key.params) + # # Create a new keypair + # key_ident = await session.create_keypair(KeyAlg.ED25519, metadata="metadata") + # log("Created key:", key_ident) - # Sign a message - signature = await session.sign_message(key_ident, b"my message") - log("Signature:", signature) + # # Update keypair + # await session.update_keypair(key_ident, metadata="updated metadata") + # log("Updated key") - # Verify signature - verify = await verify_signature(key_ident, b"my message", signature) - log("Verify signature:", verify) + # # Fetch keypair + # key = await session.fetch_keypair(key_ident) + # log("Fetch key:", key, "\nKey params:", key.params) - # Pack message - packed = await session.pack_message([key_ident], key_ident, b"my message") - log("Packed message:", packed) + # # Sign a message + # signature = await session.sign_message(key_ident, b"my message") + # log("Signature:", signature) - # Unpack message - unpacked = await session.unpack_message(packed) - log("Unpacked message:", unpacked) + # # Verify signature + # verify = await verify_signature(key_ident, b"my message", signature) + # log("Verify signature:", verify) # Remove rows by category and (optional) tag filter log( From a1564c11207934c8870378e4e6fce8eebbaf2315 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Thu, 15 Apr 2021 20:09:24 -0700 Subject: [PATCH 035/116] remove unused dependency Signed-off-by: Andrew Whitehead --- askar-crypto/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/askar-crypto/Cargo.toml b/askar-crypto/Cargo.toml index 5f2a5f3f..b696fb37 100644 --- a/askar-crypto/Cargo.toml +++ b/askar-crypto/Cargo.toml @@ -37,7 +37,7 @@ aes-gcm = { version = "0.8", default-features = false, features = ["aes", "alloc argon2 = "0.1" base64 = { version = "0.13", default-features = false, features = ["alloc"] } blake2 = { version = "0.9", default-features = false } -bls12_381 = { version = "0.4.0", path = "../../bls12_381" } +# bls12_381 = { version = "0.4.0", path = "../../bls12_381" } bs58 = { version = "0.4", default-features = false, features = ["alloc"] } chacha20 = "0.6" # should match version from chacha20poly1305 chacha20poly1305 = { version = "0.7", default-features = false, features = ["alloc", "chacha20"] } From 05c0c504f04eb70f186c14b962a67d5977ff059f Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Thu, 15 Apr 2021 21:08:52 -0700 Subject: [PATCH 036/116] reduce dependency features Signed-off-by: Andrew Whitehead --- askar-crypto/Cargo.toml | 21 +++++++++++---------- askar-crypto/src/alg/ed25519.rs | 13 +++++++++---- askar-crypto/src/alg/x25519.rs | 8 ++++++-- 3 files changed, 26 insertions(+), 16 deletions(-) diff --git a/askar-crypto/Cargo.toml b/askar-crypto/Cargo.toml index b696fb37..f55e0e7a 100644 --- a/askar-crypto/Cargo.toml +++ b/askar-crypto/Cargo.toml @@ -19,6 +19,7 @@ any = [] std = [] [dev-dependencies] +base64 = { version = "*", default-features = false, features = ["alloc"] } criterion = "0.3" hex-literal = "0.3" serde_cbor = "0.11" @@ -33,17 +34,17 @@ harness = false [dependencies] aead = "0.3" -aes-gcm = { version = "0.8", default-features = false, features = ["aes", "alloc"] } -argon2 = "0.1" -base64 = { version = "0.13", default-features = false, features = ["alloc"] } +aes-gcm = { version = "0.8", default-features = false, features = ["aes"] } +argon2 = { version = "0.1", default-features = false, features = ["password-hash"] } +base64 = { version = "0.13", default-features = false } blake2 = { version = "0.9", default-features = false } # bls12_381 = { version = "0.4.0", path = "../../bls12_381" } -bs58 = { version = "0.4", default-features = false, features = ["alloc"] } +bs58 = { version = "0.4", default-features = false } chacha20 = "0.6" # should match version from chacha20poly1305 -chacha20poly1305 = { version = "0.7", default-features = false, features = ["alloc", "chacha20"] } -crypto_box = "0.5" -curve25519-dalek = { version = "3.0", default-features = false, features = ["alloc", "u64_backend"] } -ed25519-dalek = { version = "1.0", default-features = false, features = ["alloc", "u64_backend"] } +chacha20poly1305 = { version = "0.7", default-features = false, features = ["chacha20"] } +crypto_box = { version = "0.5", default-features = false, features = ["u64_backend"] } +curve25519-dalek = { version = "3.0", default-features = false, features = ["u64_backend"] } +ed25519-dalek = { version = "1.0", default-features = false, features = ["u64_backend"] } digest = "0.9" group = "0.9" hex = { version = "0.4", default-features = false } @@ -51,8 +52,8 @@ hkdf = "0.10" k256 = { version = "0.7", default-features = false, features = ["arithmetic", "ecdsa", "ecdh", "sha256", "zeroize"] } p256 = { version = "0.7", default-features = false, features = ["arithmetic", "ecdsa", "ecdh", "zeroize"] } rand = { version = "0.7", default-features = false, features = ["getrandom"] } -serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } -serde_json = { version = "1.0", default-features = false, features = ["alloc"] } +serde = { version = "1.0", default-features = false, features = ["derive"] } +serde_json = { version = "1.0", default-features = false } sha2 = { version = "0.9", default-features = false } x25519-dalek = { version = "1.1", default-features = false, features = ["u64_backend"] } zeroize = { version = "1.1.0", features = ["zeroize_derive"] } diff --git a/askar-crypto/src/alg/ed25519.rs b/askar-crypto/src/alg/ed25519.rs index 007a2f12..334245ad 100644 --- a/askar-crypto/src/alg/ed25519.rs +++ b/askar-crypto/src/alg/ed25519.rs @@ -5,7 +5,6 @@ use core::{ use curve25519_dalek::edwards::CompressedEdwardsY; use ed25519_dalek::{ExpandedSecretKey, PublicKey, SecretKey, Signature}; -use rand::rngs::OsRng; use sha2::{self, Digest}; use x25519_dalek::{PublicKey as XPublicKey, StaticSecret as XSecretKey}; @@ -15,6 +14,7 @@ use crate::{ error::Error, generic_array::typenum::{U32, U64}, jwk::{FromJwk, JwkEncoder, JwkParts, ToJwk}, + random::fill_random, repr::{KeyGen, KeyMeta, KeyPublicBytes, KeySecretBytes, KeypairMeta}, sign::{KeySigVerify, KeySign, SignatureType}, }; @@ -42,8 +42,8 @@ impl Ed25519KeyPair { if kp.len() != KEYPAIR_LENGTH { return Err(err_msg!("Invalid keypair bytes")); } - let sk = SecretKey::from_bytes(&kp[..SECRET_KEY_LENGTH]) - .map_err(|_| err_msg!("Invalid ed25519 secret key bytes"))?; + // NB: this is infallible if the slice is the right length + let sk = SecretKey::from_bytes(&kp[..SECRET_KEY_LENGTH]).unwrap(); let pk = PublicKey::from_bytes(&kp[SECRET_KEY_LENGTH..]) .map_err(|_| err_msg!("Invalid ed25519 public key bytes"))?; // FIXME: derive pk from sk and check value? @@ -143,7 +143,12 @@ impl Debug for Ed25519KeyPair { impl KeyGen for Ed25519KeyPair { fn generate() -> Result { - Ok(Self::from_secret_key(SecretKey::generate(&mut OsRng))) + let mut sk = ArrayKey::::default(); + fill_random(sk.as_mut()); + // NB: from_bytes is infallible if the slice is the right length + Ok(Self::from_secret_key( + SecretKey::from_bytes(sk.as_ref()).unwrap(), + )) } } diff --git a/askar-crypto/src/alg/x25519.rs b/askar-crypto/src/alg/x25519.rs index 800e00dc..c63c876a 100644 --- a/askar-crypto/src/alg/x25519.rs +++ b/askar-crypto/src/alg/x25519.rs @@ -3,7 +3,6 @@ use core::{ fmt::{self, Debug, Formatter}, }; -use rand::rngs::OsRng; use x25519_dalek::{PublicKey, StaticSecret as SecretKey}; use zeroize::Zeroizing; @@ -13,6 +12,7 @@ use crate::{ generic_array::typenum::{U32, U64}, jwk::{FromJwk, JwkEncoder, JwkParts, ToJwk}, kdf::KeyExchange, + random::fill_random, repr::{KeyGen, KeyMeta, KeyPublicBytes, KeySecretBytes, KeypairMeta}, }; @@ -106,7 +106,11 @@ impl KeypairMeta for X25519KeyPair { impl KeyGen for X25519KeyPair { fn generate() -> Result { - let sk = SecretKey::new(OsRng); + let mut sk = ArrayKey::::default(); + fill_random(sk.as_mut()); + let sk = SecretKey::from( + TryInto::<[u8; SECRET_KEY_LENGTH]>::try_into(&sk.as_ref()[..]).unwrap(), + ); let pk = PublicKey::from(&sk); Ok(Self::new(Some(sk), pk)) } From 99ad1a73985a167be007d1db9a2e6447ee793ea8 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Thu, 15 Apr 2021 21:23:11 -0700 Subject: [PATCH 037/116] move (most) alloc features behind flag Signed-off-by: Andrew Whitehead --- askar-crypto/Cargo.toml | 6 +- askar-crypto/src/buffer/mod.rs | 5 ++ askar-crypto/src/buffer/writer.rs | 2 + askar-crypto/src/jwk/borrow.rs | 64 ++++++++++++++++ askar-crypto/src/jwk/mod.rs | 118 +++--------------------------- askar-crypto/src/lib.rs | 2 +- 6 files changed, 85 insertions(+), 112 deletions(-) create mode 100644 askar-crypto/src/jwk/borrow.rs diff --git a/askar-crypto/Cargo.toml b/askar-crypto/Cargo.toml index f55e0e7a..6104a548 100644 --- a/askar-crypto/Cargo.toml +++ b/askar-crypto/Cargo.toml @@ -14,9 +14,9 @@ keywords = ["hyperledger", "aries", "ssi", "verifiable", "credentials"] rustdoc-args = ["--cfg", "docsrs"] [features] -default = [] -any = [] -std = [] +default = ["alloc"] +alloc = [] +std = ["alloc"] [dev-dependencies] base64 = { version = "*", default-features = false, features = ["alloc"] } diff --git a/askar-crypto/src/buffer/mod.rs b/askar-crypto/src/buffer/mod.rs index ad8b6501..746802ac 100644 --- a/askar-crypto/src/buffer/mod.rs +++ b/askar-crypto/src/buffer/mod.rs @@ -1,3 +1,4 @@ +#[cfg(feature = "alloc")] use alloc::vec::Vec; use core::{iter, ops::Range}; @@ -6,7 +7,9 @@ use crate::error::Error; mod array; pub use self::array::ArrayKey; +#[cfg(feature = "alloc")] mod secret; +#[cfg(feature = "alloc")] pub use self::secret::SecretBytes; mod string; @@ -54,6 +57,7 @@ pub trait ResizeBuffer: WriteBuffer + AsRef<[u8]> + AsMut<[u8]> { ) -> Result<(), Error>; } +#[cfg(feature = "alloc")] impl WriteBuffer for Vec { fn write_with( &mut self, @@ -70,6 +74,7 @@ impl WriteBuffer for Vec { } } +#[cfg(feature = "alloc")] impl ResizeBuffer for Vec { fn buffer_resize(&mut self, len: usize) -> Result<(), Error> { self.resize(len, 0u8); diff --git a/askar-crypto/src/buffer/writer.rs b/askar-crypto/src/buffer/writer.rs index 3acbbbcc..cfb97a80 100644 --- a/askar-crypto/src/buffer/writer.rs +++ b/askar-crypto/src/buffer/writer.rs @@ -1,3 +1,4 @@ +#[cfg(feature = "alloc")] use alloc::vec::Vec; use core::ops::Range; @@ -102,6 +103,7 @@ impl ResizeBuffer for Writer<'_, [u8]> { } } +#[cfg(feature = "alloc")] impl<'w> Writer<'w, Vec> { #[inline] pub fn from_vec(vec: &'w mut Vec) -> Self { diff --git a/askar-crypto/src/jwk/borrow.rs b/askar-crypto/src/jwk/borrow.rs new file mode 100644 index 00000000..8a356e84 --- /dev/null +++ b/askar-crypto/src/jwk/borrow.rs @@ -0,0 +1,64 @@ +use alloc::{borrow::Cow, string::String}; + +use zeroize::Zeroize; + +use super::parts::JwkParts; +use crate::error::Error; + +#[derive(Clone, Debug)] +pub enum Jwk<'a> { + Encoded(Cow<'a, str>), + Parts(JwkParts<'a>), +} + +impl Jwk<'_> { + pub fn to_parts(&self) -> Result, Error> { + match self { + Self::Encoded(s) => Ok( + serde_json::from_str(s.as_ref()).map_err(err_map!("Error deserializing JWK"))? + ), + Self::Parts(p) => Ok(*p), + } + } + + pub fn as_opt_str(&self) -> Option<&str> { + match self { + Self::Encoded(s) => Some(s.as_ref()), + Self::Parts(_) => None, + } + } +} + +impl<'a> From> for Jwk<'a> { + fn from(jwk: Cow<'a, str>) -> Self { + Jwk::Encoded(jwk) + } +} + +impl<'a> From<&'a str> for Jwk<'a> { + fn from(jwk: &'a str) -> Self { + Jwk::Encoded(Cow::Borrowed(jwk)) + } +} + +impl<'a> From for Jwk<'a> { + fn from(jwk: String) -> Self { + Jwk::Encoded(Cow::Owned(jwk)) + } +} + +impl<'a> From> for Jwk<'a> { + fn from(jwk: JwkParts<'a>) -> Self { + Jwk::Parts(jwk) + } +} + +impl Zeroize for Jwk<'_> { + fn zeroize(&mut self) { + match self { + Self::Encoded(Cow::Owned(s)) => s.zeroize(), + Self::Encoded(_) => (), + Self::Parts(..) => (), + } + } +} diff --git a/askar-crypto/src/jwk/mod.rs b/askar-crypto/src/jwk/mod.rs index c4581320..7267804f 100644 --- a/askar-crypto/src/jwk/mod.rs +++ b/askar-crypto/src/jwk/mod.rs @@ -1,11 +1,15 @@ +#[cfg(feature = "alloc")] use alloc::{borrow::Cow, string::String, vec::Vec}; -use zeroize::Zeroize; - use crate::{buffer::WriteBuffer, error::Error}; +#[cfg(feature = "alloc")] +mod borrow; +#[cfg(feature = "alloc")] +pub use self::borrow::Jwk; + mod encode; -pub use encode::{JwkEncoder, JwkEncoderMode}; +pub use self::encode::{JwkEncoder, JwkEncoderMode}; mod ops; pub use self::ops::{KeyOps, KeyOpsSet}; @@ -13,67 +17,10 @@ pub use self::ops::{KeyOps, KeyOpsSet}; mod parts; pub use self::parts::JwkParts; -#[derive(Clone, Debug)] -pub enum Jwk<'a> { - Encoded(Cow<'a, str>), - Parts(JwkParts<'a>), -} - -impl Jwk<'_> { - pub fn to_parts(&self) -> Result, Error> { - match self { - Self::Encoded(s) => Ok( - serde_json::from_str(s.as_ref()).map_err(err_map!("Error deserializing JWK"))? - ), - Self::Parts(p) => Ok(*p), - } - } - - pub fn as_opt_str(&self) -> Option<&str> { - match self { - Self::Encoded(s) => Some(s.as_ref()), - Self::Parts(_) => None, - } - } -} - -impl<'a> From> for Jwk<'a> { - fn from(jwk: Cow<'a, str>) -> Self { - Jwk::Encoded(jwk) - } -} - -impl<'a> From<&'a str> for Jwk<'a> { - fn from(jwk: &'a str) -> Self { - Jwk::Encoded(Cow::Borrowed(jwk)) - } -} - -impl<'a> From for Jwk<'a> { - fn from(jwk: String) -> Self { - Jwk::Encoded(Cow::Owned(jwk)) - } -} - -impl<'a> From> for Jwk<'a> { - fn from(jwk: JwkParts<'a>) -> Self { - Jwk::Parts(jwk) - } -} - -impl Zeroize for Jwk<'_> { - fn zeroize(&mut self) { - match self { - Self::Encoded(Cow::Owned(s)) => s.zeroize(), - Self::Encoded(_) => (), - Self::Parts(..) => (), - } - } -} - pub trait ToJwk { fn to_jwk_buffer(&self, buffer: &mut JwkEncoder) -> Result<(), Error>; + #[cfg(feature = "alloc")] fn to_jwk_public(&self) -> Result, Error> { let mut v = Vec::with_capacity(128); let mut buf = JwkEncoder::new(&mut v, JwkEncoderMode::PublicKey)?; @@ -82,6 +29,7 @@ pub trait ToJwk { Ok(Jwk::Encoded(Cow::Owned(String::from_utf8(v).unwrap()))) } + #[cfg(feature = "alloc")] fn to_jwk_secret(&self) -> Result, Error> { let mut v = Vec::with_capacity(128); let mut buf = JwkEncoder::new(&mut v, JwkEncoderMode::SecretKey)?; @@ -92,6 +40,7 @@ pub trait ToJwk { } pub trait FromJwk: Sized { + #[cfg(feature = "alloc")] fn from_jwk(jwk: Jwk<'_>) -> Result { let parts = jwk.to_parts()?; Self::from_jwk_parts(parts) @@ -99,50 +48,3 @@ pub trait FromJwk: Sized { fn from_jwk_parts(jwk: JwkParts<'_>) -> Result; } - -// pub trait JwkBuilder<'s> { -// // key type -// kty: &'a str, -// // curve type -// crv: Option<&'a str>, -// // curve key public y coordinate -// x: Option<&'a str>, -// // curve key public y coordinate -// y: Option<&'a str>, -// // curve key private key bytes -// d: Option<&'a str>, -// // used by symmetric keys like AES -// k: Option<&'a str>, -// } - -// impl<'de> Serialize for JwkParts<'de> { -// fn serialize(&self, serializer: S) -> Result -// where -// S: Serializer, -// { -// let ret = serializer.serialize_map(None).unwrap(); - -// let add_attr = |name: &str, val: &str| { -// ret.serialize_key(name); -// ret.serialize_value(val); -// }; - -// add_attr("kty", self.kty.as_ref()); -// if let Some(attr) = self.crv.as_ref() { -// add_attr("crv", attr.as_ref()); -// if let Some(attr) = self.x.as_ref() { -// add_attr("x", attr.as_ref()); -// } -// if let Some(attr) = self.y.as_ref() { -// add_attr("y", attr.as_ref()); -// } -// if let Some(attr) = self.d.as_ref() { -// add_attr("d", attr.as_ref()); -// } -// } -// if let Some(attr) = self.k.as_ref() { -// add_attr("k", attr.as_ref()); -// } -// ret.end() -// } -// } diff --git a/askar-crypto/src/lib.rs b/askar-crypto/src/lib.rs index 660b4a5a..4d70f0f8 100644 --- a/askar-crypto/src/lib.rs +++ b/askar-crypto/src/lib.rs @@ -2,7 +2,7 @@ // #![deny(missing_debug_implementations)] // #![deny(missing_docs)] -// #[cfg(feature="alloc")] +#[cfg(feature = "alloc")] extern crate alloc; #[cfg(any(test, feature = "std"))] From 9bd668ecc315bc7381ef04ba9220248a031a2b6e Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Thu, 15 Apr 2021 21:54:25 -0700 Subject: [PATCH 038/116] move keypair bytes into trait Signed-off-by: Andrew Whitehead --- askar-crypto/src/alg/ed25519.rs | 71 ++++++++++++++------------- askar-crypto/src/alg/k256.rs | 87 +++++++++++++++++---------------- askar-crypto/src/alg/p256.rs | 86 ++++++++++++++++---------------- askar-crypto/src/alg/x25519.rs | 84 +++++++++++++++---------------- askar-crypto/src/repr.rs | 36 ++++++++++++-- 5 files changed, 197 insertions(+), 167 deletions(-) diff --git a/askar-crypto/src/alg/ed25519.rs b/askar-crypto/src/alg/ed25519.rs index 334245ad..ff8b5f5a 100644 --- a/askar-crypto/src/alg/ed25519.rs +++ b/askar-crypto/src/alg/ed25519.rs @@ -10,12 +10,12 @@ use x25519_dalek::{PublicKey as XPublicKey, StaticSecret as XSecretKey}; use super::x25519::X25519KeyPair; use crate::{ - buffer::{ArrayKey, SecretBytes, WriteBuffer}, + buffer::{ArrayKey, WriteBuffer}, error::Error, generic_array::typenum::{U32, U64}, jwk::{FromJwk, JwkEncoder, JwkParts, ToJwk}, random::fill_random, - repr::{KeyGen, KeyMeta, KeyPublicBytes, KeySecretBytes, KeypairMeta}, + repr::{KeyGen, KeyMeta, KeyPublicBytes, KeySecretBytes, KeypairBytes, KeypairMeta}, sign::{KeySigVerify, KeySign, SignatureType}, }; @@ -38,22 +38,6 @@ pub struct Ed25519KeyPair { } impl Ed25519KeyPair { - pub fn from_keypair_bytes(kp: &[u8]) -> Result { - if kp.len() != KEYPAIR_LENGTH { - return Err(err_msg!("Invalid keypair bytes")); - } - // NB: this is infallible if the slice is the right length - let sk = SecretKey::from_bytes(&kp[..SECRET_KEY_LENGTH]).unwrap(); - let pk = PublicKey::from_bytes(&kp[SECRET_KEY_LENGTH..]) - .map_err(|_| err_msg!("Invalid ed25519 public key bytes"))?; - // FIXME: derive pk from sk and check value? - - Ok(Self { - secret: Some(sk), - public: pk, - }) - } - #[inline] pub(crate) fn from_secret_key(sk: SecretKey) -> Self { let public = PublicKey::from(&sk); @@ -63,18 +47,6 @@ impl Ed25519KeyPair { } } - pub fn to_keypair_bytes(&self) -> Option { - if let Some(secret) = self.secret.as_ref() { - let output = SecretBytes::new_with(KEYPAIR_LENGTH, |buf| { - buf[..SECRET_KEY_LENGTH].copy_from_slice(secret.as_bytes()); - buf[SECRET_KEY_LENGTH..].copy_from_slice(self.public.as_bytes()); - }); - Some(output) - } else { - None - } - } - pub fn to_signing_key(&self) -> Option> { self.secret .as_ref() @@ -156,11 +128,6 @@ impl KeyMeta for Ed25519KeyPair { type KeySize = U32; } -impl KeypairMeta for Ed25519KeyPair { - type PublicKeySize = U32; - type KeypairSize = U64; -} - impl KeySecretBytes for Ed25519KeyPair { fn from_secret_bytes(key: &[u8]) -> Result { if key.len() != SECRET_KEY_LENGTH { @@ -175,6 +142,40 @@ impl KeySecretBytes for Ed25519KeyPair { } } +impl KeypairMeta for Ed25519KeyPair { + type PublicKeySize = U32; + type KeypairSize = U64; +} + +impl KeypairBytes for Ed25519KeyPair { + fn from_keypair_bytes(kp: &[u8]) -> Result { + if kp.len() != KEYPAIR_LENGTH { + return Err(err_msg!("Invalid keypair bytes")); + } + // NB: this is infallible if the slice is the right length + let sk = SecretKey::from_bytes(&kp[..SECRET_KEY_LENGTH]).unwrap(); + let pk = PublicKey::from_bytes(&kp[SECRET_KEY_LENGTH..]) + .map_err(|_| err_msg!("Invalid ed25519 public key bytes"))?; + // FIXME: derive pk from sk and check value? + + Ok(Self { + secret: Some(sk), + public: pk, + }) + } + + fn with_keypair_bytes(&self, f: impl FnOnce(Option<&[u8]>) -> O) -> O { + if let Some(secret) = self.secret.as_ref() { + let mut buf = ArrayKey::<::KeypairSize>::default(); + buf.as_mut()[..SECRET_KEY_LENGTH].copy_from_slice(secret.as_bytes()); + buf.as_mut()[SECRET_KEY_LENGTH..].copy_from_slice(self.public.as_bytes()); + f(Some(buf.as_ref())) + } else { + f(None) + } + } +} + impl KeyPublicBytes for Ed25519KeyPair { fn from_public_bytes(key: &[u8]) -> Result { if key.len() != PUBLIC_KEY_LENGTH { diff --git a/askar-crypto/src/alg/k256.rs b/askar-crypto/src/alg/k256.rs index fe28e35c..41ea2310 100644 --- a/askar-crypto/src/alg/k256.rs +++ b/askar-crypto/src/alg/k256.rs @@ -8,16 +8,15 @@ use k256::{ elliptic_curve::{ecdh::diffie_hellman, sec1::Coordinates, Curve}, EncodedPoint, PublicKey, SecretKey, }; -use zeroize::Zeroizing; use crate::{ - buffer::{ArrayKey, SecretBytes, WriteBuffer}, + buffer::{ArrayKey, WriteBuffer}, error::Error, generic_array::typenum::{U32, U33, U65}, jwk::{FromJwk, JwkEncoder, JwkParts, ToJwk}, kdf::KeyExchange, random::with_rng, - repr::{KeyGen, KeyMeta, KeyPublicBytes, KeySecretBytes, KeypairMeta}, + repr::{KeyGen, KeyMeta, KeyPublicBytes, KeySecretBytes, KeypairBytes, KeypairMeta}, sign::{KeySigVerify, KeySign, SignatureType}, }; @@ -40,23 +39,6 @@ pub struct K256KeyPair { } impl K256KeyPair { - pub fn from_keypair_bytes(kp: &[u8]) -> Result { - if kp.len() != KEYPAIR_LENGTH { - return Err(err_msg!("Invalid keypair bytes")); - } - let sk = SecretKey::from_bytes(&kp[..SECRET_KEY_LENGTH]) - .map_err(|_| err_msg!("Invalid k-256 secret key bytes"))?; - let pk = EncodedPoint::from_bytes(&kp[SECRET_KEY_LENGTH..]) - .and_then(|pt| pt.decode()) - .map_err(|_| err_msg!("Invalid k-256 public key bytes"))?; - // FIXME: derive pk from sk and check value? - - Ok(Self { - secret: Some(sk), - public: pk, - }) - } - #[inline] pub(crate) fn from_secret_key(sk: SecretKey) -> Self { let pk = sk.public_key(); @@ -66,19 +48,6 @@ impl K256KeyPair { } } - pub fn to_keypair_bytes(&self) -> Option { - if let Some(secret) = self.secret.as_ref() { - let encp = EncodedPoint::encode(self.public, true); - let output = SecretBytes::new_with(KEYPAIR_LENGTH, |buf| { - buf[..SECRET_KEY_LENGTH].copy_from_slice(&secret.to_bytes()[..]); - buf[SECRET_KEY_LENGTH..].copy_from_slice(encp.as_ref()); - }); - Some(output) - } else { - None - } - } - pub(crate) fn to_signing_key(&self) -> Option { self.secret.as_ref().map(SigningKey::from) } @@ -103,21 +72,16 @@ impl K256KeyPair { } } -impl KeyMeta for K256KeyPair { - type KeySize = U32; -} - -impl KeypairMeta for K256KeyPair { - type PublicKeySize = U33; - type KeypairSize = U65; -} - impl KeyGen for K256KeyPair { fn generate() -> Result { Ok(Self::from_secret_key(with_rng(|r| SecretKey::random(r)))) } } +impl KeyMeta for K256KeyPair { + type KeySize = U32; +} + impl KeySecretBytes for K256KeyPair { fn from_secret_bytes(key: &[u8]) -> Result { Ok(Self::from_secret_key( @@ -127,7 +91,7 @@ impl KeySecretBytes for K256KeyPair { fn with_secret_bytes(&self, f: impl FnOnce(Option<&[u8]>) -> O) -> O { if let Some(sk) = self.secret.as_ref() { - let b = Zeroizing::new(<[u8; SECRET_KEY_LENGTH]>::from(sk.to_bytes())); + let b = k256::SecretBytes::from(sk.to_bytes()); f(Some(&b[..])) } else { f(None) @@ -135,6 +99,43 @@ impl KeySecretBytes for K256KeyPair { } } +impl KeypairMeta for K256KeyPair { + type PublicKeySize = U33; + type KeypairSize = U65; +} + +impl KeypairBytes for K256KeyPair { + fn from_keypair_bytes(kp: &[u8]) -> Result { + if kp.len() != KEYPAIR_LENGTH { + return Err(err_msg!("Invalid keypair bytes")); + } + let sk = SecretKey::from_bytes(&kp[..SECRET_KEY_LENGTH]) + .map_err(|_| err_msg!("Invalid k-256 secret key bytes"))?; + let pk = EncodedPoint::from_bytes(&kp[SECRET_KEY_LENGTH..]) + .and_then(|pt| pt.decode()) + .map_err(|_| err_msg!("Invalid k-256 public key bytes"))?; + // FIXME: derive pk from sk and check value? + + Ok(Self { + secret: Some(sk), + public: pk, + }) + } + + fn with_keypair_bytes(&self, f: impl FnOnce(Option<&[u8]>) -> O) -> O { + if let Some(sk) = self.secret.as_ref() { + let mut buf = ArrayKey::<::KeypairSize>::default(); + let sk_b = k256::SecretBytes::from(sk.to_bytes()); + let pk_enc = EncodedPoint::encode(self.public, true); + buf.as_mut()[..SECRET_KEY_LENGTH].copy_from_slice(&sk_b[..]); + buf.as_mut()[SECRET_KEY_LENGTH..].copy_from_slice(pk_enc.as_ref()); + f(Some(buf.as_ref())) + } else { + f(None) + } + } +} + impl KeyPublicBytes for K256KeyPair { fn from_public_bytes(key: &[u8]) -> Result { let pk = EncodedPoint::from_bytes(key) diff --git a/askar-crypto/src/alg/p256.rs b/askar-crypto/src/alg/p256.rs index c97302d5..bac88550 100644 --- a/askar-crypto/src/alg/p256.rs +++ b/askar-crypto/src/alg/p256.rs @@ -8,16 +8,15 @@ use p256::{ elliptic_curve::{ecdh::diffie_hellman, sec1::Coordinates, Curve}, EncodedPoint, PublicKey, SecretKey, }; -use zeroize::Zeroizing; use crate::{ - buffer::{ArrayKey, SecretBytes, WriteBuffer}, + buffer::{ArrayKey, WriteBuffer}, error::Error, generic_array::typenum::{U32, U33, U65}, jwk::{FromJwk, JwkEncoder, JwkParts, ToJwk}, kdf::KeyExchange, random::with_rng, - repr::{KeyGen, KeyMeta, KeyPublicBytes, KeySecretBytes, KeypairMeta}, + repr::{KeyGen, KeyMeta, KeyPublicBytes, KeySecretBytes, KeypairBytes, KeypairMeta}, sign::{KeySigVerify, KeySign, SignatureType}, }; @@ -40,23 +39,6 @@ pub struct P256KeyPair { } impl P256KeyPair { - pub fn from_keypair_bytes(kp: &[u8]) -> Result { - if kp.len() != KEYPAIR_LENGTH { - return Err(err_msg!("Invalid keypair bytes")); - } - let sk = SecretKey::from_bytes(&kp[..SECRET_KEY_LENGTH]) - .map_err(|_| err_msg!("Invalid p-256 secret key bytes"))?; - let pk = EncodedPoint::from_bytes(&kp[SECRET_KEY_LENGTH..]) - .and_then(|pt| pt.decode()) - .map_err(|_| err_msg!("Invalid p-256 public key bytes"))?; - // FIXME: derive pk from sk and check value? - - Ok(Self { - secret: Some(sk), - public: pk, - }) - } - #[inline] pub(crate) fn from_secret_key(sk: SecretKey) -> Self { let pk = sk.public_key(); @@ -66,19 +48,6 @@ impl P256KeyPair { } } - pub fn to_keypair_bytes(&self) -> Option { - if let Some(secret) = self.secret.as_ref() { - let encp = EncodedPoint::encode(self.public, true); - let output = SecretBytes::new_with(KEYPAIR_LENGTH, |buf| { - buf[..SECRET_KEY_LENGTH].copy_from_slice(&secret.to_bytes()[..]); - buf[SECRET_KEY_LENGTH..].copy_from_slice(encp.as_ref()); - }); - Some(output) - } else { - None - } - } - pub(crate) fn to_signing_key(&self) -> Option { self.secret.clone().map(SigningKey::from) } @@ -103,20 +72,14 @@ impl P256KeyPair { } } -impl KeyMeta for P256KeyPair { - type KeySize = U32; -} - -impl KeypairMeta for P256KeyPair { - type PublicKeySize = U33; - type KeypairSize = U65; -} - impl KeyGen for P256KeyPair { fn generate() -> Result { Ok(Self::from_secret_key(with_rng(|r| SecretKey::random(r)))) } } +impl KeyMeta for P256KeyPair { + type KeySize = U32; +} impl KeySecretBytes for P256KeyPair { fn from_secret_bytes(key: &[u8]) -> Result { @@ -127,7 +90,7 @@ impl KeySecretBytes for P256KeyPair { fn with_secret_bytes(&self, f: impl FnOnce(Option<&[u8]>) -> O) -> O { if let Some(sk) = self.secret.as_ref() { - let b = Zeroizing::new(<[u8; SECRET_KEY_LENGTH]>::from(sk.to_bytes())); + let b = p256::SecretBytes::from(sk.to_bytes()); f(Some(&b[..])) } else { f(None) @@ -135,6 +98,43 @@ impl KeySecretBytes for P256KeyPair { } } +impl KeypairMeta for P256KeyPair { + type PublicKeySize = U33; + type KeypairSize = U65; +} + +impl KeypairBytes for P256KeyPair { + fn from_keypair_bytes(kp: &[u8]) -> Result { + if kp.len() != KEYPAIR_LENGTH { + return Err(err_msg!("Invalid keypair bytes")); + } + let sk = SecretKey::from_bytes(&kp[..SECRET_KEY_LENGTH]) + .map_err(|_| err_msg!("Invalid k-256 secret key bytes"))?; + let pk = EncodedPoint::from_bytes(&kp[SECRET_KEY_LENGTH..]) + .and_then(|pt| pt.decode()) + .map_err(|_| err_msg!("Invalid k-256 public key bytes"))?; + // FIXME: derive pk from sk and check value? + + Ok(Self { + secret: Some(sk), + public: pk, + }) + } + + fn with_keypair_bytes(&self, f: impl FnOnce(Option<&[u8]>) -> O) -> O { + if let Some(sk) = self.secret.as_ref() { + let mut buf = ArrayKey::<::KeypairSize>::default(); + let sk_b = p256::SecretBytes::from(sk.to_bytes()); + let pk_enc = EncodedPoint::encode(self.public, true); + buf.as_mut()[..SECRET_KEY_LENGTH].copy_from_slice(&sk_b[..]); + buf.as_mut()[SECRET_KEY_LENGTH..].copy_from_slice(pk_enc.as_ref()); + f(Some(buf.as_ref())) + } else { + f(None) + } + } +} + impl KeyPublicBytes for P256KeyPair { fn from_public_bytes(key: &[u8]) -> Result { let pk = EncodedPoint::from_bytes(key) diff --git a/askar-crypto/src/alg/x25519.rs b/askar-crypto/src/alg/x25519.rs index c63c876a..d8ab071a 100644 --- a/askar-crypto/src/alg/x25519.rs +++ b/askar-crypto/src/alg/x25519.rs @@ -7,13 +7,13 @@ use x25519_dalek::{PublicKey, StaticSecret as SecretKey}; use zeroize::Zeroizing; use crate::{ - buffer::{ArrayKey, SecretBytes, WriteBuffer}, + buffer::{ArrayKey, WriteBuffer}, error::Error, generic_array::typenum::{U32, U64}, jwk::{FromJwk, JwkEncoder, JwkParts, ToJwk}, kdf::KeyExchange, random::fill_random, - repr::{KeyGen, KeyMeta, KeyPublicBytes, KeySecretBytes, KeypairMeta}, + repr::{KeyGen, KeyMeta, KeyPublicBytes, KeySecretBytes, KeypairBytes, KeypairMeta}, }; pub const PUBLIC_KEY_LENGTH: usize = 32; @@ -39,24 +39,6 @@ impl X25519KeyPair { } } - pub fn from_keypair_bytes(kp: &[u8]) -> Result { - if kp.len() != KEYPAIR_LENGTH { - return Err(err_msg!("Invalid keypair bytes")); - } - let sk = SecretKey::from( - TryInto::<[u8; SECRET_KEY_LENGTH]>::try_into(&kp[..SECRET_KEY_LENGTH]).unwrap(), - ); - let pk = PublicKey::from( - TryInto::<[u8; PUBLIC_KEY_LENGTH]>::try_into(&kp[SECRET_KEY_LENGTH..]).unwrap(), - ); - // FIXME: derive pk from sk and check value? - - Ok(Self { - secret: Some(sk), - public: pk, - }) - } - #[inline] pub(crate) fn from_secret_key(sk: SecretKey) -> Self { let public = PublicKey::from(&sk); @@ -65,18 +47,6 @@ impl X25519KeyPair { public, } } - - pub fn to_keypair_bytes(&self) -> Option { - if let Some(secret) = self.secret.as_ref() { - let output = SecretBytes::new_with(KEYPAIR_LENGTH, |buf| { - buf[..SECRET_KEY_LENGTH].copy_from_slice(&secret.to_bytes()[..]); - buf[SECRET_KEY_LENGTH..].copy_from_slice(self.public.as_bytes()); - }); - Some(output) - } else { - None - } - } } impl Debug for X25519KeyPair { @@ -95,15 +65,6 @@ impl Debug for X25519KeyPair { } } -impl KeyMeta for X25519KeyPair { - type KeySize = U32; -} - -impl KeypairMeta for X25519KeyPair { - type PublicKeySize = U32; - type KeypairSize = U64; -} - impl KeyGen for X25519KeyPair { fn generate() -> Result { let mut sk = ArrayKey::::default(); @@ -116,6 +77,10 @@ impl KeyGen for X25519KeyPair { } } +impl KeyMeta for X25519KeyPair { + type KeySize = U32; +} + impl KeySecretBytes for X25519KeyPair { fn from_secret_bytes(key: &[u8]) -> Result { if key.len() != SECRET_KEY_LENGTH { @@ -149,6 +114,43 @@ impl KeySecretBytes for X25519KeyPair { } } +impl KeypairMeta for X25519KeyPair { + type PublicKeySize = U32; + type KeypairSize = U64; +} + +impl KeypairBytes for X25519KeyPair { + fn from_keypair_bytes(kp: &[u8]) -> Result { + if kp.len() != KEYPAIR_LENGTH { + return Err(err_msg!("Invalid keypair bytes")); + } + let sk = SecretKey::from( + TryInto::<[u8; SECRET_KEY_LENGTH]>::try_into(&kp[..SECRET_KEY_LENGTH]).unwrap(), + ); + let pk = PublicKey::from( + TryInto::<[u8; PUBLIC_KEY_LENGTH]>::try_into(&kp[SECRET_KEY_LENGTH..]).unwrap(), + ); + // FIXME: derive pk from sk and check value? + + Ok(Self { + secret: Some(sk), + public: pk, + }) + } + + fn with_keypair_bytes(&self, f: impl FnOnce(Option<&[u8]>) -> O) -> O { + if let Some(secret) = self.secret.as_ref() { + let mut buf = ArrayKey::<::KeypairSize>::default(); + let b = Zeroizing::new(secret.to_bytes()); + buf.as_mut()[..SECRET_KEY_LENGTH].copy_from_slice(&b[..]); + buf.as_mut()[SECRET_KEY_LENGTH..].copy_from_slice(self.public.as_bytes()); + f(Some(buf.as_ref())) + } else { + f(None) + } + } +} + impl KeyPublicBytes for X25519KeyPair { fn from_public_bytes(key: &[u8]) -> Result { if key.len() != PUBLIC_KEY_LENGTH { diff --git a/askar-crypto/src/repr.rs b/askar-crypto/src/repr.rs index 1b5abd44..51e3fcaa 100644 --- a/askar-crypto/src/repr.rs +++ b/askar-crypto/src/repr.rs @@ -1,8 +1,6 @@ -use crate::{ - buffer::{SecretBytes, WriteBuffer}, - error::Error, - generic_array::ArrayLength, -}; +#[cfg(feature = "alloc")] +use crate::buffer::SecretBytes; +use crate::{buffer::WriteBuffer, error::Error, generic_array::ArrayLength}; /// Generate a new random key. pub trait KeyGen: Sized { @@ -33,6 +31,7 @@ pub trait KeySecretBytes { }) } + #[cfg(feature = "alloc")] fn to_secret_bytes(&self) -> Result { let mut buf = SecretBytes::with_capacity(128); self.to_secret_bytes_buffer(&mut buf)?; @@ -52,6 +51,7 @@ pub trait KeyPublicBytes { self.with_public_bytes(|buf| out.write_slice(buf)) } + #[cfg(feature = "alloc")] fn to_public_bytes(&self) -> Result { let mut buf = SecretBytes::with_capacity(128); self.to_public_bytes_buffer(&mut buf)?; @@ -59,6 +59,32 @@ pub trait KeyPublicBytes { } } +/// Convert between keypair instance and keypair (secret and public) bytes. +pub trait KeypairBytes { + fn from_keypair_bytes(key: &[u8]) -> Result + where + Self: Sized; + + fn with_keypair_bytes(&self, f: impl FnOnce(Option<&[u8]>) -> O) -> O; + + fn to_keypair_bytes_buffer(&self, out: &mut B) -> Result<(), Error> { + self.with_keypair_bytes(|buf| { + if let Some(buf) = buf { + out.write_slice(buf) + } else { + Err(err_msg!(MissingSecretKey)) + } + }) + } + + #[cfg(feature = "alloc")] + fn to_keypair_bytes(&self) -> Result { + let mut buf = SecretBytes::with_capacity(128); + self.to_keypair_bytes_buffer(&mut buf)?; + Ok(buf) + } +} + /// For concrete secret key types pub trait KeyMeta { type KeySize: ArrayLength; From bde24d5c9f4cf869dd9f9233ac9ce73788da6fe2 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Thu, 15 Apr 2021 21:57:43 -0700 Subject: [PATCH 039/116] remove unused Signed-off-by: Andrew Whitehead --- askar-crypto/src/lib.rs | 2 - askar-crypto/src/pack/alg.rs | 369 ----------------------------------- askar-crypto/src/pack/mod.rs | 3 - 3 files changed, 374 deletions(-) delete mode 100644 askar-crypto/src/pack/alg.rs delete mode 100644 askar-crypto/src/pack/mod.rs diff --git a/askar-crypto/src/lib.rs b/askar-crypto/src/lib.rs index 4d70f0f8..59f0b49d 100644 --- a/askar-crypto/src/lib.rs +++ b/askar-crypto/src/lib.rs @@ -36,8 +36,6 @@ pub mod jwk; pub mod kdf; -pub mod pack; - pub mod random; pub mod sign; diff --git a/askar-crypto/src/pack/alg.rs b/askar-crypto/src/pack/alg.rs deleted file mode 100644 index 83da7be0..00000000 --- a/askar-crypto/src/pack/alg.rs +++ /dev/null @@ -1,369 +0,0 @@ -use chacha20poly1305::{ - aead::{generic_array::typenum::Unsigned, Aead, NewAead, Payload}, - ChaCha20Poly1305, Key as ChaChaKey, -}; - -pub use crate::alg::ed25519::Ed25519KeyPair as KeyPair; - -use std::string::ToString; - -use super::nacl_box::*; -// use super::types::*; -use crate::{ - buffer::SecretBytes, - error::Error, - random::{random_array, random_vec}, -}; - -pub const PROTECTED_HEADER_ENC: &'static str = "xchacha20poly1305_ietf"; -pub const PROTECTED_HEADER_TYP: &'static str = "JWM/1.0"; -pub const PROTECTED_HEADER_ALG_AUTH: &'static str = "Authcrypt"; -pub const PROTECTED_HEADER_ALG_ANON: &'static str = "Anoncrypt"; - -type KeySize = ::KeySize; - -const NONCE_SIZE: usize = ::NonceSize::USIZE; -const TAG_SIZE: usize = ::TagSize::USIZE; - -pub fn pack_message>( - message: M, - receiver_list: Vec, - sender_key: Option, -) -> Result, Error> { - // break early and error out if no receivers keys are provided - if receiver_list.is_empty() { - return Err(err_msg!("No message recipients")); - } - - // generate content encryption key that will encrypt `message` - let cek = SecretBytes::from(random_vec(KeySize::to_usize())); - - let base64_protected = if let Some(sender_key) = sender_key { - // returns authcrypted pack_message format. See Wire message format HIPE for details - prepare_protected_authcrypt(&cek, receiver_list, &sender_key)? - } else { - // returns anoncrypted pack_message format. See Wire message format HIPE for details - prepare_protected_anoncrypt(&cek, receiver_list)? - }; - - // Use AEAD to encrypt `message` with "protected" data as "associated data" - let chacha = ChaCha20Poly1305::new(ChaChaKey::from_slice(&cek)); - let nonce = random_array(); - let payload = Payload { - aad: base64_protected.as_bytes(), - msg: message.as_ref(), - }; - let ciphertext = chacha - .encrypt(&nonce, payload) - .map_err(|_| err_msg!(Encryption, "Error encrypting payload"))?; - let iv = b64_encode(nonce); - let clen = ciphertext.len() - TAG_SIZE; - let tag = b64_encode(&ciphertext[clen..]); - let ciphertext = b64_encode(&ciphertext[..clen]); - - format_pack_message(&base64_protected, &ciphertext, &iv, &tag) -} - -fn prepare_protected_anoncrypt(cek: &[u8], receiver_list: Vec) -> Result { - let mut encrypted_recipients_struct: Vec = Vec::with_capacity(receiver_list.len()); - - for their_vk in receiver_list { - // encrypt cek for recipient - let their_vk_x = their_vk.to_x25519(); - let enc_cek = crypto_box_seal(&their_vk_x.to_bytes(), cek.as_ref())?; - - // create recipient struct and push to encrypted list - encrypted_recipients_struct.push(Recipient { - encrypted_key: b64_encode(enc_cek.as_slice()), - header: Header { - kid: their_vk.to_base58(), - sender: None, - iv: None, - }, - }); - } - - b64_encode_protected(encrypted_recipients_struct, false) -} - -fn prepare_protected_authcrypt( - cek: &[u8], - receiver_list: Vec, - sender_key: &KeyPair, -) -> Result { - let mut encrypted_recipients_struct: Vec = vec![]; - - let sender_key_x = sender_key.to_x25519(); - let sender_sk_x = sender_key_x.private_key(); - let sender_pk = sender_key.public_key(); - - for their_vk in receiver_list { - let their_vk_x = their_vk.to_x25519(); - - // encrypt cek for recipient - let (enc_cek, iv) = crypto_box(&their_vk_x.to_bytes(), sender_sk_x.as_ref(), cek, None)?; - - // encrypt sender key for recipient - let enc_sender = crypto_box_seal(&their_vk_x.to_bytes(), sender_pk.to_base58().as_ref())?; - - // create recipient struct and push to encrypted list - encrypted_recipients_struct.push(Recipient { - encrypted_key: b64_encode(enc_cek.as_slice()), - header: Header { - kid: their_vk.to_base58(), - sender: Some(b64_encode(enc_sender.as_slice())), - iv: Some(b64_encode(iv.as_slice())), - }, - }); - } - - b64_encode_protected(encrypted_recipients_struct, true) -} - -#[inline(always)] -fn b64_decode(input: impl AsRef<[u8]>) -> Result, Error> { - base64::decode_config(input, base64::URL_SAFE).map_err(|_| err_msg!("Error decoding as base64")) -} - -#[inline(always)] -fn b64_encode(input: impl AsRef<[u8]>) -> String { - base64::encode_config(input, base64::URL_SAFE_NO_PAD) -} - -fn b64_encode_protected( - encrypted_recipients_struct: Vec, - alg_is_authcrypt: bool, -) -> Result { - let alg_val = if alg_is_authcrypt { - String::from(PROTECTED_HEADER_ALG_AUTH) - } else { - String::from(PROTECTED_HEADER_ALG_ANON) - }; - - // structure protected and base64URL encode it - let protected_struct = Protected { - enc: PROTECTED_HEADER_ENC.to_string(), - typ: PROTECTED_HEADER_TYP.to_string(), - alg: alg_val, - recipients: encrypted_recipients_struct, - }; - let protected_encoded = serde_json::to_string(&protected_struct) - .map_err(|err| err_msg!(Encryption, "Failed to serialize protected field {}", err))?; - - Ok(b64_encode(protected_encoded.as_bytes())) -} - -fn format_pack_message( - base64_protected: &str, - ciphertext: &str, - iv: &str, - tag: &str, -) -> Result, Error> { - // serialize pack message and return as vector of bytes - let jwe_struct = JWE { - protected: base64_protected.to_string(), - iv: iv.to_string(), - ciphertext: ciphertext.to_string(), - tag: tag.to_string(), - }; - - serde_json::to_vec(&jwe_struct).map_err(|_| err_msg!("Error serializing JWE")) -} - -pub async fn unpack_message<'f>( - message: impl AsRef<[u8]>, - lookup: impl KeyLookup<'f>, -) -> Result<(Vec, PublicKey, Option), Error> { - let jwe = - serde_json::from_slice(message.as_ref()).map_err(|_| err_msg!("Invalid format for JWE"))?; - unpack_jwe(&jwe, lookup).await -} - -pub async fn unpack_jwe<'f>( - jwe_struct: &JWE, - lookup: impl KeyLookup<'f>, -) -> Result<(Vec, PublicKey, Option), Error> { - // decode protected data - let protected_decoded = b64_decode(&jwe_struct.protected)?; - let protected: Protected = serde_json::from_slice(&protected_decoded) - .map_err(|_| err_msg!(Encryption, "Invalid format for protected data"))?; - - // extract recipient that matches a key in the wallet - let (recipient, recip_pk, recip_sk) = - if let Some(recip) = find_unpack_recipient(protected, lookup).await? { - recip - } else { - return Err(err_msg!(Encryption, "No matching recipient found")); - }; - let is_auth_recipient = recipient.header.sender.is_some() && recipient.header.iv.is_some(); - - // get cek and sender data - let (sender_verkey_option, cek) = if is_auth_recipient { - let (send, cek) = unpack_cek_authcrypt(&recipient, &recip_sk)?; - (Some(send), cek) - } else { - let cek = unpack_cek_anoncrypt(&recipient, &recip_sk)?; - (None, cek) - }; - - // decrypt message - let chacha = ChaCha20Poly1305::new_varkey(&cek) - .map_err(|_| err_msg!(Encryption, "Error creating unpack decryptor for cek"))?; - let nonce = b64_decode(&jwe_struct.iv)?; - if nonce.len() != NONCE_SIZE { - return Err(err_msg!(Encryption, "Invalid size for message nonce")); - } - let mut ciphertext = b64_decode(&jwe_struct.ciphertext)?; - ciphertext.append(b64_decode(&jwe_struct.tag)?.as_mut()); - let payload = Payload { - aad: jwe_struct.protected.as_bytes(), - msg: ciphertext.as_slice(), - }; - let message = chacha - .decrypt(nonce.as_slice().into(), payload) - .map_err(|_| err_msg!(Encryption, "Error decrypting message payload"))?; - - Ok((message, recip_pk, sender_verkey_option)) -} - -fn unpack_cek_authcrypt( - recipient: &Recipient, - recip_sk: &KeyPair, -) -> Result<(PublicKey, Vec), Error> { - let encrypted_key_vec = b64_decode(&recipient.encrypted_key)?; - let iv = b64_decode(&recipient.header.iv.as_ref().unwrap())?; - let enc_sender_vk = b64_decode(&recipient.header.sender.as_ref().unwrap())?; - - // decrypt sender_vk - let recip_x = recip_sk.to_x25519(); - let recip_sk_x = recip_x.private_key(); - let sender_vk_vec = crypto_box_seal_open( - &recip_x.public_key().to_bytes(), - recip_sk_x.as_ref(), - &enc_sender_vk, - )?; - let sender_vk = PublicKey::from_str( - std::str::from_utf8(&sender_vk_vec) - .map_err(|_| err_msg!(Encryption, "Invalid sender verkey"))?, - )?; - - // decrypt cek - let cek = crypto_box_open( - recip_sk_x.as_ref(), - &sender_vk.to_x25519().to_bytes(), - encrypted_key_vec.as_slice(), - iv.as_slice(), - )?; - - Ok((sender_vk, cek)) -} - -fn unpack_cek_anoncrypt(recipient: &Recipient, recip_sk: &KeyPair) -> Result, Error> { - let encrypted_key = b64_decode(&recipient.encrypted_key)?; - - // decrypt cek - let recip_x = recip_sk.to_x25519(); - let cek = crypto_box_seal_open( - &recip_x.public_key().to_bytes(), - recip_x.private_key().as_ref(), - &encrypted_key, - )?; - - Ok(cek) -} - -async fn find_unpack_recipient<'f>( - protected: Protected, - lookup: impl KeyLookup<'f>, -) -> Result, Error> { - let mut recip_vks = Vec::::with_capacity(protected.recipients.len()); - for recipient in &protected.recipients { - let vk = PublicKey::from_str(&recipient.header.kid)?; - recip_vks.push(vk); - } - if let Some((idx, sk)) = lookup.find(&recip_vks).await { - let recip = protected.recipients.into_iter().nth(idx).unwrap(); - let vk = recip_vks.into_iter().nth(idx).unwrap(); - Ok(Some((recip, vk, sk))) - } else { - Ok(None) - } -} - -#[cfg(test)] -mod tests { - use async_global_executor::block_on; - - use super::*; - - #[test] - fn anon_pack_basic() { - let pk = KeyPair::from_seed(b"000000000000000000000000000Test2") - .unwrap() - .public_key(); - let packed = pack_message(b"hello there", vec![pk], None); - assert!(packed.is_ok()); - } - - #[test] - fn auth_pack_basic() { - let sk = KeyPair::from_seed(b"000000000000000000000000000Test1").unwrap(); - let pk = KeyPair::from_seed(b"000000000000000000000000000Test2") - .unwrap() - .public_key(); - let packed = pack_message(b"hello there", vec![pk], Some(sk)); - assert!(packed.is_ok()); - } - - #[test] - fn anon_pack_round_trip() { - let sk1 = KeyPair::from_seed(b"000000000000000000000000000Test3").unwrap(); - let pk1 = sk1.public_key(); - - let input_msg = b"hello there"; - let packed = pack_message(&input_msg, vec![pk1.clone()], None).unwrap(); - - let lookup = |find_pks: &Vec| { - for (idx, pk) in find_pks.into_iter().enumerate() { - if pk == &pk1 { - return Some((idx, sk1.clone())); - } - } - None - }; - - let lookup_fn = key_lookup_fn(lookup); - let result = unpack_message(&packed, &lookup_fn); - let (msg, p_recip, p_send) = block_on(result).unwrap(); - assert_eq!(msg, input_msg); - assert_eq!(p_recip, pk1); - assert_eq!(p_send, None); - } - - #[test] - fn auth_pack_round_trip() { - let sk1 = KeyPair::from_seed(b"000000000000000000000000000Test3").unwrap(); - let pk1 = sk1.public_key(); - let sk2 = KeyPair::from_seed(b"000000000000000000000000000Test4").unwrap(); - let pk2 = sk2.public_key(); - - let input_msg = b"hello there"; - let packed = pack_message(&input_msg, vec![pk2.clone()], Some(sk1.clone())).unwrap(); - - let lookup = |find_pks: &Vec| { - for (idx, pk) in find_pks.into_iter().enumerate() { - if pk == &pk2 { - return Some((idx, sk2.clone())); - } - } - None - }; - - let lookup_fn = key_lookup_fn(lookup); - let result = unpack_message(&packed, &lookup_fn); - let (msg, p_recip, p_send) = block_on(result).unwrap(); - assert_eq!(msg, input_msg); - assert_eq!(p_recip, pk2); - assert_eq!(p_send, Some(pk1)); - } -} diff --git a/askar-crypto/src/pack/mod.rs b/askar-crypto/src/pack/mod.rs deleted file mode 100644 index 1f266fbc..00000000 --- a/askar-crypto/src/pack/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -// mod alg; - -// mod nacl_box; From 5bbdb5cf7978b81dee34aca15afa35fa7754bffa Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Thu, 15 Apr 2021 22:39:04 -0700 Subject: [PATCH 040/116] enable building without alloc feature Signed-off-by: Andrew Whitehead --- askar-crypto/Cargo.toml | 2 +- askar-crypto/src/alg/aesgcm.rs | 15 +++---- askar-crypto/src/alg/bls.rs | 6 +-- askar-crypto/src/alg/chacha20.rs | 15 +++---- askar-crypto/src/alg/ed25519.rs | 22 ++++------ askar-crypto/src/alg/k256.rs | 30 +++++++------- askar-crypto/src/alg/mod.rs | 2 +- askar-crypto/src/alg/p256.rs | 30 +++++++------- askar-crypto/src/alg/x25519.rs | 12 +++--- askar-crypto/src/any.rs | 38 ----------------- askar-crypto/src/buffer/mod.rs | 4 +- askar-crypto/src/encrypt/mod.rs | 1 + askar-crypto/src/encrypt/nacl_box.rs | 6 +-- askar-crypto/src/error.rs | 61 +++++++++++----------------- askar-crypto/src/jwk/borrow.rs | 5 +-- askar-crypto/src/jwk/parts.rs | 6 +-- askar-crypto/src/kdf/argon2.rs | 11 +++-- askar-crypto/src/kdf/concat.rs | 4 +- askar-crypto/src/kdf/mod.rs | 8 ++-- askar-crypto/src/random.rs | 7 +++- askar-crypto/src/sign.rs | 9 ++-- 21 files changed, 121 insertions(+), 173 deletions(-) diff --git a/askar-crypto/Cargo.toml b/askar-crypto/Cargo.toml index 6104a548..7969d710 100644 --- a/askar-crypto/Cargo.toml +++ b/askar-crypto/Cargo.toml @@ -3,7 +3,7 @@ name = "askar-crypto" version = "0.1.0" authors = ["Hyperledger Aries Contributors "] edition = "2018" -description = "Hyperledger Aries Askar key management" +description = "Hyperledger Aries Askar cryptography" license = "MIT OR Apache-2.0" readme = "README.md" repository = "https://github.com/hyperledger/aries-askar/" diff --git a/askar-crypto/src/alg/aesgcm.rs b/askar-crypto/src/alg/aesgcm.rs index f2cd12e3..3f2ab63a 100644 --- a/askar-crypto/src/alg/aesgcm.rs +++ b/askar-crypto/src/alg/aesgcm.rs @@ -107,7 +107,7 @@ impl KeyGen for AesGcmKey { impl KeySecretBytes for AesGcmKey { fn from_secret_bytes(key: &[u8]) -> Result { if key.len() != ::KeySize::USIZE { - return Err(err_msg!("Invalid length for AES-GCM key")); + return Err(err_msg!(InvalidKeyData)); } Ok(Self(KeyType::::from_slice(key))) } @@ -137,7 +137,7 @@ impl KeyAeadInPlace for AesGcmKey { let chacha = T::Aead::new(self.0.as_ref()); let tag = chacha .encrypt_in_place_detached(nonce, aad, buffer.as_mut()) - .map_err(|e| err_msg!(Encryption, "{}", e))?; + .map_err(|_| err_msg!(Encryption, "AEAD encryption error"))?; buffer.write_slice(&tag[..])?; Ok(()) } @@ -150,15 +150,12 @@ impl KeyAeadInPlace for AesGcmKey { aad: &[u8], ) -> Result<(), Error> { if nonce.len() != NonceSize::::USIZE { - return Err(err_msg!( - "invalid size for nonce (expected {} bytes)", - NonceSize::::USIZE - )); + return Err(err_msg!(InvalidNonce)); } let nonce = GenericArray::from_slice(nonce); let buf_len = buffer.as_ref().len(); if buf_len < TagSize::::USIZE { - return Err(err_msg!("invalid size for encrypted data")); + return Err(err_msg!(Encryption, "Invalid size for encrypted data")); } let tag_start = buf_len - TagSize::::USIZE; let mut tag = GenericArray::default(); @@ -166,7 +163,7 @@ impl KeyAeadInPlace for AesGcmKey { let chacha = T::Aead::new(self.0.as_ref()); chacha .decrypt_in_place_detached(nonce, aad, &mut buffer.as_mut()[..tag_start], &tag) - .map_err(|e| err_msg!(Encryption, "{}", e))?; + .map_err(|_| err_msg!(Encryption, "AEAD decryption error"))?; buffer.buffer_resize(tag_start)?; Ok(()) } @@ -206,7 +203,7 @@ where let mut buf = Writer::from_slice(key.0.as_mut()); lhs.key_exchange_buffer(rhs, &mut buf)?; if buf.position() != Self::KEY_LENGTH { - return Err(err_msg!("invalid length for key exchange output")); + return Err(err_msg!(Usage, "Invalid length for key exchange output")); } Ok(key) } diff --git a/askar-crypto/src/alg/bls.rs b/askar-crypto/src/alg/bls.rs index a95c5c64..e771719b 100644 --- a/askar-crypto/src/alg/bls.rs +++ b/askar-crypto/src/alg/bls.rs @@ -60,7 +60,7 @@ impl Bls12PrivateKey { pub fn from_bytes(sk: &[u8]) -> Result { if sk.len() != 32 { - return Err(err_msg!("Invalid key length")); + return Err(err_msg!(InvalidKeyLength)); } let mut skb = [0u8; 32]; skb.copy_from_slice(sk); @@ -68,9 +68,7 @@ impl Bls12PrivateKey { let result: Option = Scalar::from_bytes(&skb).into(); // turn into little-endian format skb.zeroize(); - Ok(Self( - result.ok_or_else(|| err_msg!("Invalid keypair bytes"))?, - )) + Ok(Self(result.ok_or_else(|| err_msg!(InvalidKeyData))?)) } pub fn to_bytes(&self) -> SecretBytes { diff --git a/askar-crypto/src/alg/chacha20.rs b/askar-crypto/src/alg/chacha20.rs index 4c809383..30f500a1 100644 --- a/askar-crypto/src/alg/chacha20.rs +++ b/askar-crypto/src/alg/chacha20.rs @@ -113,7 +113,7 @@ impl KeyGen for Chacha20Key { impl KeySecretBytes for Chacha20Key { fn from_secret_bytes(key: &[u8]) -> Result { if key.len() != ::KeySize::USIZE { - return Err(err_msg!("Invalid length for chacha20 key")); + return Err(err_msg!(InvalidKeyData)); } Ok(Self(KeyType::::from_slice(key))) } @@ -150,16 +150,13 @@ impl KeyAeadInPlace for Chacha20Key { aad: &[u8], ) -> Result<(), Error> { if nonce.len() != NonceSize::::USIZE { - return Err(err_msg!( - "invalid size for nonce (expected {} bytes)", - NonceSize::::USIZE - )); + return Err(err_msg!(InvalidNonce)); } let nonce = GenericArray::from_slice(nonce); let chacha = T::Aead::new(self.0.as_ref()); let tag = chacha .encrypt_in_place_detached(nonce, aad, buffer.as_mut()) - .map_err(|e| err_msg!(Encryption, "{}", e))?; + .map_err(|_| err_msg!(Encryption, "AEAD encryption error"))?; buffer.write_slice(&tag[..])?; Ok(()) } @@ -177,7 +174,7 @@ impl KeyAeadInPlace for Chacha20Key { let nonce = GenericArray::from_slice(nonce); let buf_len = buffer.as_ref().len(); if buf_len < TagSize::::USIZE { - return Err(err_msg!("invalid size for encrypted data")); + return Err(err_msg!(InvalidData, "Invalid size for encrypted data")); } let tag_start = buf_len - TagSize::::USIZE; let mut tag = GenericArray::default(); @@ -185,7 +182,7 @@ impl KeyAeadInPlace for Chacha20Key { let chacha = T::Aead::new(self.0.as_ref()); chacha .decrypt_in_place_detached(nonce, aad, &mut buffer.as_mut()[..tag_start], &tag) - .map_err(|e| err_msg!(Encryption, "{}", e))?; + .map_err(|_| err_msg!(Encryption, "AEAD decryption error"))?; buffer.buffer_resize(tag_start)?; Ok(()) } @@ -225,7 +222,7 @@ where let mut buf = Writer::from_slice(key.0.as_mut()); lhs.key_exchange_buffer(rhs, &mut buf)?; if buf.position() != Self::KEY_LENGTH { - return Err(err_msg!("invalid length for key exchange output")); + return Err(err_msg!(Usage, "Invalid length for key exchange output")); } Ok(key) } diff --git a/askar-crypto/src/alg/ed25519.rs b/askar-crypto/src/alg/ed25519.rs index ff8b5f5a..8865a521 100644 --- a/askar-crypto/src/alg/ed25519.rs +++ b/askar-crypto/src/alg/ed25519.rs @@ -131,7 +131,7 @@ impl KeyMeta for Ed25519KeyPair { impl KeySecretBytes for Ed25519KeyPair { fn from_secret_bytes(key: &[u8]) -> Result { if key.len() != SECRET_KEY_LENGTH { - return Err(err_msg!("Invalid ed25519 secret key length")); + return Err(err_msg!(InvalidKeyData)); } let sk = SecretKey::from_bytes(key).expect("Error loading ed25519 key"); Ok(Self::from_secret_key(sk)) @@ -150,12 +150,12 @@ impl KeypairMeta for Ed25519KeyPair { impl KeypairBytes for Ed25519KeyPair { fn from_keypair_bytes(kp: &[u8]) -> Result { if kp.len() != KEYPAIR_LENGTH { - return Err(err_msg!("Invalid keypair bytes")); + return Err(err_msg!(InvalidKeyData)); } // NB: this is infallible if the slice is the right length let sk = SecretKey::from_bytes(&kp[..SECRET_KEY_LENGTH]).unwrap(); let pk = PublicKey::from_bytes(&kp[SECRET_KEY_LENGTH..]) - .map_err(|_| err_msg!("Invalid ed25519 public key bytes"))?; + .map_err(|_| err_msg!(InvalidKeyData))?; // FIXME: derive pk from sk and check value? Ok(Self { @@ -179,11 +179,11 @@ impl KeypairBytes for Ed25519KeyPair { impl KeyPublicBytes for Ed25519KeyPair { fn from_public_bytes(key: &[u8]) -> Result { if key.len() != PUBLIC_KEY_LENGTH { - return Err(err_msg!("Invalid ed25519 public key length")); + return Err(err_msg!(InvalidKeyData)); } Ok(Self { secret: None, - public: PublicKey::from_bytes(key).map_err(|_| err_msg!("Invalid keypair bytes"))?, + public: PublicKey::from_bytes(key).map_err(|_| err_msg!(InvalidKeyData))?, }) } @@ -254,19 +254,15 @@ impl FromJwk for Ed25519KeyPair { // SECURITY: ArrayKey zeroizes on drop let mut pk = ArrayKey::::default(); if jwk.x.decode_base64(pk.as_mut())? != pk.len() { - return Err(err_msg!("invalid length for ed25519 attribute 'x'")); + return Err(err_msg!(InvalidKeyData)); } - let pk = PublicKey::from_bytes(&pk.as_ref()[..]) - .map_err(|_| err_msg!("Invalid ed25519 public key bytes"))?; + let pk = PublicKey::from_bytes(&pk.as_ref()[..]).map_err(|_| err_msg!(InvalidKeyData))?; let sk = if jwk.d.is_some() { let mut sk = ArrayKey::::default(); if jwk.d.decode_base64(sk.as_mut())? != sk.len() { - return Err(err_msg!("invalid length for ed25519 attribute 'd'")); + return Err(err_msg!(InvalidKeyData)); } - Some( - SecretKey::from_bytes(&sk.as_ref()[..]) - .map_err(|_| err_msg!("Invalid ed25519 secret key bytes"))?, - ) + Some(SecretKey::from_bytes(&sk.as_ref()[..]).map_err(|_| err_msg!(InvalidKeyData))?) } else { None }; diff --git a/askar-crypto/src/alg/k256.rs b/askar-crypto/src/alg/k256.rs index 41ea2310..b8138c06 100644 --- a/askar-crypto/src/alg/k256.rs +++ b/askar-crypto/src/alg/k256.rs @@ -85,7 +85,7 @@ impl KeyMeta for K256KeyPair { impl KeySecretBytes for K256KeyPair { fn from_secret_bytes(key: &[u8]) -> Result { Ok(Self::from_secret_key( - SecretKey::from_bytes(key).map_err(|_| err_msg!("Invalid k-256 secret key bytes"))?, + SecretKey::from_bytes(key).map_err(|_| err_msg!(InvalidKeyData))?, )) } @@ -107,13 +107,13 @@ impl KeypairMeta for K256KeyPair { impl KeypairBytes for K256KeyPair { fn from_keypair_bytes(kp: &[u8]) -> Result { if kp.len() != KEYPAIR_LENGTH { - return Err(err_msg!("Invalid keypair bytes")); + return Err(err_msg!(InvalidKeyData)); } let sk = SecretKey::from_bytes(&kp[..SECRET_KEY_LENGTH]) - .map_err(|_| err_msg!("Invalid k-256 secret key bytes"))?; + .map_err(|_| err_msg!(InvalidKeyData))?; let pk = EncodedPoint::from_bytes(&kp[SECRET_KEY_LENGTH..]) .and_then(|pt| pt.decode()) - .map_err(|_| err_msg!("Invalid k-256 public key bytes"))?; + .map_err(|_| err_msg!(InvalidKeyData))?; // FIXME: derive pk from sk and check value? Ok(Self { @@ -140,7 +140,7 @@ impl KeyPublicBytes for K256KeyPair { fn from_public_bytes(key: &[u8]) -> Result { let pk = EncodedPoint::from_bytes(key) .and_then(|pt| pt.decode()) - .map_err(|_| err_msg!("Invalid k-256 public key bytes"))?; + .map_err(|_| err_msg!(InvalidKeyData))?; Ok(Self { secret: None, public: pk, @@ -194,7 +194,12 @@ impl ToJwk for K256KeyPair { fn to_jwk_buffer(&self, buffer: &mut JwkEncoder) -> Result<(), Error> { let encp = EncodedPoint::encode(self.public, false); let (x, y) = match encp.coordinates() { - Coordinates::Identity => return Err(err_msg!("Cannot convert identity point to JWK")), + Coordinates::Identity => { + return Err(err_msg!( + Unsupported, + "Cannot convert identity point to JWK" + )) + } Coordinates::Uncompressed { x, y } => (x, y), Coordinates::Compressed { .. } => unreachable!(), }; @@ -223,23 +228,20 @@ impl FromJwk for K256KeyPair { let mut pk_x = ArrayKey::::default(); let mut pk_y = ArrayKey::::default(); if jwk.x.decode_base64(pk_x.as_mut())? != pk_x.len() { - return Err(err_msg!("invalid length for p-256 attribute 'x'")); + return Err(err_msg!(InvalidKeyData)); } if jwk.y.decode_base64(pk_y.as_mut())? != pk_y.len() { - return Err(err_msg!("invalid length for p-256 attribute 'y'")); + return Err(err_msg!(InvalidKeyData)); } let pk = EncodedPoint::from_affine_coordinates(pk_x.as_ref(), pk_y.as_ref(), false) .decode() - .map_err(|_| err_msg!("error decoding p-256 public key"))?; + .map_err(|_| err_msg!(InvalidKeyData))?; let sk = if jwk.d.is_some() { let mut sk = ArrayKey::::default(); if jwk.d.decode_base64(sk.as_mut())? != sk.len() { - return Err(err_msg!("invalid length for p-256 attribute 'd'")); + return Err(err_msg!(InvalidKeyData)); } - Some( - SecretKey::from_bytes(sk.as_ref()) - .map_err(|_| err_msg!("Invalid p-256 secret key bytes"))?, - ) + Some(SecretKey::from_bytes(sk.as_ref()).map_err(|_| err_msg!(InvalidKeyData))?) } else { None }; diff --git a/askar-crypto/src/alg/mod.rs b/askar-crypto/src/alg/mod.rs index 2e8ae87c..a8a8e86f 100644 --- a/askar-crypto/src/alg/mod.rs +++ b/askar-crypto/src/alg/mod.rs @@ -78,7 +78,7 @@ impl FromStr for KeyAlg { "p256" => Self::EcCurve(EcCurves::Secp256r1), "secp256k1" => Self::EcCurve(EcCurves::Secp256k1), "secp256r1" => Self::EcCurve(EcCurves::Secp256r1), - _ => return Err(err_msg!("Unknown key algorithm: {}", s)), + _ => return Err(err_msg!(Unsupported, "Unknown key algorithm")), }) } } diff --git a/askar-crypto/src/alg/p256.rs b/askar-crypto/src/alg/p256.rs index bac88550..c60acbd6 100644 --- a/askar-crypto/src/alg/p256.rs +++ b/askar-crypto/src/alg/p256.rs @@ -84,7 +84,7 @@ impl KeyMeta for P256KeyPair { impl KeySecretBytes for P256KeyPair { fn from_secret_bytes(key: &[u8]) -> Result { Ok(Self::from_secret_key( - SecretKey::from_bytes(key).map_err(|_| err_msg!("Invalid p-256 secret key bytes"))?, + SecretKey::from_bytes(key).map_err(|_| err_msg!(InvalidKeyData))?, )) } @@ -106,13 +106,13 @@ impl KeypairMeta for P256KeyPair { impl KeypairBytes for P256KeyPair { fn from_keypair_bytes(kp: &[u8]) -> Result { if kp.len() != KEYPAIR_LENGTH { - return Err(err_msg!("Invalid keypair bytes")); + return Err(err_msg!(InvalidKeyData)); } let sk = SecretKey::from_bytes(&kp[..SECRET_KEY_LENGTH]) - .map_err(|_| err_msg!("Invalid k-256 secret key bytes"))?; + .map_err(|_| err_msg!(InvalidKeyData))?; let pk = EncodedPoint::from_bytes(&kp[SECRET_KEY_LENGTH..]) .and_then(|pt| pt.decode()) - .map_err(|_| err_msg!("Invalid k-256 public key bytes"))?; + .map_err(|_| err_msg!(InvalidKeyData))?; // FIXME: derive pk from sk and check value? Ok(Self { @@ -139,7 +139,7 @@ impl KeyPublicBytes for P256KeyPair { fn from_public_bytes(key: &[u8]) -> Result { let pk = EncodedPoint::from_bytes(key) .and_then(|pt| pt.decode()) - .map_err(|_| err_msg!("Invalid p-256 public key bytes"))?; + .map_err(|_| err_msg!(InvalidKeyData))?; Ok(Self { secret: None, public: pk, @@ -193,7 +193,12 @@ impl ToJwk for P256KeyPair { fn to_jwk_buffer(&self, buffer: &mut JwkEncoder) -> Result<(), Error> { let encp = EncodedPoint::encode(self.public, false); let (x, y) = match encp.coordinates() { - Coordinates::Identity => return Err(err_msg!("Cannot convert identity point to JWK")), + Coordinates::Identity => { + return Err(err_msg!( + Unsupported, + "Cannot convert identity point to JWK" + )) + } Coordinates::Uncompressed { x, y } => (x, y), Coordinates::Compressed { .. } => unreachable!(), }; @@ -222,23 +227,20 @@ impl FromJwk for P256KeyPair { let mut pk_x = ArrayKey::::default(); let mut pk_y = ArrayKey::::default(); if jwk.x.decode_base64(pk_x.as_mut())? != pk_x.len() { - return Err(err_msg!("invalid length for p-256 attribute 'x'")); + return Err(err_msg!(InvalidKeyData)); } if jwk.y.decode_base64(pk_y.as_mut())? != pk_y.len() { - return Err(err_msg!("invalid length for p-256 attribute 'y'")); + return Err(err_msg!(InvalidKeyData)); } let pk = EncodedPoint::from_affine_coordinates(pk_x.as_ref(), pk_y.as_ref(), false) .decode() - .map_err(|_| err_msg!("error decoding p-256 public key"))?; + .map_err(|_| err_msg!(InvalidKeyData))?; let sk = if jwk.d.is_some() { let mut sk = ArrayKey::::default(); if jwk.d.decode_base64(sk.as_mut())? != sk.len() { - return Err(err_msg!("invalid length for p-256 attribute 'd'")); + return Err(err_msg!(InvalidKeyData)); } - Some( - SecretKey::from_bytes(sk.as_ref()) - .map_err(|_| err_msg!("Invalid p-256 secret key bytes"))?, - ) + Some(SecretKey::from_bytes(sk.as_ref()).map_err(|_| err_msg!(InvalidKeyData))?) } else { None }; diff --git a/askar-crypto/src/alg/x25519.rs b/askar-crypto/src/alg/x25519.rs index d8ab071a..2b8e8f5c 100644 --- a/askar-crypto/src/alg/x25519.rs +++ b/askar-crypto/src/alg/x25519.rs @@ -84,12 +84,12 @@ impl KeyMeta for X25519KeyPair { impl KeySecretBytes for X25519KeyPair { fn from_secret_bytes(key: &[u8]) -> Result { if key.len() != SECRET_KEY_LENGTH { - return Err(err_msg!("Invalid x25519 key length")); + return Err(err_msg!(InvalidKeyData)); } // pre-check key to ensure that clamping has no effect if key[0] & 7 != 0 || (key[31] & 127 | 64) != key[31] { - return Err(err_msg!("invalid x25519 secret key")); + return Err(err_msg!(InvalidKeyData)); } let sk = SecretKey::from(TryInto::<[u8; SECRET_KEY_LENGTH]>::try_into(key).unwrap()); @@ -122,7 +122,7 @@ impl KeypairMeta for X25519KeyPair { impl KeypairBytes for X25519KeyPair { fn from_keypair_bytes(kp: &[u8]) -> Result { if kp.len() != KEYPAIR_LENGTH { - return Err(err_msg!("Invalid keypair bytes")); + return Err(err_msg!(InvalidKeyData)); } let sk = SecretKey::from( TryInto::<[u8; SECRET_KEY_LENGTH]>::try_into(&kp[..SECRET_KEY_LENGTH]).unwrap(), @@ -154,7 +154,7 @@ impl KeypairBytes for X25519KeyPair { impl KeyPublicBytes for X25519KeyPair { fn from_public_bytes(key: &[u8]) -> Result { if key.len() != PUBLIC_KEY_LENGTH { - return Err(err_msg!("Invalid x25519 public key length")); + return Err(err_msg!(InvalidKeyData)); } Ok(Self::new( None, @@ -191,7 +191,7 @@ impl FromJwk for X25519KeyPair { // SECURITY: ArrayKey zeroizes on drop let mut pk = ArrayKey::::default(); if jwk.x.decode_base64(pk.as_mut())? != pk.len() { - return Err(err_msg!("invalid length for x25519 attribute 'x'")); + return Err(err_msg!(InvalidKeyData)); } let pk = PublicKey::from( TryInto::<[u8; PUBLIC_KEY_LENGTH]>::try_into(&pk.as_ref()[..]).unwrap(), @@ -199,7 +199,7 @@ impl FromJwk for X25519KeyPair { let sk = if jwk.d.is_some() { let mut sk = ArrayKey::::default(); if jwk.d.decode_base64(sk.as_mut())? != sk.len() { - return Err(err_msg!("invalid length for x25519 attribute 'd'")); + return Err(err_msg!(InvalidKeyData)); } Some(SecretKey::from( TryInto::<[u8; SECRET_KEY_LENGTH]>::try_into(&sk.as_ref()[..]).unwrap(), diff --git a/askar-crypto/src/any.rs b/askar-crypto/src/any.rs index 20540a68..73b72642 100644 --- a/askar-crypto/src/any.rs +++ b/askar-crypto/src/any.rs @@ -27,25 +27,6 @@ impl AnyPublicKey { } } -// impl TryFrom for AnyPublicKey { -// type Error = Error; - -// fn try_from(value: KeyEntry) -> Result { -// if value.category == KeyCategory::PublicKey { -// if let Some(data) = value.params.data { -// Ok(AnyPublicKey { -// alg: value.params.alg, -// data: data.into_vec(), -// }) -// } else { -// Err(err_msg!(Unsupported, "Missing public key raw data")) -// } -// } else { -// Err(err_msg!(Unsupported, "Not a public key entry")) -// } -// } -// } - impl KeyCapVerify for AnyPublicKey { fn key_verify( &self, @@ -123,22 +104,3 @@ impl KeyCapVerify for AnyPrivateKey { } } } - -// impl TryFrom for AnyPrivateKey { -// type Error = Error; - -// fn try_from(value: KeyEntry) -> Result { -// if value.category == KeyCategory::PrivateKey { -// if let Some(data) = value.params.data { -// Ok(AnyPrivateKey { -// alg: value.params.alg, -// data, -// }) -// } else { -// Err(err_msg!(Unsupported, "Missing private key raw data")) -// } -// } else { -// Err(err_msg!(Unsupported, "Not a private key entry")) -// } -// } -// } diff --git a/askar-crypto/src/buffer/mod.rs b/askar-crypto/src/buffer/mod.rs index 746802ac..aece1dd1 100644 --- a/askar-crypto/src/buffer/mod.rs +++ b/askar-crypto/src/buffer/mod.rs @@ -1,6 +1,8 @@ #[cfg(feature = "alloc")] use alloc::vec::Vec; -use core::{iter, ops::Range}; +#[cfg(feature = "alloc")] +use core::iter; +use core::ops::Range; use crate::error::Error; diff --git a/askar-crypto/src/encrypt/mod.rs b/askar-crypto/src/encrypt/mod.rs index 70b159d3..4b973040 100644 --- a/askar-crypto/src/encrypt/mod.rs +++ b/askar-crypto/src/encrypt/mod.rs @@ -5,6 +5,7 @@ use crate::{ random::fill_random, }; +#[cfg(feature = "alloc")] // FIXME - support non-alloc pub mod nacl_box; /// Trait for key types which perform AEAD encryption diff --git a/askar-crypto/src/encrypt/nacl_box.rs b/askar-crypto/src/encrypt/nacl_box.rs index f849010e..7ba3f426 100644 --- a/askar-crypto/src/encrypt/nacl_box.rs +++ b/askar-crypto/src/encrypt/nacl_box.rs @@ -61,7 +61,7 @@ pub fn crypto_box( let box_inst = SalsaBox::new(&recip_pk.public, &sender_sk); let tag = box_inst .encrypt_in_place_detached(nonce, &[], buffer.as_mut()) - .map_err(|_| err_msg!(Encryption, "Error encrypting box"))?; + .map_err(|_| err_msg!(Encryption, "Crypto box AEAD encryption error"))?; buffer.write_slice(&tag[..])?; Ok(()) } @@ -76,7 +76,7 @@ pub fn crypto_box_open( let nonce = nonce_from(nonce)?; let buf_len = buffer.as_ref().len(); if buf_len < TagSize::::USIZE { - return Err(err_msg!("invalid size for encrypted data")); + return Err(err_msg!(Encryption, "Invalid size for encrypted data")); } let tag_start = buf_len - TagSize::::USIZE; let mut tag = GenericArray::default(); @@ -86,7 +86,7 @@ pub fn crypto_box_open( box_inst .decrypt_in_place_detached(nonce, &[], &mut buffer.as_mut()[..tag_start], &tag) - .map_err(|_| err_msg!(Encryption, "Error decrypting box"))?; + .map_err(|_| err_msg!(Encryption, "Crypto box AEAD decryption error"))?; buffer.buffer_resize(tag_start)?; Ok(()) } diff --git a/askar-crypto/src/error.rs b/askar-crypto/src/error.rs index 571c5e1f..e0b3d3c9 100644 --- a/askar-crypto/src/error.rs +++ b/askar-crypto/src/error.rs @@ -1,6 +1,5 @@ #[cfg(feature = "std")] use alloc::boxed::Box; -use alloc::string::String; use core::fmt::{self, Display, Formatter}; #[cfg(feature = "std")] @@ -12,12 +11,15 @@ pub enum ErrorKind { /// An encryption or decryption operation failed Encryption, - /// The input parameters to the method were incorrect - Input, - /// Out of space in provided buffer ExceededBuffer, + /// The provided input was invalid + InvalidData, + + /// The provided key was invalid + InvalidKeyData, + /// The provided nonce was invalid (bad length) InvalidNonce, @@ -27,6 +29,9 @@ pub enum ErrorKind { /// An unexpected error occurred Unexpected, + /// The input parameters to the method were incorrect + Usage, + /// An unsupported operation was requested Unsupported, } @@ -37,10 +42,12 @@ impl ErrorKind { match self { Self::Encryption => "Encryption error", Self::ExceededBuffer => "Exceeded allocated buffer", - Self::Input => "Input error", + Self::InvalidData => "Invalid data", Self::InvalidNonce => "Invalid encryption nonce", + Self::InvalidKeyData => "Invalid key data", Self::MissingSecretKey => "Missing secret key", Self::Unexpected => "Unexpected error", + Self::Usage => "Usage error", Self::Unsupported => "Unsupported", } } @@ -56,28 +63,18 @@ impl Display for ErrorKind { #[derive(Debug)] pub struct Error { pub(crate) kind: ErrorKind, - #[cfg(not(feature = "std"))] - pub(crate) cause: Option, #[cfg(feature = "std")] pub(crate) cause: Option>, - pub(crate) message: Option, + pub(crate) message: Option<&'static str>, } impl Error { - pub(crate) fn from_msg>(kind: ErrorKind, msg: T) -> Self { + pub(crate) fn from_msg(kind: ErrorKind, msg: &'static str) -> Self { Self { kind, + #[cfg(feature = "std")] cause: None, - message: Some(msg.into()), - } - } - - #[allow(dead_code)] - pub(crate) fn from_opt_msg>(kind: ErrorKind, msg: Option) -> Self { - Self { - kind, - cause: None, - message: msg.map(Into::into), + message: Some(msg), } } @@ -87,14 +84,8 @@ impl Error { } /// Accessor for the error message - pub fn message(&self) -> Option<&str> { - self.message.as_ref().map(String::as_str) - } - - #[cfg(not(feature = "std"))] - pub(crate) fn with_cause>>(mut self, err: T) -> Self { - self.cause = err.into(); - self + pub fn message(&self) -> Option<&'static str> { + self.message } #[cfg(feature = "std")] @@ -106,11 +97,12 @@ impl Error { impl Display for Error { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - if let Some(msg) = self.message.as_ref() { + if let Some(msg) = self.message { f.write_str(msg)?; } else { f.write_str(self.kind.as_str())?; } + #[cfg(feature = "std")] if let Some(cause) = self.cause.as_ref() { write!(f, "\nCaused by: {}", cause)?; } @@ -137,6 +129,7 @@ impl From for Error { fn from(kind: ErrorKind) -> Self { Self { kind, + #[cfg(feature = "std")] cause: None, message: None, } @@ -144,17 +137,11 @@ impl From for Error { } macro_rules! err_msg { - () => { - $crate::error::Error::from($crate::error::ErrorKind::Input) - }; ($kind:ident) => { $crate::error::Error::from($crate::error::ErrorKind::$kind) }; - ($kind:ident, $($args:tt)+) => { - $crate::error::Error::from_msg($crate::error::ErrorKind::$kind, $crate::alloc::format!($($args)+)) - }; - ($($args:tt)+) => { - $crate::error::Error::from_msg($crate::error::ErrorKind::Input, $crate::alloc::format!($($args)+)) + ($kind:ident, $msg:expr) => { + $crate::error::Error::from_msg($crate::error::ErrorKind::$kind, $msg) }; } @@ -168,6 +155,6 @@ macro_rules! err_map { #[cfg(not(feature = "std"))] macro_rules! err_map { ($($params:tt)*) => { - |err| err_msg!($($params)*).with_cause(alloc::string::ToString::to_string(&err)) + |_| err_msg!($($params)*) }; } diff --git a/askar-crypto/src/jwk/borrow.rs b/askar-crypto/src/jwk/borrow.rs index 8a356e84..ffa3ca8f 100644 --- a/askar-crypto/src/jwk/borrow.rs +++ b/askar-crypto/src/jwk/borrow.rs @@ -14,9 +14,8 @@ pub enum Jwk<'a> { impl Jwk<'_> { pub fn to_parts(&self) -> Result, Error> { match self { - Self::Encoded(s) => Ok( - serde_json::from_str(s.as_ref()).map_err(err_map!("Error deserializing JWK"))? - ), + Self::Encoded(s) => Ok(serde_json::from_str(s.as_ref()) + .map_err(err_map!(InvalidData, "Error deserializing JWK"))?), Self::Parts(p) => Ok(*p), } } diff --git a/askar-crypto/src/jwk/parts.rs b/askar-crypto/src/jwk/parts.rs index 7c0fccef..608508cd 100644 --- a/askar-crypto/src/jwk/parts.rs +++ b/askar-crypto/src/jwk/parts.rs @@ -49,13 +49,13 @@ impl OptAttr<'_> { if let Some(s) = self.0 { let max_input = (output.len() * 4 + 2) / 3; // ceil(4*n/3) if s.len() > max_input { - Err(err_msg!("base64 length exceeds max")) + Err(err_msg!(InvalidData, "Base64 length exceeds max")) } else { base64::decode_config_slice(s, base64::URL_SAFE_NO_PAD, output) - .map_err(|e| err_msg!("base64 decode error: {}", e)) + .map_err(|_| err_msg!(InvalidData, "Base64 decode error")) } } else { - Err(err_msg!("empty attribute")) + Err(err_msg!(InvalidData, "Empty attribute")) } } } diff --git a/askar-crypto/src/kdf/argon2.rs b/askar-crypto/src/kdf/argon2.rs index 803659d0..956d0b64 100644 --- a/askar-crypto/src/kdf/argon2.rs +++ b/askar-crypto/src/kdf/argon2.rs @@ -40,17 +40,20 @@ impl Argon2 { output: &mut [u8], ) -> Result<(), Error> { if salt.len() < SALT_LENGTH { - return Err(err_msg!("invalid salt for argon2i hash")); + return Err(err_msg!(Usage, "Invalid salt for argon2i hash")); } if output.len() > u32::MAX as usize { - return Err(err_msg!("output length exceeds max for argon2i hash")); + return Err(err_msg!( + Usage, + "Output length exceeds max for argon2i hash" + )); } let context = argon2::Argon2::new(None, params.time_cost, params.mem_cost, 1, params.version) - .map_err(|e| err_msg!("Error creating hasher: {}", e))?; + .map_err(|_| err_msg!(Unexpected, "Error creating hasher"))?; context .hash_password_into(params.alg, password, salt, &[], output) - .map_err(|e| err_msg!("Error deriving key: {}", e)) + .map_err(|_| err_msg!(Unexpected, "Error deriving key")) } } diff --git a/askar-crypto/src/kdf/concat.rs b/askar-crypto/src/kdf/concat.rs index 2ae99176..7994fc39 100644 --- a/askar-crypto/src/kdf/concat.rs +++ b/askar-crypto/src/kdf/concat.rs @@ -31,7 +31,7 @@ where let output_len = output.len(); if output_len > u32::MAX as usize / 8 { // output_len is used as SuppPubInfo later - return Err(err_msg!("exceeded max output size for concat KDF")); + return Err(err_msg!(Usage, "Exceeded max output size for concat KDF")); } let mut hasher = ConcatKDFHash::::new(); let mut remain = output_len; @@ -104,7 +104,7 @@ impl WriteBuffer for ConcatKDFHash { // this could use a Vec to support larger inputs // but for current purposes a small fixed buffer is fine if max_len > HASH_BUFFER_SIZE { - return Err(err_msg!("exceeded hash buffer size")); + return Err(err_msg!(Usage, "Exceeded hash buffer size")); } let mut buf = [0u8; HASH_BUFFER_SIZE]; let written = f(&mut buf[..max_len])?; diff --git a/askar-crypto/src/kdf/mod.rs b/askar-crypto/src/kdf/mod.rs index 30d540c3..4252a4d0 100644 --- a/askar-crypto/src/kdf/mod.rs +++ b/askar-crypto/src/kdf/mod.rs @@ -1,7 +1,6 @@ -use crate::{ - buffer::{SecretBytes, WriteBuffer}, - error::Error, -}; +#[cfg(feature = "alloc")] +use crate::buffer::SecretBytes; +use crate::{buffer::WriteBuffer, error::Error}; pub mod argon2; @@ -14,6 +13,7 @@ pub mod ecdh_es; pub trait KeyExchange { fn key_exchange_buffer(&self, other: &Rhs, out: &mut B) -> Result<(), Error>; + #[cfg(feature = "alloc")] fn key_exchange_bytes(&self, other: &Rhs) -> Result { let mut buf = SecretBytes::with_capacity(128); self.key_exchange_buffer(other, &mut buf)?; diff --git a/askar-crypto/src/random.rs b/askar-crypto/src/random.rs index 8a008c87..0abeceda 100644 --- a/askar-crypto/src/random.rs +++ b/askar-crypto/src/random.rs @@ -5,7 +5,9 @@ use chacha20::{ }; use rand::{rngs::OsRng, RngCore}; -use crate::{buffer::SecretBytes, error::Error}; +#[cfg(feature = "alloc")] +use crate::buffer::SecretBytes; +use crate::error::Error; pub const SEED_LENGTH: usize = ::KeySize::USIZE; @@ -28,7 +30,7 @@ pub fn fill_random(value: &mut [u8]) { /// used to generate a deterministic symmetric encryption key pub fn fill_random_deterministic(seed: &[u8], output: &mut [u8]) -> Result<(), Error> { if seed.len() != SEED_LENGTH { - return Err(err_msg!("Invalid length for seed")); + return Err(err_msg!(Usage, "Invalid length for seed")); } let mut cipher = ChaCha20::new( GenericArray::from_slice(seed), @@ -38,6 +40,7 @@ pub fn fill_random_deterministic(seed: &[u8], output: &mut [u8]) -> Result<(), E Ok(()) } +#[cfg(feature = "alloc")] /// Create a new `SecretBytes` instance with random data. #[inline(always)] pub fn random_secret(len: usize) -> SecretBytes { diff --git a/askar-crypto/src/sign.rs b/askar-crypto/src/sign.rs index 27197dce..e9a0a557 100644 --- a/askar-crypto/src/sign.rs +++ b/askar-crypto/src/sign.rs @@ -1,8 +1,6 @@ -use crate::{ - alg::BlsGroups, - buffer::{SecretBytes, WriteBuffer}, - error::Error, -}; +#[cfg(feature = "alloc")] +use crate::buffer::SecretBytes; +use crate::{alg::BlsGroups, buffer::WriteBuffer, error::Error}; pub trait KeySign: KeySigVerify { fn key_sign_buffer( @@ -12,6 +10,7 @@ pub trait KeySign: KeySigVerify { out: &mut B, ) -> Result<(), Error>; + #[cfg(feature = "alloc")] fn key_sign(&self, data: &[u8], sig_type: Option) -> Result { let mut buf = SecretBytes::with_capacity(128); self.key_sign_buffer(data, sig_type, &mut buf)?; From 7b0e5d472626ba344fa387f1d4e7c7666cfc0777 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Thu, 15 Apr 2021 23:43:55 -0700 Subject: [PATCH 041/116] update sqlx to 0.5.2 Signed-off-by: Andrew Whitehead --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 480fbfe4..19c79228 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,7 +74,7 @@ default-features = false features = ["serde_support", "wql"] [dependencies.sqlx] -version = "0.5.1" +version = "0.5.2" default-features = false features = ["chrono", "runtime-async-std-rustls"] optional = true From 0fe623c0bf0f50b81684c0e7a6d0103eed4c6975 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Fri, 16 Apr 2021 10:52:04 -0700 Subject: [PATCH 042/116] rename wrap key to store key Signed-off-by: Andrew Whitehead --- src/backend/any.rs | 8 +- src/backend/db_utils.rs | 21 ++-- src/backend/postgres/mod.rs | 20 ++-- src/backend/postgres/provision.rs | 46 ++++----- src/backend/postgres/test_db.rs | 13 +-- src/backend/sqlite/mod.rs | 26 ++--- src/backend/sqlite/provision.rs | 44 ++++----- src/ffi/mod.rs | 4 +- src/ffi/store.rs | 30 +++--- src/lib.rs | 2 +- src/protect/kdf/argon2.rs | 10 +- src/protect/kdf/mod.rs | 6 +- src/protect/mod.rs | 14 +-- src/protect/pass_key.rs | 2 +- src/protect/{wrap_key.rs => store_key.rs} | 112 +++++++++++----------- src/storage/types.rs | 10 +- tests/backends.rs | 28 +++--- wrappers/python/aries_askar/bindings.py | 14 +-- wrappers/python/aries_askar/store.py | 14 ++- wrappers/python/demo/test.py | 2 +- 20 files changed, 217 insertions(+), 209 deletions(-) rename src/protect/{wrap_key.rs => store_key.rs} (74%) diff --git a/src/backend/any.rs b/src/backend/any.rs index 9169b43f..9e9d3d76 100644 --- a/src/backend/any.rs +++ b/src/backend/any.rs @@ -1,6 +1,6 @@ use crate::error::Error; use crate::future::BoxFuture; -use crate::protect::{PassKey, WrapKeyMethod}; +use crate::protect::{PassKey, StoreKeyMethod}; use crate::storage::{ entry::{Entry, EntryKind, EntryOperation, EntryTag, TagFilter}, @@ -102,7 +102,7 @@ impl Backend for AnyBackend { fn rekey_backend( &mut self, - method: WrapKeyMethod, + method: StoreKeyMethod, pass_key: PassKey<'_>, ) -> BoxFuture<'_, Result<(), Error>> { with_backend!(self, store, store.rekey_backend(method, pass_key)) @@ -248,7 +248,7 @@ impl<'a> ManageBackend<'a> for &'a str { fn open_backend( self, - method: Option, + method: Option, pass_key: PassKey<'a>, profile: Option<&'a str>, ) -> BoxFuture<'a, Result> { @@ -278,7 +278,7 @@ impl<'a> ManageBackend<'a> for &'a str { fn provision_backend( self, - method: WrapKeyMethod, + method: StoreKeyMethod, pass_key: PassKey<'a>, profile: Option<&'a str>, recreate: bool, diff --git a/src/backend/db_utils.rs b/src/backend/db_utils.rs index 2fa7aa7f..77e045e0 100644 --- a/src/backend/db_utils.rs +++ b/src/backend/db_utils.rs @@ -10,7 +10,7 @@ use sqlx::{ use crate::{ error::Error, future::BoxFuture, - protect::{EntryEncryptor, KeyCache, PassKey, ProfileId, ProfileKey, WrapKey, WrapKeyMethod}, + protect::{EntryEncryptor, KeyCache, PassKey, ProfileId, ProfileKey, StoreKey, StoreKeyMethod}, storage::{ entry::{EncEntryTag, Entry, EntryTag, TagFilter}, wql::{ @@ -610,22 +610,25 @@ where } pub fn init_keys<'a>( - method: WrapKeyMethod, + method: StoreKeyMethod, pass_key: PassKey<'a>, -) -> Result<(ProfileKey, Vec, WrapKey, String), Error> { - let (wrap_key, wrap_key_ref) = method.resolve(pass_key)?; +) -> Result<(ProfileKey, Vec, StoreKey, String), Error> { + let (store_key, store_key_ref) = method.resolve(pass_key)?; let profile_key = ProfileKey::new()?; - let enc_profile_key = encode_profile_key(&profile_key, &wrap_key)?; + let enc_profile_key = encode_profile_key(&profile_key, &store_key)?; Ok(( profile_key, enc_profile_key, - wrap_key, - wrap_key_ref.into_uri(), + store_key, + store_key_ref.into_uri(), )) } -pub fn encode_profile_key(profile_key: &ProfileKey, wrap_key: &WrapKey) -> Result, Error> { - wrap_key.wrap_data(profile_key.to_bytes()?) +pub fn encode_profile_key( + profile_key: &ProfileKey, + store_key: &StoreKey, +) -> Result, Error> { + store_key.wrap_data(profile_key.to_bytes()?) } #[inline] diff --git a/src/backend/postgres/mod.rs b/src/backend/postgres/mod.rs index bac65522..d5a26fbf 100644 --- a/src/backend/postgres/mod.rs +++ b/src/backend/postgres/mod.rs @@ -24,7 +24,7 @@ use crate::{ }, error::Error, future::{unblock, BoxFuture}, - protect::{EntryEncryptor, KeyCache, PassKey, ProfileId, ProfileKey, WrapKeyMethod}, + protect::{EntryEncryptor, KeyCache, PassKey, ProfileId, ProfileKey, StoreKeyMethod}, storage::{ entry::{EncEntryTag, Entry, EntryKind, EntryOperation, EntryTag, TagFilter}, types::{Backend, QueryBackend, Scan}, @@ -144,13 +144,13 @@ impl Backend for PostgresStore { fn rekey_backend( &mut self, - method: WrapKeyMethod, + method: StoreKeyMethod, pass_key: PassKey<'_>, ) -> BoxFuture<'_, Result<(), Error>> { let pass_key = pass_key.into_owned(); Box::pin(async move { - let (wrap_key, wrap_key_ref) = unblock(move || method.resolve(pass_key)).await?; - let wrap_key = Arc::new(wrap_key); + let (store_key, store_key_ref) = unblock(move || method.resolve(pass_key)).await?; + let store_key = Arc::new(store_key); let mut txn = self.conn_pool.begin().await?; let mut rows = sqlx::query("SELECT id, profile_key FROM profiles").fetch(&mut txn); let mut upd_keys = BTreeMap::>::new(); @@ -160,8 +160,8 @@ impl Backend for PostgresStore { let enc_key = row.try_get(1)?; let profile_key = self.key_cache.load_key(enc_key).await?; let upd_key = unblock({ - let wrap_key = wrap_key.clone(); - move || encode_profile_key(&profile_key, &wrap_key) + let store_key = store_key.clone(); + move || encode_profile_key(&profile_key, &store_key) }) .await?; upd_keys.insert(pid, upd_key); @@ -179,17 +179,17 @@ impl Backend for PostgresStore { return Err(err_msg!(Backend, "Error updating profile key")); } } - if sqlx::query("UPDATE config SET value=$1 WHERE name='wrap_key'") - .bind(wrap_key_ref.into_uri()) + if sqlx::query("UPDATE config SET value=$1 WHERE name='key'") + .bind(store_key_ref.into_uri()) .execute(&mut txn) .await? .rows_affected() != 1 { - return Err(err_msg!(Backend, "Error updating wrap key")); + return Err(err_msg!(Backend, "Error updating store key")); } txn.commit().await?; - self.key_cache = Arc::new(KeyCache::new(wrap_key)); + self.key_cache = Arc::new(KeyCache::new(store_key)); Ok(()) }) } diff --git a/src/backend/postgres/provision.rs b/src/backend/postgres/provision.rs index 7fe48619..d5dd43e0 100644 --- a/src/backend/postgres/provision.rs +++ b/src/backend/postgres/provision.rs @@ -11,7 +11,7 @@ use crate::{ backend::db_utils::{init_keys, random_profile_name}, error::Error, future::{unblock, BoxFuture}, - protect::{KeyCache, PassKey, ProfileId, WrapKeyMethod, WrapKeyReference}, + protect::{KeyCache, PassKey, ProfileId, StoreKeyMethod, StoreKeyReference}, storage::{ options::IntoOptions, types::{ManageBackend, Store}, @@ -169,7 +169,7 @@ impl PostgresStoreOptions { /// Provision a Postgres store from this set of configuration options pub async fn provision( self, - method: WrapKeyMethod, + method: StoreKeyMethod, pass_key: PassKey<'_>, profile: Option<&str>, recreate: bool, @@ -203,7 +203,7 @@ impl PostgresStoreOptions { // no 'config' table, assume empty database } - let (profile_key, enc_profile_key, wrap_key, wrap_key_ref) = unblock({ + let (profile_key, enc_profile_key, store_key, store_key_ref) = unblock({ let pass_key = pass_key.into_owned(); move || init_keys(method, pass_key) }) @@ -211,8 +211,8 @@ impl PostgresStoreOptions { let default_profile = profile .map(str::to_string) .unwrap_or_else(random_profile_name); - let profile_id = init_db(txn, &default_profile, wrap_key_ref, enc_profile_key).await?; - let mut key_cache = KeyCache::new(wrap_key); + let profile_id = init_db(txn, &default_profile, store_key_ref, enc_profile_key).await?; + let mut key_cache = KeyCache::new(store_key); key_cache.add_profile_mut(default_profile.clone(), profile_id, profile_key); Ok(Store::new(PostgresStore::new( @@ -227,7 +227,7 @@ impl PostgresStoreOptions { /// Open an existing Postgres store from this set of configuration options pub async fn open( self, - method: Option, + method: Option, pass_key: PassKey<'_>, profile: Option<&str>, ) -> Result, Error> { @@ -269,7 +269,7 @@ impl<'a> ManageBackend<'a> for PostgresStoreOptions { fn open_backend( self, - method: Option, + method: Option, pass_key: PassKey<'_>, profile: Option<&'a str>, ) -> BoxFuture<'a, Result, Error>> { @@ -279,7 +279,7 @@ impl<'a> ManageBackend<'a> for PostgresStoreOptions { fn provision_backend( self, - method: WrapKeyMethod, + method: StoreKeyMethod, pass_key: PassKey<'_>, profile: Option<&'a str>, recreate: bool, @@ -296,7 +296,7 @@ impl<'a> ManageBackend<'a> for PostgresStoreOptions { pub(crate) async fn init_db<'t>( mut txn: Transaction<'t, Postgres>, profile_name: &str, - wrap_key_ref: String, + store_key_ref: String, enc_profile_key: Vec, ) -> Result { txn.execute( @@ -350,12 +350,12 @@ pub(crate) async fn init_db<'t>( sqlx::query( "INSERT INTO config (name, value) VALUES ('default_profile', $1), - ('version', '1'), - ('wrap_key', $2)", + ('key', $2), + ('version', '1')", ) .persistent(false) .bind(profile_name) - .bind(wrap_key_ref) + .bind(store_key_ref) .execute(&mut txn) .await?; @@ -386,7 +386,7 @@ pub(crate) async fn reset_db(conn: &mut PgConnection) -> Result<(), Error> { pub(crate) async fn open_db( conn_pool: PgPool, - method: Option, + method: Option, pass_key: PassKey<'_>, profile: Option<&str>, host: String, @@ -395,11 +395,11 @@ pub(crate) async fn open_db( let mut conn = conn_pool.acquire().await?; let mut ver_ok = false; let mut default_profile: Option = None; - let mut wrap_key_ref: Option = None; + let mut store_key_ref: Option = None; let config = sqlx::query( r#"SELECT name, value FROM config - WHERE name IN ('default_profile', 'version', 'wrap_key')"#, + WHERE name IN ('default_profile', 'key', 'version')"#, ) .fetch_all(&mut conn) .await?; @@ -408,15 +408,15 @@ pub(crate) async fn open_db( "default_profile" => { default_profile.replace(row.try_get(1)?); } + "key" => { + store_key_ref.replace(row.try_get(1)?); + } "version" => { if row.try_get::<&str, _>(1)? != "1" { return Err(err_msg!(Unsupported, "Unsupported store version")); } ver_ok = true; } - "wrap_key" => { - wrap_key_ref.replace(row.try_get(1)?); - } _ => (), } } @@ -427,11 +427,11 @@ pub(crate) async fn open_db( .map(str::to_string) .or(default_profile) .ok_or_else(|| err_msg!(Unsupported, "Default store profile not found"))?; - let wrap_key = if let Some(wrap_key_ref) = wrap_key_ref { - let wrap_ref = WrapKeyReference::parse_uri(&wrap_key_ref)?; + let store_key = if let Some(store_key_ref) = store_key_ref { + let wrap_ref = StoreKeyReference::parse_uri(&store_key_ref)?; if let Some(method) = method { if !wrap_ref.compare_method(&method) { - return Err(err_msg!(Input, "Store wrap key method mismatch")); + return Err(err_msg!(Input, "Store key method mismatch")); } } unblock({ @@ -440,9 +440,9 @@ pub(crate) async fn open_db( }) .await? } else { - return Err(err_msg!(Unsupported, "Store wrap key not found")); + return Err(err_msg!(Unsupported, "Store key not found")); }; - let mut key_cache = KeyCache::new(wrap_key); + let mut key_cache = KeyCache::new(store_key); let row = sqlx::query("SELECT id, profile_key FROM profiles WHERE name = $1") .bind(&profile) diff --git a/src/backend/postgres/test_db.rs b/src/backend/postgres/test_db.rs index 2546991f..038c2ed1 100644 --- a/src/backend/postgres/test_db.rs +++ b/src/backend/postgres/test_db.rs @@ -12,7 +12,7 @@ use crate::{ backend::db_utils::{init_keys, random_profile_name}, error::Error, future::{block_on, unblock}, - protect::{generate_raw_wrap_key, KeyCache, WrapKeyMethod}, + protect::{generate_raw_store_key, KeyCache, StoreKeyMethod}, storage::types::Store, }; @@ -32,9 +32,9 @@ impl TestDB { _ => panic!("'POSTGRES_URL' must be defined"), }; - let key = generate_raw_wrap_key(None)?; - let (profile_key, enc_profile_key, wrap_key, wrap_key_ref) = - unblock(|| init_keys(WrapKeyMethod::RawKey, key)).await?; + let key = generate_raw_store_key(None)?; + let (profile_key, enc_profile_key, store_key, store_key_ref) = + unblock(|| init_keys(StoreKeyMethod::RawKey, key)).await?; let default_profile = random_profile_name(); let opts = PostgresStoreOptions::new(path.as_str())?; @@ -63,9 +63,10 @@ impl TestDB { reset_db(&mut *init_txn).await?; // create tables and add default profile - let profile_id = init_db(init_txn, &default_profile, wrap_key_ref, enc_profile_key).await?; + let profile_id = + init_db(init_txn, &default_profile, store_key_ref, enc_profile_key).await?; - let mut key_cache = KeyCache::new(wrap_key); + let mut key_cache = KeyCache::new(store_key); key_cache.add_profile_mut(default_profile.clone(), profile_id, profile_key); let inst = Store::new(PostgresStore::new( conn_pool, diff --git a/src/backend/sqlite/mod.rs b/src/backend/sqlite/mod.rs index aa015b30..2dcebe49 100644 --- a/src/backend/sqlite/mod.rs +++ b/src/backend/sqlite/mod.rs @@ -22,7 +22,7 @@ use crate::{ }, error::Error, future::{unblock, BoxFuture}, - protect::{EntryEncryptor, KeyCache, PassKey, ProfileId, ProfileKey, WrapKeyMethod}, + protect::{EntryEncryptor, KeyCache, PassKey, ProfileId, ProfileKey, StoreKeyMethod}, storage::entry::{EncEntryTag, Entry, EntryKind, EntryOperation, EntryTag, TagFilter}, storage::types::{Backend, QueryBackend, Scan}, }; @@ -134,13 +134,13 @@ impl Backend for SqliteStore { fn rekey_backend( &mut self, - method: WrapKeyMethod, + method: StoreKeyMethod, pass_key: PassKey<'_>, ) -> BoxFuture<'_, Result<(), Error>> { let pass_key = pass_key.into_owned(); Box::pin(async move { - let (wrap_key, wrap_key_ref) = unblock(move || method.resolve(pass_key)).await?; - let wrap_key = Arc::new(wrap_key); + let (store_key, store_key_ref) = unblock(move || method.resolve(pass_key)).await?; + let store_key = Arc::new(store_key); let mut txn = self.conn_pool.begin().await?; let mut rows = sqlx::query("SELECT id, profile_key FROM profiles").fetch(&mut txn); let mut upd_keys = BTreeMap::>::new(); @@ -150,8 +150,8 @@ impl Backend for SqliteStore { let enc_key = row.try_get(1)?; let profile_key = self.key_cache.load_key(enc_key).await?; let upd_key = unblock({ - let wrap_key = wrap_key.clone(); - move || encode_profile_key(&profile_key, &wrap_key) + let store_key = store_key.clone(); + move || encode_profile_key(&profile_key, &store_key) }) .await?; upd_keys.insert(pid, upd_key); @@ -169,17 +169,17 @@ impl Backend for SqliteStore { return Err(err_msg!(Backend, "Error updating profile key")); } } - if sqlx::query("UPDATE config SET value=?1 WHERE name='wrap_key'") - .bind(wrap_key_ref.into_uri()) + if sqlx::query("UPDATE config SET value=?1 WHERE name='key'") + .bind(store_key_ref.into_uri()) .execute(&mut txn) .await? .rows_affected() != 1 { - return Err(err_msg!(Backend, "Error updating wrap key")); + return Err(err_msg!(Backend, "Error updating store key")); } txn.commit().await?; - self.key_cache = Arc::new(KeyCache::new(wrap_key)); + self.key_cache = Arc::new(KeyCache::new(store_key)); Ok(()) }) } @@ -634,14 +634,14 @@ mod tests { use super::*; use crate::backend::db_utils::replace_arg_placeholders; use crate::future::block_on; - use crate::protect::{generate_raw_wrap_key, WrapKeyMethod}; + use crate::protect::{generate_raw_store_key, StoreKeyMethod}; #[test] fn sqlite_check_expiry_timestamp() { block_on(async { - let key = generate_raw_wrap_key(None)?; + let key = generate_raw_store_key(None)?; let db = SqliteStoreOptions::in_memory() - .provision(WrapKeyMethod::RawKey, key, None, false) + .provision(StoreKeyMethod::RawKey, key, None, false) .await?; let ts = expiry_timestamp(1000).unwrap(); let check = sqlx::query("SELECT datetime('now'), ?1, ?1 > datetime('now')") diff --git a/src/backend/sqlite/provision.rs b/src/backend/sqlite/provision.rs index 6574c984..b1e1da14 100644 --- a/src/backend/sqlite/provision.rs +++ b/src/backend/sqlite/provision.rs @@ -13,7 +13,7 @@ use crate::{ backend::db_utils::{init_keys, random_profile_name}, error::Error, future::{unblock, BoxFuture}, - protect::{KeyCache, PassKey, WrapKeyMethod, WrapKeyReference}, + protect::{KeyCache, PassKey, StoreKeyMethod, StoreKeyReference}, storage::options::{IntoOptions, Options}, storage::types::{ManageBackend, Store}, }; @@ -69,7 +69,7 @@ impl SqliteStoreOptions { /// Provision a new Sqlite store from these configuration options pub async fn provision( self, - method: WrapKeyMethod, + method: StoreKeyMethod, pass_key: PassKey<'_>, profile: Option<&'_ str>, recreate: bool, @@ -115,7 +115,7 @@ impl SqliteStoreOptions { /// Open an existing Sqlite store from this set of configuration options pub async fn open( self, - method: Option, + method: Option, pass_key: PassKey<'_>, profile: Option<&'_ str>, ) -> Result, Error> { @@ -166,7 +166,7 @@ impl<'a> ManageBackend<'a> for SqliteStoreOptions { fn open_backend( self, - method: Option, + method: Option, pass_key: PassKey<'a>, profile: Option<&'a str>, ) -> BoxFuture<'a, Result, Error>> { @@ -175,7 +175,7 @@ impl<'a> ManageBackend<'a> for SqliteStoreOptions { fn provision_backend( self, - method: WrapKeyMethod, + method: StoreKeyMethod, pass_key: PassKey<'a>, profile: Option<&'a str>, recreate: bool, @@ -191,10 +191,10 @@ impl<'a> ManageBackend<'a> for SqliteStoreOptions { async fn init_db( conn_pool: &SqlitePool, profile_name: &str, - method: WrapKeyMethod, + method: StoreKeyMethod, pass_key: PassKey<'_>, ) -> Result { - let (profile_key, enc_profile_key, wrap_key, wrap_key_ref) = unblock({ + let (profile_key, enc_profile_key, store_key, store_key_ref) = unblock({ let pass_key = pass_key.into_owned(); move || init_keys(method, pass_key) }) @@ -213,8 +213,8 @@ async fn init_db( ); INSERT INTO config (name, value) VALUES ("default_profile", ?1), - ("version", "1"), - ("wrap_key", ?2); + ("key", ?2), + ("version", "1"); CREATE TABLE profiles ( id INTEGER NOT NULL, @@ -260,12 +260,12 @@ async fn init_db( ) .persistent(false) .bind(profile_name) - .bind(wrap_key_ref) + .bind(store_key_ref) .bind(enc_profile_key) .execute(&mut conn) .await?; - let mut key_cache = KeyCache::new(wrap_key); + let mut key_cache = KeyCache::new(store_key); let row = sqlx::query("SELECT id FROM profiles WHERE name = ?1") .persistent(false) @@ -279,7 +279,7 @@ async fn init_db( async fn open_db( conn_pool: SqlitePool, - method: Option, + method: Option, pass_key: PassKey<'_>, profile: Option<&str>, path: String, @@ -287,11 +287,11 @@ async fn open_db( let mut conn = conn_pool.acquire().await?; let mut ver_ok = false; let mut default_profile: Option = None; - let mut wrap_key_ref: Option = None; + let mut store_key_ref: Option = None; let config = sqlx::query( r#"SELECT name, value FROM config - WHERE name IN ("default_profile", "version", "wrap_key")"#, + WHERE name IN ("default_profile", "key", "version")"#, ) .fetch_all(&mut conn) .await?; @@ -300,15 +300,15 @@ async fn open_db( "default_profile" => { default_profile.replace(row.try_get(1)?); } + "key" => { + store_key_ref.replace(row.try_get(1)?); + } "version" => { if row.try_get::<&str, _>(1)? != "1" { return Err(err_msg!(Unsupported, "Unsupported store version")); } ver_ok = true; } - "wrap_key" => { - wrap_key_ref.replace(row.try_get(1)?); - } _ => (), } } @@ -319,11 +319,11 @@ async fn open_db( .map(str::to_string) .or(default_profile) .ok_or_else(|| err_msg!(Unsupported, "Default store profile not found"))?; - let wrap_key = if let Some(wrap_key_ref) = wrap_key_ref { - let wrap_ref = WrapKeyReference::parse_uri(&wrap_key_ref)?; + let store_key = if let Some(store_key_ref) = store_key_ref { + let wrap_ref = StoreKeyReference::parse_uri(&store_key_ref)?; if let Some(method) = method { if !wrap_ref.compare_method(&method) { - return Err(err_msg!(Input, "Store wrap key method mismatch")); + return Err(err_msg!(Input, "Store key method mismatch")); } } unblock({ @@ -332,9 +332,9 @@ async fn open_db( }) .await? } else { - return Err(err_msg!(Unsupported, "Store wrap key not found")); + return Err(err_msg!(Unsupported, "Store key not found")); }; - let mut key_cache = KeyCache::new(wrap_key); + let mut key_cache = KeyCache::new(store_key); let row = sqlx::query("SELECT id, profile_key FROM profiles WHERE name = ?1") .bind(&profile) diff --git a/src/ffi/mod.rs b/src/ffi/mod.rs index 011e50cd..48461c77 100644 --- a/src/ffi/mod.rs +++ b/src/ffi/mod.rs @@ -23,7 +23,7 @@ mod store; use self::error::{set_last_error, ErrorCode}; use crate::error::Error; use crate::future::{spawn_ok, unblock}; -use crate::protect::generate_raw_wrap_key; +use crate::protect::generate_raw_store_key; pub type CallbackId = i64; @@ -82,7 +82,7 @@ pub extern "C" fn askar_generate_raw_key( } ); spawn_ok(async move { - let result = unblock(move || generate_raw_wrap_key( + let result = unblock(move || generate_raw_store_key( seed.as_ref().map(|s| s.as_slice()) ).map(|p| p.to_string())).await; cb.resolve(result); diff --git a/src/ffi/store.rs b/src/ffi/store.rs index 9be38c22..4ef63ec2 100644 --- a/src/ffi/store.rs +++ b/src/ffi/store.rs @@ -22,7 +22,7 @@ use crate::{ backend::any::{AnySession, AnyStore}, error::Error, future::spawn_ok, - protect::{PassKey, WrapKeyMethod}, + protect::{PassKey, StoreKeyMethod}, storage::{ entry::{Entry, EntryOperation, EntryTagSet, TagFilter}, key::{KeyCategory, KeyEntry}, @@ -273,7 +273,7 @@ impl FfiEntry { #[no_mangle] pub extern "C" fn askar_store_provision( spec_uri: FfiStr<'_>, - wrap_key_method: FfiStr<'_>, + key_method: FfiStr<'_>, pass_key: FfiStr<'_>, profile: FfiStr<'_>, recreate: i8, @@ -284,9 +284,9 @@ pub extern "C" fn askar_store_provision( trace!("Provision store"); let cb = cb.ok_or_else(|| err_msg!("No callback provided"))?; let spec_uri = spec_uri.into_opt_string().ok_or_else(|| err_msg!("No provision spec URI provided"))?; - let wrap_key_method = match wrap_key_method.as_opt_str() { - Some(method) => WrapKeyMethod::parse_uri(method)?, - None => WrapKeyMethod::default() + let key_method = match key_method.as_opt_str() { + Some(method) => StoreKeyMethod::parse_uri(method)?, + None => StoreKeyMethod::default() }; let pass_key = PassKey::from(pass_key.as_opt_str()).into_owned(); let profile = profile.into_opt_string(); @@ -302,7 +302,7 @@ pub extern "C" fn askar_store_provision( spawn_ok(async move { let result = async { let store = spec_uri.provision_backend( - wrap_key_method, + key_method, pass_key, profile.as_ref().map(String::as_str), recreate != 0 @@ -318,7 +318,7 @@ pub extern "C" fn askar_store_provision( #[no_mangle] pub extern "C" fn askar_store_open( spec_uri: FfiStr<'_>, - wrap_key_method: FfiStr<'_>, + key_method: FfiStr<'_>, pass_key: FfiStr<'_>, profile: FfiStr<'_>, cb: Option, @@ -328,8 +328,8 @@ pub extern "C" fn askar_store_open( trace!("Open store"); let cb = cb.ok_or_else(|| err_msg!("No callback provided"))?; let spec_uri = spec_uri.into_opt_string().ok_or_else(|| err_msg!("No store URI provided"))?; - let wrap_key_method = match wrap_key_method.as_opt_str() { - Some(method) => Some(WrapKeyMethod::parse_uri(method)?), + let key_method = match key_method.as_opt_str() { + Some(method) => Some(StoreKeyMethod::parse_uri(method)?), None => None }; let pass_key = PassKey::from(pass_key.as_opt_str()).into_owned(); @@ -346,7 +346,7 @@ pub extern "C" fn askar_store_open( spawn_ok(async move { let result = async { let store = spec_uri.open_backend( - wrap_key_method, + key_method, pass_key, profile.as_ref().map(String::as_str) ).await?; @@ -471,7 +471,7 @@ pub extern "C" fn askar_store_remove_profile( #[no_mangle] pub extern "C" fn askar_store_rekey( handle: StoreHandle, - wrap_key_method: FfiStr<'_>, + key_method: FfiStr<'_>, pass_key: FfiStr<'_>, cb: Option, cb_id: CallbackId, @@ -479,9 +479,9 @@ pub extern "C" fn askar_store_rekey( catch_err! { trace!("Re-key store"); let cb = cb.ok_or_else(|| err_msg!("No callback provided"))?; - let wrap_key_method = match wrap_key_method.as_opt_str() { - Some(method) => WrapKeyMethod::parse_uri(method)?, - None => WrapKeyMethod::default() + let key_method = match key_method.as_opt_str() { + Some(method) => StoreKeyMethod::parse_uri(method)?, + None => StoreKeyMethod::default() }; let pass_key = PassKey::from(pass_key.as_opt_str()).into_owned(); let cb = EnsureCallback::new(move |result| @@ -495,7 +495,7 @@ pub extern "C" fn askar_store_rekey( let store = handle.remove().await?; match Arc::try_unwrap(store) { Ok(mut store) => { - store.rekey(wrap_key_method, pass_key.as_ref()).await?; + store.rekey(key_method, pass_key.as_ref()).await?; handle.replace(Arc::new(store)).await; Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index 82bfe90b..18f1a79e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,7 +41,7 @@ extern crate serde_json; mod ffi; mod protect; -pub use protect::{generate_raw_wrap_key, PassKey, WrapKeyMethod}; +pub use protect::{generate_raw_store_key, PassKey, StoreKeyMethod}; mod storage; pub use storage::{ diff --git a/src/protect/kdf/argon2.rs b/src/protect/kdf/argon2.rs index 063c511f..73819319 100644 --- a/src/protect/kdf/argon2.rs +++ b/src/protect/kdf/argon2.rs @@ -5,7 +5,7 @@ use crate::{ repr::{KeyMeta, KeySecretBytes}, }, error::Error, - protect::wrap_key::{WrapKey, WrapKeyType}, + protect::store_key::{StoreKey, StoreKeyType}, }; pub use crate::crypto::kdf::argon2::SaltSize; @@ -53,9 +53,11 @@ impl Level { } } - pub fn derive_key(&self, password: &[u8], salt: &[u8]) -> Result { - let mut key = ArrayKey::<::KeySize>::default(); + pub fn derive_key(&self, password: &[u8], salt: &[u8]) -> Result { + let mut key = ArrayKey::<::KeySize>::default(); Argon2::derive_key(password, salt, *self.params(), key.as_mut())?; - Ok(WrapKey::from(WrapKeyType::from_secret_bytes(key.as_ref())?)) + Ok(StoreKey::from(StoreKeyType::from_secret_bytes( + key.as_ref(), + )?)) } } diff --git a/src/protect/kdf/mod.rs b/src/protect/kdf/mod.rs index f5cd3ef6..3a9c396f 100644 --- a/src/protect/kdf/mod.rs +++ b/src/protect/kdf/mod.rs @@ -1,4 +1,4 @@ -use super::wrap_key::{WrapKey, PREFIX_KDF}; +use super::store_key::{StoreKey, PREFIX_KDF}; use crate::{ crypto::{ buffer::{ArrayKey, HexRepr}, @@ -62,7 +62,7 @@ impl KdfMethod { } } - pub fn derive_new_key(&self, password: &str) -> Result<(WrapKey, String), Error> { + pub fn derive_new_key(&self, password: &str) -> Result<(StoreKey, String), Error> { match self { Self::Argon2i(level) => { let salt = level.generate_salt(); @@ -73,7 +73,7 @@ impl KdfMethod { } } - pub fn derive_key(&self, password: &str, detail: &str) -> Result { + pub fn derive_key(&self, password: &str, detail: &str) -> Result { match self { Self::Argon2i(level) => { let salt = parse_salt::(detail)?; diff --git a/src/protect/mod.rs b/src/protect/mod.rs index 6055fd28..2d969159 100644 --- a/src/protect/mod.rs +++ b/src/protect/mod.rs @@ -13,8 +13,8 @@ pub use self::pass_key::PassKey; mod profile_key; pub use self::profile_key::ProfileKey; -mod wrap_key; -pub use self::wrap_key::{generate_raw_wrap_key, WrapKey, WrapKeyMethod, WrapKeyReference}; +mod store_key; +pub use self::store_key::{generate_raw_store_key, StoreKey, StoreKeyMethod, StoreKeyReference}; use crate::{ crypto::buffer::SecretBytes, @@ -28,21 +28,21 @@ pub type ProfileId = i64; #[derive(Debug)] pub struct KeyCache { profile_info: Mutex)>>, - pub(crate) wrap_key: Arc, + pub(crate) store_key: Arc, } impl KeyCache { - pub fn new(wrap_key: impl Into>) -> Self { + pub fn new(store_key: impl Into>) -> Self { Self { profile_info: Mutex::new(HashMap::new()), - wrap_key: wrap_key.into(), + store_key: store_key.into(), } } pub async fn load_key(&self, ciphertext: Vec) -> Result { - let wrap_key = self.wrap_key.clone(); + let store_key = self.store_key.clone(); unblock(move || { - let mut data = wrap_key + let mut data = store_key .unwrap_data(ciphertext) .map_err(err_map!(Encryption, "Error decrypting profile key"))?; let key = ProfileKey::from_slice(&data)?; diff --git a/src/protect/pass_key.rs b/src/protect/pass_key.rs index 09101700..83901d6c 100644 --- a/src/protect/pass_key.rs +++ b/src/protect/pass_key.rs @@ -7,7 +7,7 @@ use std::{ ops::Deref, }; -/// A possibly-empty password or key used to derive a store wrap key +/// A possibly-empty password or key used to derive a store key #[derive(Clone)] pub struct PassKey<'a>(Option>); diff --git a/src/protect/wrap_key.rs b/src/protect/store_key.rs similarity index 74% rename from src/protect/wrap_key.rs rename to src/protect/store_key.rs index 5991f378..8be9e1c3 100644 --- a/src/protect/wrap_key.rs +++ b/src/protect/store_key.rs @@ -15,42 +15,44 @@ pub const PREFIX_KDF: &'static str = "kdf"; pub const PREFIX_RAW: &'static str = "raw"; pub const PREFIX_NONE: &'static str = "none"; -pub type WrapKeyType = Chacha20Key; +pub type StoreKeyType = Chacha20Key; -type WrapKeyNonce = ArrayKey<::NonceSize>; +type StoreKeyNonce = ArrayKey<::NonceSize>; -/// Create a new raw wrap key for a store -pub fn generate_raw_wrap_key(seed: Option<&[u8]>) -> Result, Error> { +/// Create a new raw (non-derived) store key +pub fn generate_raw_store_key(seed: Option<&[u8]>) -> Result, Error> { let key = if let Some(seed) = seed { - WrapKey::from(WrapKeyType::from_seed(seed)?) + StoreKey::from(StoreKeyType::from_seed(seed)?) } else { - WrapKey::from(WrapKeyType::generate()?) + StoreKey::from(StoreKeyType::generate()?) }; Ok(key.to_passkey()) } -pub fn parse_raw_key(raw_key: &str) -> Result { - let mut key = ArrayKey::<::KeySize>::default(); +pub fn parse_raw_store_key(raw_key: &str) -> Result { + let mut key = ArrayKey::<::KeySize>::default(); let key_len = bs58::decode(raw_key) .into(key.as_mut()) .map_err(|_| err_msg!(Input, "Error parsing raw key as base58 value"))?; if key_len != key.len() { Err(err_msg!(Input, "Incorrect length for encoded raw key")) } else { - Ok(WrapKey::from(WrapKeyType::from_secret_bytes(key.as_ref())?)) + Ok(StoreKey::from(StoreKeyType::from_secret_bytes( + key.as_ref(), + )?)) } } #[derive(Clone, Debug)] -pub struct WrapKey(pub Option); +pub struct StoreKey(pub Option); -impl WrapKey { +impl StoreKey { pub const fn empty() -> Self { Self(None) } pub fn random() -> Result { - Ok(Self(Some(WrapKeyType::generate()?))) + Ok(Self(Some(StoreKeyType::generate()?))) } pub fn is_empty(&self) -> bool { @@ -60,7 +62,7 @@ impl WrapKey { pub fn wrap_data(&self, mut data: SecretBytes) -> Result, Error> { match &self.0 { Some(key) => { - let nonce = WrapKeyNonce::random(); + let nonce = StoreKeyNonce::random(); key.encrypt_in_place(&mut data, nonce.as_ref(), &[])?; data.buffer_insert_slice(0, nonce.as_ref())?; Ok(data.into_vec()) @@ -72,9 +74,9 @@ impl WrapKey { pub fn unwrap_data(&self, ciphertext: Vec) -> Result { match &self.0 { Some(key) => { - let nonce = WrapKeyNonce::from_slice(&ciphertext[..WrapKeyNonce::SIZE]); + let nonce = StoreKeyNonce::from_slice(&ciphertext[..StoreKeyNonce::SIZE]); let mut buffer = SecretBytes::from(ciphertext); - buffer.buffer_remove(0..WrapKeyNonce::SIZE)?; + buffer.buffer_remove(0..StoreKeyNonce::SIZE)?; key.decrypt_in_place(&mut buffer, nonce.as_ref(), &[])?; Ok(buffer) } @@ -91,15 +93,15 @@ impl WrapKey { } } -impl From for WrapKey { - fn from(data: WrapKeyType) -> Self { +impl From for StoreKey { + fn from(data: StoreKeyType) -> Self { Self(Some(data)) } } -/// Supported methods for generating or referencing a new wrap key +/// Supported methods for generating or referencing a new store key #[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub enum WrapKeyMethod { +pub enum StoreKeyMethod { // CreateManagedKey(String), // ExistingManagedKey(String), /// Derive a new wrapping key using a key derivation function @@ -110,7 +112,7 @@ pub enum WrapKeyMethod { Unprotected, } -impl WrapKeyMethod { +impl StoreKeyMethod { pub(crate) fn parse_uri(uri: &str) -> Result { let mut prefix_and_detail = uri.splitn(2, ':'); let prefix = prefix_and_detail.next().unwrap_or_default(); @@ -122,21 +124,21 @@ impl WrapKeyMethod { None => Err(err_msg!(Unsupported, "Invalid key derivation method")), }, PREFIX_NONE => Ok(Self::Unprotected), - _ => Err(err_msg!(Unsupported, "Invalid wrap key method")), + _ => Err(err_msg!(Unsupported, "Invalid store key method")), } } pub(crate) fn resolve( &self, pass_key: PassKey<'_>, - ) -> Result<(WrapKey, WrapKeyReference), Error> { + ) -> Result<(StoreKey, StoreKeyReference), Error> { match self { // Self::CreateManagedKey(_mgr_ref) => unimplemented!(), // Self::ExistingManagedKey(String) => unimplemented!(), Self::DeriveKey(method) => { if !pass_key.is_none() { let (key, detail) = method.derive_new_key(&*pass_key)?; - let key_ref = WrapKeyReference::DeriveKey(*method, detail); + let key_ref = StoreKeyReference::DeriveKey(*method, detail); Ok((key, key_ref)) } else { Err(err_msg!(Input, "Key derivation password not provided")) @@ -144,32 +146,32 @@ impl WrapKeyMethod { } Self::RawKey => { let key = if !pass_key.is_empty() { - parse_raw_key(&*pass_key)? + parse_raw_store_key(&*pass_key)? } else { - WrapKey::random()? + StoreKey::random()? }; - Ok((key, WrapKeyReference::RawKey)) + Ok((key, StoreKeyReference::RawKey)) } - Self::Unprotected => Ok((WrapKey::empty(), WrapKeyReference::Unprotected)), + Self::Unprotected => Ok((StoreKey::empty(), StoreKeyReference::Unprotected)), } } } -impl Default for WrapKeyMethod { +impl Default for StoreKeyMethod { fn default() -> Self { Self::DeriveKey(KdfMethod::Argon2i(Default::default())) } } #[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub enum WrapKeyReference { +pub enum StoreKeyReference { // ManagedKey(String), DeriveKey(KdfMethod, String), RawKey, Unprotected, } -impl WrapKeyReference { +impl StoreKeyReference { pub fn parse_uri(uri: &str) -> Result { let mut prefix_and_detail = uri.splitn(2, ':'); let prefix = prefix_and_detail.next().unwrap_or_default(); @@ -185,20 +187,20 @@ impl WrapKeyReference { PREFIX_NONE => Ok(Self::Unprotected), _ => Err(err_msg!( Unsupported, - "Invalid wrap key method for reference" + "Invalid store key method for reference" )), } } - pub fn compare_method(&self, method: &WrapKeyMethod) -> bool { + pub fn compare_method(&self, method: &StoreKeyMethod) -> bool { match self { // Self::ManagedKey(_keyref) => matches!(method, WrapKeyMethod::CreateManagedKey(..)), Self::DeriveKey(kdf_method, _detail) => match method { - WrapKeyMethod::DeriveKey(m) if m == kdf_method => true, + StoreKeyMethod::DeriveKey(m) if m == kdf_method => true, _ => false, }, - Self::RawKey => *method == WrapKeyMethod::RawKey, - Self::Unprotected => *method == WrapKeyMethod::Unprotected, + Self::RawKey => *method == StoreKeyMethod::RawKey, + Self::Unprotected => *method == StoreKeyMethod::Unprotected, } } @@ -211,7 +213,7 @@ impl WrapKeyReference { } } - pub fn resolve(&self, pass_key: PassKey<'_>) -> Result { + pub fn resolve(&self, pass_key: PassKey<'_>) -> Result { match self { // Self::ManagedKey(_key_ref) => unimplemented!(), Self::DeriveKey(method, detail) => { @@ -223,12 +225,12 @@ impl WrapKeyReference { } Self::RawKey => { if !pass_key.is_empty() { - parse_raw_key(&*pass_key) + parse_raw_store_key(&*pass_key) } else { Err(err_msg!(Input, "Encoded raw key not provided")) } } - Self::Unprotected => Ok(WrapKey::empty()), + Self::Unprotected => Ok(StoreKey::empty()), } } } @@ -240,12 +242,12 @@ mod tests { #[test] fn protection_method_parse() { - let parse = WrapKeyMethod::parse_uri; - assert_eq!(parse("none"), Ok(WrapKeyMethod::Unprotected)); - assert_eq!(parse("raw"), Ok(WrapKeyMethod::RawKey)); + let parse = StoreKeyMethod::parse_uri; + assert_eq!(parse("none"), Ok(StoreKeyMethod::Unprotected)); + assert_eq!(parse("raw"), Ok(StoreKeyMethod::RawKey)); assert_eq!( parse("kdf:argon2i"), - Ok(WrapKeyMethod::DeriveKey(KdfMethod::Argon2i( + Ok(StoreKeyMethod::DeriveKey(KdfMethod::Argon2i( Default::default() ))) ); @@ -259,7 +261,7 @@ mod tests { fn derived_key_wrap() { let input = b"test data"; let pass = PassKey::from("pass"); - let (key, key_ref) = WrapKeyMethod::DeriveKey(KdfMethod::Argon2i(Default::default())) + let (key, key_ref) = StoreKeyMethod::DeriveKey(KdfMethod::Argon2i(Default::default())) .resolve(pass.as_ref()) .expect("Error deriving new key"); assert!(!key.is_empty()); @@ -280,9 +282,10 @@ mod tests { "c29c66fde50b30b8a077da1ea9bcf4dfeb5fabea12050973aed0e8251f20fad8205cfd2dec" )); let pass = PassKey::from("pass"); - let key_ref = - WrapKeyReference::parse_uri("kdf:argon2i:13:mod?salt=a553cfb9c558b5c11c78efcfa06f3e29") - .expect("Error parsing derived key ref"); + let key_ref = StoreKeyReference::parse_uri( + "kdf:argon2i:13:mod?salt=a553cfb9c558b5c11c78efcfa06f3e29", + ) + .expect("Error parsing derived key ref"); let key = key_ref.resolve(pass).expect("Error deriving existing key"); let unwrapped = key.unwrap_data(wrapped).expect("Error unwrapping data"); assert_eq!(unwrapped, &input[..]); @@ -293,9 +296,10 @@ mod tests { let wrapped = Vec::from(hex!( "c29c66fde50b30b8a077da1ea9bcf4dfeb5fabea12050973aed0e8251f20fad8205cfd2dec" )); - let key_ref = - WrapKeyReference::parse_uri("kdf:argon2i:13:mod?salt=a553cfb9c558b5c11c78efcfa06f3e29") - .expect("Error parsing derived key ref"); + let key_ref = StoreKeyReference::parse_uri( + "kdf:argon2i:13:mod?salt=a553cfb9c558b5c11c78efcfa06f3e29", + ) + .expect("Error parsing derived key ref"); let check_bad_pass = key_ref .resolve("not my pass".into()) .expect("Error deriving comparison key"); @@ -306,9 +310,9 @@ mod tests { #[test] fn raw_key_wrap() { let input = b"test data"; - let raw_key = generate_raw_wrap_key(None).unwrap(); + let raw_key = generate_raw_store_key(None).unwrap(); - let (key, key_ref) = WrapKeyMethod::RawKey + let (key, key_ref) = StoreKeyMethod::RawKey .resolve(raw_key.as_ref()) .expect("Error resolving raw key"); assert_eq!(key.is_empty(), false); @@ -319,7 +323,7 @@ mod tests { // round trip the key reference let key_uri = key_ref.into_uri(); - let key_ref = WrapKeyReference::parse_uri(&key_uri).expect("Error parsing raw key URI"); + let key_ref = StoreKeyReference::parse_uri(&key_uri).expect("Error parsing raw key URI"); let key = key_ref.resolve(raw_key).expect("Error resolving raw key"); let unwrapped = key.unwrap_data(wrapped).expect("Error unwrapping data"); @@ -335,7 +339,7 @@ mod tests { #[test] fn unprotected_wrap() { let input = b"test data"; - let (key, key_ref) = WrapKeyMethod::Unprotected + let (key, key_ref) = StoreKeyMethod::Unprotected .resolve(None.into()) .expect("Error resolving unprotected"); assert_eq!(key.is_empty(), true); @@ -347,7 +351,7 @@ mod tests { // round trip the key reference let key_uri = key_ref.into_uri(); let key_ref = - WrapKeyReference::parse_uri(&key_uri).expect("Error parsing unprotected key ref"); + StoreKeyReference::parse_uri(&key_uri).expect("Error parsing unprotected key ref"); let key = key_ref .resolve(None.into()) .expect("Error resolving unprotected key ref"); diff --git a/src/storage/types.rs b/src/storage/types.rs index 03acdba9..486b2698 100644 --- a/src/storage/types.rs +++ b/src/storage/types.rs @@ -14,7 +14,7 @@ use super::{ use crate::{ error::Error, future::BoxFuture, - protect::{PassKey, WrapKeyMethod}, + protect::{PassKey, StoreKeyMethod}, }; /// Represents a generic backend implementation @@ -48,7 +48,7 @@ pub trait Backend: Send + Sync { /// Replace the wrapping key of the store fn rekey_backend( &mut self, - method: WrapKeyMethod, + method: StoreKeyMethod, key: PassKey<'_>, ) -> BoxFuture<'_, Result<(), Error>>; @@ -64,7 +64,7 @@ pub trait ManageBackend<'a> { /// Open an existing store fn open_backend( self, - method: Option, + method: Option, pass_key: PassKey<'a>, profile: Option<&'a str>, ) -> BoxFuture<'a, Result>; @@ -72,7 +72,7 @@ pub trait ManageBackend<'a> { /// Provision a new store fn provision_backend( self, - method: WrapKeyMethod, + method: StoreKeyMethod, pass_key: PassKey<'a>, profile: Option<&'a str>, recreate: bool, @@ -164,7 +164,7 @@ impl Store { /// Replace the wrapping key on a store pub async fn rekey( &mut self, - method: WrapKeyMethod, + method: StoreKeyMethod, pass_key: PassKey<'_>, ) -> Result<(), Error> { Ok(self.0.rekey_backend(method, pass_key).await?) diff --git a/tests/backends.rs b/tests/backends.rs index fbb2f2e7..b601a8b8 100644 --- a/tests/backends.rs +++ b/tests/backends.rs @@ -182,7 +182,7 @@ macro_rules! backend_tests { #[cfg(feature = "sqlite")] mod sqlite { use aries_askar::backend::sqlite::{SqliteStore, SqliteStoreOptions}; - use aries_askar::{generate_raw_wrap_key, ManageBackend, Store, WrapKeyMethod}; + use aries_askar::{generate_raw_store_key, ManageBackend, Store, StoreKeyMethod}; use std::path::Path; #[test] @@ -195,7 +195,7 @@ mod sqlite { "Oops, should be a unique filename" ); - let key = generate_raw_wrap_key(None).expect("Error creating raw key"); + let key = generate_raw_store_key(None).expect("Error creating raw key"); block_on(async move { assert_eq!( SqliteStoreOptions::new(fname.as_str()) @@ -208,14 +208,14 @@ mod sqlite { let store = SqliteStoreOptions::new(fname.as_str()) .expect("Error initializing sqlite store options") - .provision_backend(WrapKeyMethod::RawKey, key.as_ref(), None, false) + .provision_backend(StoreKeyMethod::RawKey, key.as_ref(), None, false) .await .expect("Error provisioning sqlite store"); assert_eq!(Path::new(&fname).exists(), true); let store2 = SqliteStoreOptions::new(fname.as_str()) .expect("Error initializing sqlite store options") - .open_backend(Some(WrapKeyMethod::RawKey), key.as_ref(), None) + .open_backend(Some(StoreKeyMethod::RawKey), key.as_ref(), None) .await .expect("Error opening sqlite store"); store2.close().await.expect("Error closing sqlite store"); @@ -239,25 +239,25 @@ mod sqlite { fn rekey_db() { env_logger::builder().is_test(true).try_init().unwrap_or(()); let fname = format!("sqlite-test-{}.db", uuid::Uuid::new_v4().to_string()); - let key1 = generate_raw_wrap_key(None).expect("Error creating raw key"); - let key2 = generate_raw_wrap_key(None).expect("Error creating raw key"); + let key1 = generate_raw_store_key(None).expect("Error creating raw key"); + let key2 = generate_raw_store_key(None).expect("Error creating raw key"); assert_ne!(key1, key2); block_on(async move { let mut store = SqliteStoreOptions::new(fname.as_str()) .expect("Error initializing sqlite store options") - .provision_backend(WrapKeyMethod::RawKey, key1.as_ref(), None, false) + .provision_backend(StoreKeyMethod::RawKey, key1.as_ref(), None, false) .await .expect("Error provisioning sqlite store"); store - .rekey(WrapKeyMethod::RawKey, key2.as_ref()) + .rekey(StoreKeyMethod::RawKey, key2.as_ref()) .await .expect("Error rekeying database"); SqliteStoreOptions::new(fname.as_str()) .expect("Error initializing sqlite store options") - .open_backend(Some(WrapKeyMethod::RawKey), key2.as_ref(), None) + .open_backend(Some(StoreKeyMethod::RawKey), key2.as_ref(), None) .await .expect("Error opening rekeyed store") .close() @@ -276,9 +276,9 @@ mod sqlite { async fn init_db() -> Store { env_logger::builder().is_test(true).try_init().unwrap_or(()); - let key = generate_raw_wrap_key(None).expect("Error creating raw key"); + let key = generate_raw_store_key(None).expect("Error creating raw key"); SqliteStoreOptions::in_memory() - .provision(WrapKeyMethod::RawKey, key, None, false) + .provision(StoreKeyMethod::RawKey, key, None, false) .await .expect("Error provisioning sqlite store") } @@ -287,12 +287,12 @@ mod sqlite { #[test] fn provision_from_str() { - let key = generate_raw_wrap_key(None).expect("Error creating raw key"); + let key = generate_raw_store_key(None).expect("Error creating raw key"); block_on(async { let db_url = "sqlite://:memory:"; let _db = db_url - .provision_backend(WrapKeyMethod::RawKey, key.as_ref(), None, false) + .provision_backend(StoreKeyMethod::RawKey, key.as_ref(), None, false) .await .expect("Error provisioning store"); }); @@ -300,7 +300,7 @@ mod sqlite { block_on(async { let db_url = "not-sqlite://test-db"; let _db = db_url - .provision_backend(WrapKeyMethod::RawKey, key.as_ref(), None, false) + .provision_backend(StoreKeyMethod::RawKey, key.as_ref(), None, false) .await .expect_err("Expected provision failure"); }); diff --git a/wrappers/python/aries_askar/bindings.py b/wrappers/python/aries_askar/bindings.py index 366005a2..ec598c7b 100644 --- a/wrappers/python/aries_askar/bindings.py +++ b/wrappers/python/aries_askar/bindings.py @@ -447,13 +447,13 @@ def version() -> str: async def store_open( - uri: str, wrap_method: str = None, pass_key: str = None, profile: str = None + uri: str, key_method: str = None, pass_key: str = None, profile: str = None ) -> StoreHandle: """Open an existing Store and return the open handle.""" return await do_call_async( "askar_store_open", encode_str(uri), - encode_str(wrap_method and wrap_method.lower()), + encode_str(key_method and key_method.lower()), encode_str(pass_key), encode_str(profile), return_type=StoreHandle, @@ -462,7 +462,7 @@ async def store_open( async def store_provision( uri: str, - wrap_method: str = None, + key_method: str = None, pass_key: str = None, profile: str = None, recreate: bool = False, @@ -471,7 +471,7 @@ async def store_provision( return await do_call_async( "askar_store_provision", encode_str(uri), - encode_str(wrap_method and wrap_method.lower()), + encode_str(key_method and key_method.lower()), encode_str(pass_key), encode_str(profile), c_int8(recreate), @@ -517,14 +517,14 @@ async def store_remove_profile(handle: StoreHandle, name: str) -> bool: async def store_rekey( handle: StoreHandle, - wrap_method: str = None, + key_method: str = None, pass_key: str = None, ) -> StoreHandle: - """Replace the wrap key on a Store.""" + """Replace the store key on a Store.""" return await do_call_async( "askar_store_rekey", handle, - encode_str(wrap_method and wrap_method.lower()), + encode_str(key_method and key_method.lower()), encode_str(pass_key), ) diff --git a/wrappers/python/aries_askar/store.py b/wrappers/python/aries_askar/store.py index acb0e90c..61d72c01 100644 --- a/wrappers/python/aries_askar/store.py +++ b/wrappers/python/aries_askar/store.py @@ -108,7 +108,7 @@ def uri(self) -> str: async def provision( cls, uri: str, - wrap_method: str = None, + key_method: str = None, pass_key: str = None, *, profile: str = None, @@ -116,7 +116,7 @@ async def provision( ) -> "Store": return Store( await bindings.store_provision( - uri, wrap_method, pass_key, profile, recreate + uri, key_method, pass_key, profile, recreate ), uri, ) @@ -125,14 +125,12 @@ async def provision( async def open( cls, uri: str, - wrap_method: str = None, + key_method: str = None, pass_key: str = None, *, profile: str = None, ) -> "Store": - return Store( - await bindings.store_open(uri, wrap_method, pass_key, profile), uri - ) + return Store(await bindings.store_open(uri, key_method, pass_key, profile), uri) @classmethod async def remove(cls, uri: str) -> bool: @@ -157,10 +155,10 @@ async def remove_profile(self, name: str) -> bool: async def rekey( self, - wrap_method: str = None, + key_method: str = None, pass_key: str = None, ): - await bindings.store_rekey(self._handle, wrap_method, pass_key) + await bindings.store_rekey(self._handle, key_method, pass_key) def scan( self, diff --git a/wrappers/python/demo/test.py b/wrappers/python/demo/test.py index c3312242..060e6f58 100644 --- a/wrappers/python/demo/test.py +++ b/wrappers/python/demo/test.py @@ -29,7 +29,7 @@ async def basic_test(): if ENCRYPT: key = await generate_raw_key(b"00000000000000000000000000000My1") key_method = "raw" - log("Generated raw wallet key:", key) + log("Generated raw store key:", key) else: key = None key_method = "none" From 9f6fa4cb6e216a74df30b334dcd4d64e710abbd8 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Fri, 16 Apr 2021 10:52:15 -0700 Subject: [PATCH 043/116] fix sqlx version at 0.5.1 for now Signed-off-by: Andrew Whitehead --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 19c79228..2a1cf736 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,7 +74,7 @@ default-features = false features = ["serde_support", "wql"] [dependencies.sqlx] -version = "0.5.2" +version = "=0.5.1" default-features = false features = ["chrono", "runtime-async-std-rustls"] optional = true From eb1c5fe97e7aaf07fda85c01299799df373fef68 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Fri, 16 Apr 2021 22:36:23 -0700 Subject: [PATCH 044/116] JWK thumbprint generation Signed-off-by: Andrew Whitehead --- askar-crypto/src/alg/aesgcm.rs | 13 +++++----- askar-crypto/src/alg/chacha20.rs | 13 +++++----- askar-crypto/src/alg/ed25519.rs | 15 +++++------ askar-crypto/src/alg/k256.rs | 19 +++++++------- askar-crypto/src/alg/p256.rs | 36 ++++++++++++++++++-------- askar-crypto/src/alg/x25519.rs | 15 +++++------ askar-crypto/src/buffer/hash.rs | 44 ++++++++++++++++++++++++++++++++ askar-crypto/src/buffer/mod.rs | 3 +++ askar-crypto/src/jwk/mod.rs | 42 +++++++++++++++++++++++++----- askar-crypto/src/kdf/ecdh_1pu.rs | 16 ++++++------ askar-crypto/src/kdf/ecdh_es.rs | 12 ++++----- 11 files changed, 159 insertions(+), 69 deletions(-) create mode 100644 askar-crypto/src/buffer/hash.rs diff --git a/askar-crypto/src/alg/aesgcm.rs b/askar-crypto/src/alg/aesgcm.rs index 3f2ab63a..f37c0f32 100644 --- a/askar-crypto/src/alg/aesgcm.rs +++ b/askar-crypto/src/alg/aesgcm.rs @@ -178,14 +178,15 @@ impl KeyAeadInPlace for AesGcmKey { } impl ToJwk for AesGcmKey { - fn to_jwk_buffer(&self, buffer: &mut JwkEncoder) -> Result<(), Error> { - buffer.add_str("kty", JWK_KEY_TYPE)?; - if !buffer.is_secret() { + fn to_jwk_encoder(&self, enc: &mut JwkEncoder) -> Result<(), Error> { + if enc.is_public() { return Err(err_msg!(Unsupported, "Cannot export as a public key")); } - buffer.add_str("alg", T::JWK_ALG)?; - buffer.add_as_base64("k", self.0.as_ref())?; - buffer.add_str("use", "enc")?; + if !enc.is_thumbprint() { + enc.add_str("alg", T::JWK_ALG)?; + } + enc.add_as_base64("k", self.0.as_ref())?; + enc.add_str("kty", JWK_KEY_TYPE)?; Ok(()) } } diff --git a/askar-crypto/src/alg/chacha20.rs b/askar-crypto/src/alg/chacha20.rs index 30f500a1..1f2a8e7b 100644 --- a/askar-crypto/src/alg/chacha20.rs +++ b/askar-crypto/src/alg/chacha20.rs @@ -197,14 +197,15 @@ impl KeyAeadInPlace for Chacha20Key { } impl ToJwk for Chacha20Key { - fn to_jwk_buffer(&self, buffer: &mut JwkEncoder) -> Result<(), Error> { - buffer.add_str("kty", JWK_KEY_TYPE)?; - if !buffer.is_secret() { + fn to_jwk_encoder(&self, enc: &mut JwkEncoder) -> Result<(), Error> { + if enc.is_public() { return Err(err_msg!(Unsupported, "Cannot export as a public key")); } - buffer.add_str("alg", T::JWK_ALG)?; - buffer.add_as_base64("k", self.0.as_ref())?; - buffer.add_str("use", "enc")?; + if !enc.is_thumbprint() { + enc.add_str("alg", T::JWK_ALG)?; + } + enc.add_as_base64("k", self.0.as_ref())?; + enc.add_str("kty", JWK_KEY_TYPE)?; Ok(()) } } diff --git a/askar-crypto/src/alg/ed25519.rs b/askar-crypto/src/alg/ed25519.rs index 8865a521..77ee9251 100644 --- a/askar-crypto/src/alg/ed25519.rs +++ b/askar-crypto/src/alg/ed25519.rs @@ -231,20 +231,19 @@ impl KeySigVerify for Ed25519KeyPair { } impl ToJwk for Ed25519KeyPair { - fn to_jwk_buffer(&self, buffer: &mut JwkEncoder) -> Result<(), Error> { - buffer.add_str("kty", JWK_KEY_TYPE)?; - buffer.add_str("crv", JWK_CURVE)?; - self.with_public_bytes(|buf| buffer.add_as_base64("x", buf))?; - if buffer.is_secret() { + fn to_jwk_encoder(&self, enc: &mut JwkEncoder) -> Result<(), Error> { + enc.add_str("crv", JWK_CURVE)?; + enc.add_str("kty", JWK_KEY_TYPE)?; + self.with_public_bytes(|buf| enc.add_as_base64("x", buf))?; + if enc.is_secret() { self.with_secret_bytes(|buf| { if let Some(sk) = buf { - buffer.add_as_base64("d", sk) + enc.add_as_base64("d", sk) } else { Ok(()) } })?; } - buffer.add_str("use", "sig")?; Ok(()) } } @@ -334,7 +333,7 @@ mod tests { .to_jwk_public() .expect("Error converting public key to JWK"); let jwk = jwk.to_parts().expect("Error parsing JWK output"); - assert_eq!(jwk.kty, "OKP"); + assert_eq!(jwk.kty, JWK_KEY_TYPE); assert_eq!(jwk.crv, JWK_CURVE); assert_eq!(jwk.x, "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"); let pk_load = Ed25519KeyPair::from_jwk_parts(jwk).unwrap(); diff --git a/askar-crypto/src/alg/k256.rs b/askar-crypto/src/alg/k256.rs index b8138c06..c0ad2d1d 100644 --- a/askar-crypto/src/alg/k256.rs +++ b/askar-crypto/src/alg/k256.rs @@ -191,9 +191,9 @@ impl KeySigVerify for K256KeyPair { } impl ToJwk for K256KeyPair { - fn to_jwk_buffer(&self, buffer: &mut JwkEncoder) -> Result<(), Error> { - let encp = EncodedPoint::encode(self.public, false); - let (x, y) = match encp.coordinates() { + fn to_jwk_encoder(&self, enc: &mut JwkEncoder) -> Result<(), Error> { + let pk_enc = EncodedPoint::encode(self.public, false); + let (x, y) = match pk_enc.coordinates() { Coordinates::Identity => { return Err(err_msg!( Unsupported, @@ -204,20 +204,19 @@ impl ToJwk for K256KeyPair { Coordinates::Compressed { .. } => unreachable!(), }; - buffer.add_str("kty", JWK_KEY_TYPE)?; - buffer.add_str("crv", JWK_CURVE)?; - buffer.add_as_base64("x", &x[..])?; - buffer.add_as_base64("y", &y[..])?; - if buffer.is_secret() { + enc.add_str("crv", JWK_CURVE)?; + enc.add_str("kty", JWK_KEY_TYPE)?; + enc.add_as_base64("x", &x[..])?; + enc.add_as_base64("y", &y[..])?; + if enc.is_secret() { self.with_secret_bytes(|buf| { if let Some(sk) = buf { - buffer.add_as_base64("d", sk) + enc.add_as_base64("d", sk) } else { Ok(()) } })?; } - // buffer.add_str("use", "enc")?; Ok(()) } } diff --git a/askar-crypto/src/alg/p256.rs b/askar-crypto/src/alg/p256.rs index c60acbd6..059dfec9 100644 --- a/askar-crypto/src/alg/p256.rs +++ b/askar-crypto/src/alg/p256.rs @@ -190,9 +190,9 @@ impl KeySigVerify for P256KeyPair { } impl ToJwk for P256KeyPair { - fn to_jwk_buffer(&self, buffer: &mut JwkEncoder) -> Result<(), Error> { - let encp = EncodedPoint::encode(self.public, false); - let (x, y) = match encp.coordinates() { + fn to_jwk_encoder(&self, enc: &mut JwkEncoder) -> Result<(), Error> { + let pk_enc = EncodedPoint::encode(self.public, false); + let (x, y) = match pk_enc.coordinates() { Coordinates::Identity => { return Err(err_msg!( Unsupported, @@ -203,20 +203,19 @@ impl ToJwk for P256KeyPair { Coordinates::Compressed { .. } => unreachable!(), }; - buffer.add_str("kty", JWK_KEY_TYPE)?; - buffer.add_str("crv", JWK_CURVE)?; - buffer.add_as_base64("x", &x[..])?; - buffer.add_as_base64("y", &y[..])?; - if buffer.is_secret() { + enc.add_str("crv", JWK_CURVE)?; + enc.add_str("kty", JWK_KEY_TYPE)?; + enc.add_as_base64("x", &x[..])?; + enc.add_as_base64("y", &y[..])?; + if enc.is_secret() { self.with_secret_bytes(|buf| { if let Some(sk) = buf { - buffer.add_as_base64("d", sk) + enc.add_as_base64("d", sk) } else { Ok(()) } })?; } - // buffer.add_str("use", "enc")?; Ok(()) } } @@ -309,6 +308,23 @@ mod tests { ); } + #[test] + fn jwk_thumbprint() { + let pk = P256KeyPair::from_jwk( + r#"{ + "kty": "EC", + "crv": "P-256", + "x": "tDeeYABgKEAbWicYPCEEI8sP4SRIhHKcHDW7VqrB4LA", + "y": "J08HOoIZ0rX2Me3bNFZUltfxIk1Hrc8FsLu8VaSxsMI" + }"#, + ) + .unwrap(); + assert_eq!( + pk.to_jwk_thumbprint().unwrap(), + "8fm8079s3nu4FLV_7dVJoJ69A8XCXn7Za2mtaWCnxR4" + ); + } + #[test] fn sign_verify_expected() { let test_msg = b"This is a dummy message for use with tests"; diff --git a/askar-crypto/src/alg/x25519.rs b/askar-crypto/src/alg/x25519.rs index 2b8e8f5c..62af47b5 100644 --- a/askar-crypto/src/alg/x25519.rs +++ b/askar-crypto/src/alg/x25519.rs @@ -168,20 +168,19 @@ impl KeyPublicBytes for X25519KeyPair { } impl ToJwk for X25519KeyPair { - fn to_jwk_buffer(&self, buffer: &mut JwkEncoder) -> Result<(), Error> { - buffer.add_str("kty", JWK_KEY_TYPE)?; - buffer.add_str("crv", JWK_CURVE)?; - self.with_public_bytes(|buf| buffer.add_as_base64("x", buf))?; - if buffer.is_secret() { + fn to_jwk_encoder(&self, enc: &mut JwkEncoder) -> Result<(), Error> { + enc.add_str("crv", JWK_CURVE)?; + enc.add_str("kty", JWK_KEY_TYPE)?; + self.with_public_bytes(|buf| enc.add_as_base64("x", buf))?; + if enc.is_secret() { self.with_secret_bytes(|buf| { if let Some(sk) = buf { - buffer.add_as_base64("d", sk) + enc.add_as_base64("d", sk) } else { Ok(()) } })?; } - buffer.add_str("use", "enc")?; Ok(()) } } @@ -259,7 +258,7 @@ mod tests { .to_jwk_secret() .expect("Error converting private key to JWK"); let jwk = jwk.to_parts().expect("Error parsing JWK output"); - assert_eq!(jwk.kty, "OKP"); + assert_eq!(jwk.kty, JWK_KEY_TYPE); assert_eq!(jwk.crv, JWK_CURVE); assert_eq!(jwk.x, "tGskN_ae61DP4DLY31_fjkbvnKqf-ze7kA6Cj2vyQxU"); assert_eq!(jwk.d, test_pvt_b64); diff --git a/askar-crypto/src/buffer/hash.rs b/askar-crypto/src/buffer/hash.rs new file mode 100644 index 00000000..a3c8bbe2 --- /dev/null +++ b/askar-crypto/src/buffer/hash.rs @@ -0,0 +1,44 @@ +use digest::Digest; + +use crate::generic_array::GenericArray; + +use crate::{buffer::WriteBuffer, error::Error}; + +const BUFFER_SIZE: usize = 256; + +pub struct HashBuffer(D); + +impl HashBuffer { + pub fn new() -> Self { + Self(D::new()) + } + + pub fn finalize(self) -> GenericArray { + self.0.finalize() + } +} + +impl WriteBuffer for HashBuffer { + fn write_slice(&mut self, data: &[u8]) -> Result<(), Error> { + self.0.update(data); + Ok(()) + } + + fn write_with( + &mut self, + max_len: usize, + f: impl FnOnce(&mut [u8]) -> Result, + ) -> Result { + // This could use a Vec to support larger inputs + // but for current purposes a small fixed buffer is fine. + // Could also accept the buffer (ResizeBuffer) as an argument + // when creating the hasher. + if max_len > BUFFER_SIZE { + return Err(err_msg!(Usage, "Exceeded hash buffer size")); + } + let mut buf = [0u8; BUFFER_SIZE]; + let written = f(&mut buf[..max_len])?; + self.write_slice(&buf[..written])?; + Ok(written) + } +} diff --git a/askar-crypto/src/buffer/mod.rs b/askar-crypto/src/buffer/mod.rs index aece1dd1..22ee24b3 100644 --- a/askar-crypto/src/buffer/mod.rs +++ b/askar-crypto/src/buffer/mod.rs @@ -9,6 +9,9 @@ use crate::error::Error; mod array; pub use self::array::ArrayKey; +mod hash; +pub use self::hash::HashBuffer; + #[cfg(feature = "alloc")] mod secret; #[cfg(feature = "alloc")] diff --git a/askar-crypto/src/jwk/mod.rs b/askar-crypto/src/jwk/mod.rs index 7267804f..e8a7dffc 100644 --- a/askar-crypto/src/jwk/mod.rs +++ b/askar-crypto/src/jwk/mod.rs @@ -1,7 +1,12 @@ #[cfg(feature = "alloc")] use alloc::{borrow::Cow, string::String, vec::Vec}; -use crate::{buffer::WriteBuffer, error::Error}; +use sha2::Sha256; + +use crate::{ + buffer::{HashBuffer, WriteBuffer}, + error::Error, +}; #[cfg(feature = "alloc")] mod borrow; @@ -18,13 +23,36 @@ mod parts; pub use self::parts::JwkParts; pub trait ToJwk { - fn to_jwk_buffer(&self, buffer: &mut JwkEncoder) -> Result<(), Error>; + fn to_jwk_encoder(&self, enc: &mut JwkEncoder) -> Result<(), Error>; + + fn to_jwk_thumbprint_buffer(&self, output: &mut B) -> Result<(), Error> { + let mut hasher = HashBuffer::::new(); + let mut buf = JwkEncoder::new(&mut hasher, JwkEncoderMode::Thumbprint)?; + self.to_jwk_encoder(&mut buf)?; + buf.finalize()?; + let hash = hasher.finalize(); + output.write_with(43, |buf| { + Ok(base64::encode_config_slice( + &hash, + base64::URL_SAFE_NO_PAD, + buf, + )) + })?; + Ok(()) + } + + #[cfg(feature = "alloc")] + fn to_jwk_thumbprint(&self) -> Result { + let mut v = Vec::with_capacity(43); + self.to_jwk_thumbprint_buffer(&mut v)?; + Ok(String::from_utf8(v).unwrap()) + } #[cfg(feature = "alloc")] fn to_jwk_public(&self) -> Result, Error> { let mut v = Vec::with_capacity(128); let mut buf = JwkEncoder::new(&mut v, JwkEncoderMode::PublicKey)?; - self.to_jwk_buffer(&mut buf)?; + self.to_jwk_encoder(&mut buf)?; buf.finalize()?; Ok(Jwk::Encoded(Cow::Owned(String::from_utf8(v).unwrap()))) } @@ -33,16 +61,16 @@ pub trait ToJwk { fn to_jwk_secret(&self) -> Result, Error> { let mut v = Vec::with_capacity(128); let mut buf = JwkEncoder::new(&mut v, JwkEncoderMode::SecretKey)?; - self.to_jwk_buffer(&mut buf)?; + self.to_jwk_encoder(&mut buf)?; buf.finalize()?; Ok(Jwk::Encoded(Cow::Owned(String::from_utf8(v).unwrap()))) } } pub trait FromJwk: Sized { - #[cfg(feature = "alloc")] - fn from_jwk(jwk: Jwk<'_>) -> Result { - let parts = jwk.to_parts()?; + fn from_jwk(jwk: &str) -> Result { + let parts = + serde_json::from_str(jwk).map_err(err_map!(InvalidData, "Error parsing JWK"))?; Self::from_jwk_parts(parts) } diff --git a/askar-crypto/src/kdf/ecdh_1pu.rs b/askar-crypto/src/kdf/ecdh_1pu.rs index ed52405b..1190f394 100644 --- a/askar-crypto/src/kdf/ecdh_1pu.rs +++ b/askar-crypto/src/kdf/ecdh_1pu.rs @@ -65,7 +65,7 @@ impl Ecdh1PU { { let ephem_key = Key::generate()?; let mut encoder = JwkEncoder::new(jwk_output, JwkEncoderMode::PublicKey)?; - ephem_key.to_jwk_buffer(&mut encoder)?; + ephem_key.to_jwk_encoder(&mut encoder)?; Self::derive_key_config(&ephem_key, send_key, recip_key, alg, apu, apv, key_output)?; @@ -84,31 +84,31 @@ mod tests { // from RFC: https://tools.ietf.org/html/draft-madden-jose-ecdh-1pu-03#appendix-A fn expected_1pu_direct_output() { use crate::alg::p256::P256KeyPair; - use crate::jwk::{FromJwk, Jwk}; + use crate::jwk::FromJwk; - let alice_sk = P256KeyPair::from_jwk(Jwk::from( + let alice_sk = P256KeyPair::from_jwk( r#"{"kty":"EC", "crv":"P-256", "x":"WKn-ZIGevcwGIyyrzFoZNBdaq9_TsqzGl96oc0CWuis", "y":"y77t-RvAHRKTsSGdIYUfweuOvwrvDD-Q3Hv5J0fSKbE", "d":"Hndv7ZZjs_ke8o9zXYo3iq-Yr8SewI5vrqd0pAvEPqg"}"#, - )) + ) .unwrap(); - let bob_sk = P256KeyPair::from_jwk(Jwk::from( + let bob_sk = P256KeyPair::from_jwk( r#"{"kty":"EC", "crv":"P-256", "x":"weNJy2HscCSM6AEDTDg04biOvhFhyyWvOHQfeF_PxMQ", "y":"e8lnCO-AlStT-NJVX-crhB7QRYhiix03illJOVAOyck", "d":"VEmDZpDXXK8p8N0Cndsxs924q6nS1RXFASRl6BfUqdw"}"#, - )) + ) .unwrap(); - let ephem_sk = P256KeyPair::from_jwk(Jwk::from( + let ephem_sk = P256KeyPair::from_jwk( r#"{"kty":"EC", "crv":"P-256", "x":"gI0GAILBdu7T53akrFmMyGcsF3n5dO7MmwNBHKW5SV0", "y":"SLW_xSffzlPWrHEVI30DHM_4egVwt3NQqeUD7nMFpps", "d":"0_NxaRPUMQoAJt50Gz8YiTr8gRTwyEaCumd-MToTmIo"}"#, - )) + ) .unwrap(); let mut key_output = [0u8; 32]; diff --git a/askar-crypto/src/kdf/ecdh_es.rs b/askar-crypto/src/kdf/ecdh_es.rs index 019efce2..1e77fab7 100644 --- a/askar-crypto/src/kdf/ecdh_es.rs +++ b/askar-crypto/src/kdf/ecdh_es.rs @@ -62,7 +62,7 @@ impl EcdhEs { { let ephem_key = Key::generate()?; let mut encoder = JwkEncoder::new(jwk_output, JwkEncoderMode::PublicKey)?; - ephem_key.to_jwk_buffer(&mut encoder)?; + ephem_key.to_jwk_encoder(&mut encoder)?; Self::derive_key_config(&ephem_key, recip_key, alg, apu, apv, key_output)?; @@ -82,19 +82,19 @@ mod tests { // https://tools.ietf.org/html/rfc8037#appendix-A.6 fn expected_es_direct_output() { use crate::alg::x25519::X25519KeyPair; - use crate::jwk::{FromJwk, Jwk}; + use crate::jwk::FromJwk; - let bob_pk = X25519KeyPair::from_jwk(Jwk::from( + let bob_pk = X25519KeyPair::from_jwk( r#"{"kty":"OKP","crv":"X25519","kid":"Bob", "x":"3p7bfXt9wbTTW2HC7OQ1Nz-DQ8hbeGdNrfx-FG-IK08"}"#, - )) + ) .unwrap(); - let ephem_sk = X25519KeyPair::from_jwk(Jwk::from( + let ephem_sk = X25519KeyPair::from_jwk( r#"{"kty":"OKP","crv":"X25519", "d":"dwdtCnMYpX08FsFyUbJmRd9ML4frwJkqsXf7pR25LCo", "x":"hSDwCYkwp1R0i33ctD73Wg2_Og0mOBr066SpjqqbTmo"} "#, - )) + ) .unwrap(); let xk = ephem_sk.key_exchange_bytes(&bob_pk).unwrap(); From f5426c08ab160df514942f8412776a2074c2e8bf Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Sat, 17 Apr 2021 12:44:12 -0700 Subject: [PATCH 045/116] start adding AnyKey support; make more traits object-safe Signed-off-by: Andrew Whitehead --- askar-crypto/Cargo.toml | 3 +- askar-crypto/src/alg/aesgcm.rs | 18 +-- askar-crypto/src/alg/any.rs | 170 +++++++++++++++++++++++++++ askar-crypto/src/alg/chacha20.rs | 15 ++- askar-crypto/src/alg/ed25519.rs | 17 +-- askar-crypto/src/alg/k256.rs | 14 ++- askar-crypto/src/alg/mod.rs | 37 +++--- askar-crypto/src/alg/p256.rs | 14 ++- askar-crypto/src/alg/x25519.rs | 6 +- askar-crypto/src/any.rs | 106 ----------------- askar-crypto/src/buffer/hash.rs | 22 +--- askar-crypto/src/buffer/mod.rs | 109 +++++------------ askar-crypto/src/buffer/secret.rs | 100 +++++----------- askar-crypto/src/buffer/writer.rs | 142 ++++++++++++---------- askar-crypto/src/encrypt/nacl_box.rs | 6 +- askar-crypto/src/jwk/encode.rs | 75 ++++++------ askar-crypto/src/jwk/mod.rs | 35 +++--- askar-crypto/src/jwk/parts.rs | 6 - askar-crypto/src/kdf/concat.rs | 20 +--- askar-crypto/src/lib.rs | 10 +- askar-crypto/src/repr.rs | 10 +- askar-crypto/src/sign.rs | 18 +-- 22 files changed, 455 insertions(+), 498 deletions(-) create mode 100644 askar-crypto/src/alg/any.rs delete mode 100644 askar-crypto/src/any.rs diff --git a/askar-crypto/Cargo.toml b/askar-crypto/Cargo.toml index 7969d710..6dd2fc2d 100644 --- a/askar-crypto/Cargo.toml +++ b/askar-crypto/Cargo.toml @@ -14,8 +14,9 @@ keywords = ["hyperledger", "aries", "ssi", "verifiable", "credentials"] rustdoc-args = ["--cfg", "docsrs"] [features] -default = ["alloc"] +default = ["alloc", "any_key"] alloc = [] +any_key = ["alloc"] std = ["alloc"] [dev-dependencies] diff --git a/askar-crypto/src/alg/aesgcm.rs b/askar-crypto/src/alg/aesgcm.rs index f37c0f32..9697aef5 100644 --- a/askar-crypto/src/alg/aesgcm.rs +++ b/askar-crypto/src/alg/aesgcm.rs @@ -5,15 +5,12 @@ use aes_gcm::{Aes128Gcm, Aes256Gcm}; use serde::{Deserialize, Serialize}; use zeroize::Zeroize; +use super::{AesTypes, KeyAlg}; use crate::{ - buffer::Writer, - generic_array::{typenum::Unsigned, GenericArray}, -}; - -use crate::{ - buffer::{ArrayKey, ResizeBuffer, WriteBuffer}, + buffer::{ArrayKey, ResizeBuffer, Writer}, encrypt::{KeyAeadInPlace, KeyAeadMeta}, error::Error, + generic_array::{typenum::Unsigned, GenericArray}, jwk::{JwkEncoder, ToJwk}, kdf::{FromKeyExchange, KeyExchange}, repr::{KeyGen, KeyMeta, KeySecretBytes}, @@ -24,6 +21,7 @@ pub static JWK_KEY_TYPE: &'static str = "oct"; pub trait AesGcmType { type Aead: NewAead + Aead + AeadInPlace; + const ALG_TYPE: AesTypes; const JWK_ALG: &'static str; } @@ -32,6 +30,7 @@ pub struct A128; impl AesGcmType for A128 { type Aead = Aes128Gcm; + const ALG_TYPE: AesTypes = AesTypes::A128GCM; const JWK_ALG: &'static str = "A128GCM"; } @@ -40,6 +39,7 @@ pub struct A256; impl AesGcmType for A256 { type Aead = Aes256Gcm; + const ALG_TYPE: AesTypes = AesTypes::A256GCM; const JWK_ALG: &'static str = "A256GCM"; } @@ -95,6 +95,8 @@ impl PartialEq for AesGcmKey { impl Eq for AesGcmKey {} impl KeyMeta for AesGcmKey { + const ALG: KeyAlg = KeyAlg::Aes(T::ALG_TYPE); + type KeySize = ::KeySize; } @@ -138,7 +140,7 @@ impl KeyAeadInPlace for AesGcmKey { let tag = chacha .encrypt_in_place_detached(nonce, aad, buffer.as_mut()) .map_err(|_| err_msg!(Encryption, "AEAD encryption error"))?; - buffer.write_slice(&tag[..])?; + buffer.buffer_write(&tag[..])?; Ok(()) } @@ -178,7 +180,7 @@ impl KeyAeadInPlace for AesGcmKey { } impl ToJwk for AesGcmKey { - fn to_jwk_encoder(&self, enc: &mut JwkEncoder) -> Result<(), Error> { + fn to_jwk_encoder(&self, enc: &mut JwkEncoder<'_>) -> Result<(), Error> { if enc.is_public() { return Err(err_msg!(Unsupported, "Cannot export as a public key")); } diff --git a/askar-crypto/src/alg/any.rs b/askar-crypto/src/alg/any.rs new file mode 100644 index 00000000..96f43917 --- /dev/null +++ b/askar-crypto/src/alg/any.rs @@ -0,0 +1,170 @@ +#[cfg(not(any(test, feature = "std")))] +use alloc::boxed::Box; +#[cfg(any(test, feature = "std"))] +use std::boxed::Box; + +use core::any::Any; + +use super::aesgcm::{AesGcmKey, A128, A256}; +use super::chacha20::{Chacha20Key, C20P, XC20P}; +use super::ed25519::{self, Ed25519KeyPair}; +use super::k256::{self, K256KeyPair}; +use super::p256::{self, P256KeyPair}; +use super::x25519::{self, X25519KeyPair}; +use super::{AesTypes, Chacha20Types, EcCurves, KeyAlg}; +use crate::{ + buffer::WriteBuffer, + error::Error, + jwk::{FromJwk, JwkEncoder, JwkParts, ToJwk}, + repr::KeyMeta, + sign::{KeySigVerify, KeySign, SignatureType}, +}; + +type BoxKey = Box; + +#[derive(Debug)] +pub struct AnyKey { + pub(crate) alg: KeyAlg, + pub(crate) inst: BoxKey, +} + +impl AnyKey { + pub fn key_alg(&self) -> KeyAlg { + self.alg + } + + pub fn downcast(self) -> Result, Self> { + match BoxKey::downcast(self.inst) { + Ok(key) => Ok(key), + Err(inst) => Err(Self { + alg: self.alg, + inst, + }), + } + } + + pub fn downcast_ref(&self) -> Option<&T> { + self.inst.downcast_ref() + } + + pub fn from_key(key: K) -> Self + where + K: KeyMeta + Send + 'static, + { + Self { + alg: K::ALG, + inst: Box::new(key) as BoxKey, + } + } + + pub fn from_boxed_key(key: Box) -> Self + where + K: KeyMeta + Send + 'static, + { + Self { + alg: K::ALG, + inst: Box::new(key) as BoxKey, + } + } +} + +fn assume<'r, T: 'static>(inst: &'r (dyn Any + Send + 'static)) -> &'r T { + if let Some(t) = inst.downcast_ref::() { + t + } else { + panic!("Invalid any key state"); + } +} + +impl FromJwk for AnyKey { + fn from_jwk_parts(jwk: JwkParts<'_>) -> Result { + let (alg, inst) = match (jwk.kty, jwk.crv.as_ref()) { + ("EC", c) if c == k256::JWK_CURVE => ( + KeyAlg::EcCurve(EcCurves::Secp256k1), + Box::new(K256KeyPair::from_jwk_parts(jwk)?) as BoxKey, + ), + ("EC", c) if c == p256::JWK_CURVE => ( + KeyAlg::EcCurve(EcCurves::Secp256r1), + Box::new(P256KeyPair::from_jwk_parts(jwk)?) as BoxKey, + ), + ("OKP", c) if c == ed25519::JWK_CURVE => ( + KeyAlg::Ed25519, + Box::new(Ed25519KeyPair::from_jwk_parts(jwk)?) as BoxKey, + ), + ("OKP", c) if c == x25519::JWK_CURVE => ( + KeyAlg::X25519, + Box::new(X25519KeyPair::from_jwk_parts(jwk)?) as BoxKey, + ), + // "oct" + _ => return Err(err_msg!(Unsupported, "Unsupported JWK for key import")), + }; + Ok(Self { alg, inst }) + } +} + +impl ToJwk for AnyKey { + fn to_jwk_encoder(&self, enc: &mut JwkEncoder<'_>) -> Result<(), Error> { + let key: &dyn ToJwk = match self.alg { + KeyAlg::Aes(AesTypes::A128GCM) => assume::>(&self.inst), + KeyAlg::Aes(AesTypes::A256GCM) => assume::>(&self.inst), + KeyAlg::Chacha20(Chacha20Types::C20P) => assume::>(&self.inst), + KeyAlg::Chacha20(Chacha20Types::XC20P) => assume::>(&self.inst), + KeyAlg::Ed25519 => assume::(&self.inst), + KeyAlg::X25519 => assume::(&self.inst), + KeyAlg::EcCurve(EcCurves::Secp256k1) => assume::(&self.inst), + KeyAlg::EcCurve(EcCurves::Secp256r1) => assume::(&self.inst), + #[allow(unreachable_patterns)] + _ => { + return Err(err_msg!( + Unsupported, + "JWK export is not supported for this key type" + )) + } + }; + key.to_jwk_encoder(enc) + } +} + +impl KeySign for AnyKey { + fn write_signature( + &self, + message: &[u8], + sig_type: Option, + out: &mut dyn WriteBuffer, + ) -> Result<(), Error> { + let key: &dyn KeySign = match self.alg { + KeyAlg::Ed25519 => assume::(&self.inst), + KeyAlg::EcCurve(EcCurves::Secp256k1) => assume::(&self.inst), + KeyAlg::EcCurve(EcCurves::Secp256r1) => assume::(&self.inst), + _ => { + return Err(err_msg!( + Unsupported, + "Signing is not supported for this key type" + )) + } + }; + key.write_signature(message, sig_type, out) + } +} + +impl KeySigVerify for AnyKey { + fn verify_signature( + &self, + message: &[u8], + signature: &[u8], + sig_type: Option, + ) -> Result { + let key: &dyn KeySigVerify = match self.alg { + KeyAlg::Ed25519 => assume::(&self.inst), + KeyAlg::EcCurve(EcCurves::Secp256k1) => assume::(&self.inst), + KeyAlg::EcCurve(EcCurves::Secp256r1) => assume::(&self.inst), + _ => { + return Err(err_msg!( + Unsupported, + "Signature verification not supported for this key type" + )) + } + }; + key.verify_signature(message, signature, sig_type) + } +} diff --git a/askar-crypto/src/alg/chacha20.rs b/askar-crypto/src/alg/chacha20.rs index 1f2a8e7b..dab3e94a 100644 --- a/askar-crypto/src/alg/chacha20.rs +++ b/askar-crypto/src/alg/chacha20.rs @@ -5,12 +5,12 @@ use chacha20poly1305::{ChaCha20Poly1305, XChaCha20Poly1305}; use serde::{Deserialize, Serialize}; use zeroize::Zeroize; -use crate::generic_array::{typenum::Unsigned, GenericArray}; - +use super::{Chacha20Types, KeyAlg}; use crate::{ - buffer::{ArrayKey, ResizeBuffer, WriteBuffer, Writer}, + buffer::{ArrayKey, ResizeBuffer, Writer}, encrypt::{KeyAeadInPlace, KeyAeadMeta}, error::Error, + generic_array::{typenum::Unsigned, GenericArray}, jwk::{JwkEncoder, ToJwk}, kdf::{FromKeyExchange, KeyExchange}, random::fill_random_deterministic, @@ -22,6 +22,7 @@ pub static JWK_KEY_TYPE: &'static str = "oct"; pub trait Chacha20Type { type Aead: NewAead + Aead + AeadInPlace; + const ALG_TYPE: Chacha20Types; const JWK_ALG: &'static str; } @@ -30,6 +31,7 @@ pub struct C20P; impl Chacha20Type for C20P { type Aead = ChaCha20Poly1305; + const ALG_TYPE: Chacha20Types = Chacha20Types::C20P; const JWK_ALG: &'static str = "C20P"; } @@ -38,6 +40,7 @@ pub struct XC20P; impl Chacha20Type for XC20P { type Aead = XChaCha20Poly1305; + const ALG_TYPE: Chacha20Types = Chacha20Types::XC20P; const JWK_ALG: &'static str = "XC20P"; } @@ -101,6 +104,8 @@ impl PartialEq for Chacha20Key { impl Eq for Chacha20Key {} impl KeyMeta for Chacha20Key { + const ALG: KeyAlg = KeyAlg::Chacha20(T::ALG_TYPE); + type KeySize = ::KeySize; } @@ -157,7 +162,7 @@ impl KeyAeadInPlace for Chacha20Key { let tag = chacha .encrypt_in_place_detached(nonce, aad, buffer.as_mut()) .map_err(|_| err_msg!(Encryption, "AEAD encryption error"))?; - buffer.write_slice(&tag[..])?; + buffer.buffer_write(&tag[..])?; Ok(()) } @@ -197,7 +202,7 @@ impl KeyAeadInPlace for Chacha20Key { } impl ToJwk for Chacha20Key { - fn to_jwk_encoder(&self, enc: &mut JwkEncoder) -> Result<(), Error> { + fn to_jwk_encoder(&self, enc: &mut JwkEncoder<'_>) -> Result<(), Error> { if enc.is_public() { return Err(err_msg!(Unsupported, "Cannot export as a public key")); } diff --git a/askar-crypto/src/alg/ed25519.rs b/askar-crypto/src/alg/ed25519.rs index 77ee9251..87e21d2f 100644 --- a/askar-crypto/src/alg/ed25519.rs +++ b/askar-crypto/src/alg/ed25519.rs @@ -8,7 +8,7 @@ use ed25519_dalek::{ExpandedSecretKey, PublicKey, SecretKey, Signature}; use sha2::{self, Digest}; use x25519_dalek::{PublicKey as XPublicKey, StaticSecret as XSecretKey}; -use super::x25519::X25519KeyPair; +use super::{x25519::X25519KeyPair, KeyAlg}; use crate::{ buffer::{ArrayKey, WriteBuffer}, error::Error, @@ -125,6 +125,7 @@ impl KeyGen for Ed25519KeyPair { } impl KeyMeta for Ed25519KeyPair { + const ALG: KeyAlg = KeyAlg::Ed25519; type KeySize = U32; } @@ -193,17 +194,17 @@ impl KeyPublicBytes for Ed25519KeyPair { } impl KeySign for Ed25519KeyPair { - fn key_sign_buffer( + fn write_signature( &self, - data: &[u8], + message: &[u8], sig_type: Option, - out: &mut B, + out: &mut dyn WriteBuffer, ) -> Result<(), Error> { match sig_type { None | Some(SignatureType::EdDSA) => { if let Some(signer) = self.to_signing_key() { - let sig = signer.sign(data); - out.write_slice(&sig[..])?; + let sig = signer.sign(message); + out.buffer_write(&sig[..])?; Ok(()) } else { Err(err_msg!(MissingSecretKey)) @@ -216,7 +217,7 @@ impl KeySign for Ed25519KeyPair { } impl KeySigVerify for Ed25519KeyPair { - fn key_verify( + fn verify_signature( &self, message: &[u8], signature: &[u8], @@ -231,7 +232,7 @@ impl KeySigVerify for Ed25519KeyPair { } impl ToJwk for Ed25519KeyPair { - fn to_jwk_encoder(&self, enc: &mut JwkEncoder) -> Result<(), Error> { + fn to_jwk_encoder(&self, enc: &mut JwkEncoder<'_>) -> Result<(), Error> { enc.add_str("crv", JWK_CURVE)?; enc.add_str("kty", JWK_KEY_TYPE)?; self.with_public_bytes(|buf| enc.add_as_base64("x", buf))?; diff --git a/askar-crypto/src/alg/k256.rs b/askar-crypto/src/alg/k256.rs index c0ad2d1d..f35ff7ee 100644 --- a/askar-crypto/src/alg/k256.rs +++ b/askar-crypto/src/alg/k256.rs @@ -9,6 +9,7 @@ use k256::{ EncodedPoint, PublicKey, SecretKey, }; +use super::{EcCurves, KeyAlg}; use crate::{ buffer::{ArrayKey, WriteBuffer}, error::Error, @@ -79,6 +80,7 @@ impl KeyGen for K256KeyPair { } impl KeyMeta for K256KeyPair { + const ALG: KeyAlg = KeyAlg::EcCurve(EcCurves::Secp256k1); type KeySize = U32; } @@ -154,16 +156,16 @@ impl KeyPublicBytes for K256KeyPair { } impl KeySign for K256KeyPair { - fn key_sign_buffer( + fn write_signature( &self, message: &[u8], sig_type: Option, - out: &mut B, + out: &mut dyn WriteBuffer, ) -> Result<(), Error> { match sig_type { None | Some(SignatureType::ES256K) => { if let Some(sig) = self.sign(message) { - out.write_slice(&sig[..])?; + out.buffer_write(&sig[..])?; Ok(()) } else { Err(err_msg!(Unsupported, "Undefined secret key")) @@ -176,7 +178,7 @@ impl KeySign for K256KeyPair { } impl KeySigVerify for K256KeyPair { - fn key_verify( + fn verify_signature( &self, message: &[u8], signature: &[u8], @@ -191,7 +193,7 @@ impl KeySigVerify for K256KeyPair { } impl ToJwk for K256KeyPair { - fn to_jwk_encoder(&self, enc: &mut JwkEncoder) -> Result<(), Error> { + fn to_jwk_encoder(&self, enc: &mut JwkEncoder<'_>) -> Result<(), Error> { let pk_enc = EncodedPoint::encode(self.public, false); let (x, y) = match pk_enc.coordinates() { Coordinates::Identity => { @@ -256,7 +258,7 @@ impl KeyExchange for K256KeyPair { match self.secret.as_ref() { Some(sk) => { let xk = diffie_hellman(sk.secret_scalar(), other.public.as_affine()); - out.write_slice(xk.as_bytes())?; + out.buffer_write(xk.as_bytes())?; Ok(()) } None => Err(err_msg!(MissingSecretKey)), diff --git a/askar-crypto/src/alg/mod.rs b/askar-crypto/src/alg/mod.rs index a8a8e86f..396407f0 100644 --- a/askar-crypto/src/alg/mod.rs +++ b/askar-crypto/src/alg/mod.rs @@ -7,6 +7,11 @@ use zeroize::Zeroize; use crate::error::Error; +#[cfg(any(test, feature = "any_key"))] +mod any; +#[cfg(any(test, feature = "any_key"))] +pub use any::AnyKey; + // pub mod bls; pub mod aesgcm; @@ -24,9 +29,9 @@ pub mod p256; #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Zeroize)] pub enum KeyAlg { /// AES - Aes(AesSizes), + Aes(AesTypes), /// (X)ChaCha20-Poly1305 - Chacha20(Chacha20Sizes), + Chacha20(Chacha20Types), /// Curve25519 signing key Ed25519, /// Curve25519 diffie-hellman key exchange key @@ -41,11 +46,10 @@ impl KeyAlg { /// Get a reference to a string representing the `KeyAlg` pub fn as_str(&self) -> &str { match self { - Self::Aes(AesSizes::A128GCM) => "a128gcm", - Self::Aes(AesSizes::A192GCM) => "a192gcm", - Self::Aes(AesSizes::A256GCM) => "a256gcm", - Self::Chacha20(Chacha20Sizes::C20P) => "c20p", - Self::Chacha20(Chacha20Sizes::XC20P) => "xc20p", + Self::Aes(AesTypes::A128GCM) => "a128gcm", + Self::Aes(AesTypes::A256GCM) => "a256gcm", + Self::Chacha20(Chacha20Types::C20P) => "c20p", + Self::Chacha20(Chacha20Types::XC20P) => "xc20p", Self::Ed25519 => "ed25519", Self::X25519 => "x25519", Self::EcCurve(EcCurves::Secp256k1) => "k256", @@ -65,13 +69,12 @@ impl FromStr for KeyAlg { fn from_str(s: &str) -> Result { Ok(match s { - "aes128gcm" => Self::Aes(AesSizes::A128GCM), - "aes192gcm" => Self::Aes(AesSizes::A192GCM), - "aes256gcm" => Self::Aes(AesSizes::A256GCM), - "chacha20poly1305" => Self::Chacha20(Chacha20Sizes::C20P), - "xchacha20poly1305" => Self::Chacha20(Chacha20Sizes::XC20P), - "c20p" => Self::Chacha20(Chacha20Sizes::C20P), - "xc20p" => Self::Chacha20(Chacha20Sizes::XC20P), + "aes128gcm" => Self::Aes(AesTypes::A128GCM), + "aes256gcm" => Self::Aes(AesTypes::A256GCM), + "chacha20poly1305" => Self::Chacha20(Chacha20Types::C20P), + "xchacha20poly1305" => Self::Chacha20(Chacha20Types::XC20P), + "c20p" => Self::Chacha20(Chacha20Types::C20P), + "xc20p" => Self::Chacha20(Chacha20Types::XC20P), "ed25519" => Self::Ed25519, "x25519" => Self::X25519, "k256" => Self::EcCurve(EcCurves::Secp256k1), @@ -99,18 +102,16 @@ pub enum BlsGroups { /// Supported algorithms for AES #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Zeroize)] -pub enum AesSizes { +pub enum AesTypes { /// AES 128-bit GCM A128GCM, - /// AES 192-bit GCM - A192GCM, /// AES 256-bit GCM A256GCM, } /// Supported algorithms for (X)ChaCha20-Poly1305 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Zeroize)] -pub enum Chacha20Sizes { +pub enum Chacha20Types { /// ChaCha20-Poly1305 C20P, /// XChaCha20-Poly1305 diff --git a/askar-crypto/src/alg/p256.rs b/askar-crypto/src/alg/p256.rs index 059dfec9..073d06df 100644 --- a/askar-crypto/src/alg/p256.rs +++ b/askar-crypto/src/alg/p256.rs @@ -9,6 +9,7 @@ use p256::{ EncodedPoint, PublicKey, SecretKey, }; +use super::{EcCurves, KeyAlg}; use crate::{ buffer::{ArrayKey, WriteBuffer}, error::Error, @@ -78,6 +79,7 @@ impl KeyGen for P256KeyPair { } } impl KeyMeta for P256KeyPair { + const ALG: KeyAlg = KeyAlg::EcCurve(EcCurves::Secp256r1); type KeySize = U32; } @@ -153,16 +155,16 @@ impl KeyPublicBytes for P256KeyPair { } impl KeySign for P256KeyPair { - fn key_sign_buffer( + fn write_signature( &self, message: &[u8], sig_type: Option, - out: &mut B, + out: &mut dyn WriteBuffer, ) -> Result<(), Error> { match sig_type { None | Some(SignatureType::ES256K) => { if let Some(sig) = self.sign(message) { - out.write_slice(&sig[..])?; + out.buffer_write(&sig[..])?; Ok(()) } else { Err(err_msg!(Unsupported, "Undefined secret key")) @@ -175,7 +177,7 @@ impl KeySign for P256KeyPair { } impl KeySigVerify for P256KeyPair { - fn key_verify( + fn verify_signature( &self, message: &[u8], signature: &[u8], @@ -190,7 +192,7 @@ impl KeySigVerify for P256KeyPair { } impl ToJwk for P256KeyPair { - fn to_jwk_encoder(&self, enc: &mut JwkEncoder) -> Result<(), Error> { + fn to_jwk_encoder(&self, enc: &mut JwkEncoder<'_>) -> Result<(), Error> { let pk_enc = EncodedPoint::encode(self.public, false); let (x, y) = match pk_enc.coordinates() { Coordinates::Identity => { @@ -255,7 +257,7 @@ impl KeyExchange for P256KeyPair { match self.secret.as_ref() { Some(sk) => { let xk = diffie_hellman(sk.secret_scalar(), other.public.as_affine()); - out.write_slice(xk.as_bytes())?; + out.buffer_write(xk.as_bytes())?; Ok(()) } None => Err(err_msg!(MissingSecretKey)), diff --git a/askar-crypto/src/alg/x25519.rs b/askar-crypto/src/alg/x25519.rs index 62af47b5..b02e7d6f 100644 --- a/askar-crypto/src/alg/x25519.rs +++ b/askar-crypto/src/alg/x25519.rs @@ -6,6 +6,7 @@ use core::{ use x25519_dalek::{PublicKey, StaticSecret as SecretKey}; use zeroize::Zeroizing; +use super::KeyAlg; use crate::{ buffer::{ArrayKey, WriteBuffer}, error::Error, @@ -78,6 +79,7 @@ impl KeyGen for X25519KeyPair { } impl KeyMeta for X25519KeyPair { + const ALG: KeyAlg = KeyAlg::X25519; type KeySize = U32; } @@ -168,7 +170,7 @@ impl KeyPublicBytes for X25519KeyPair { } impl ToJwk for X25519KeyPair { - fn to_jwk_encoder(&self, enc: &mut JwkEncoder) -> Result<(), Error> { + fn to_jwk_encoder(&self, enc: &mut JwkEncoder<'_>) -> Result<(), Error> { enc.add_str("crv", JWK_CURVE)?; enc.add_str("kty", JWK_KEY_TYPE)?; self.with_public_bytes(|buf| enc.add_as_base64("x", buf))?; @@ -218,7 +220,7 @@ impl KeyExchange for X25519KeyPair { match self.secret.as_ref() { Some(sk) => { let xk = sk.diffie_hellman(&other.public); - out.write_slice(xk.as_bytes())?; + out.buffer_write(xk.as_bytes())?; Ok(()) } None => Err(err_msg!(MissingSecretKey)), diff --git a/askar-crypto/src/any.rs b/askar-crypto/src/any.rs deleted file mode 100644 index 73b72642..00000000 --- a/askar-crypto/src/any.rs +++ /dev/null @@ -1,106 +0,0 @@ -use std::convert::TryFrom; - -use super::alg::ed25519::{Ed25519KeyPair, Ed25519PublicKey}; -use super::alg::k256::{K256SigningKey, K256VerifyingKey}; -use super::alg::p256::{P256SigningKey, P256VerifyingKey}; -use super::caps::{ - EcCurves, KeyAlg, KeyCapGetPublic, KeyCapSign, KeyCapVerify, KeyCategory, SignatureFormat, - SignatureType, -}; -// use super::store::KeyEntry; -use crate::error::Error; -use crate::types::SecretBytes; - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct AnyPublicKey { - pub(crate) alg: KeyAlg, - pub(crate) data: Vec, -} - -impl AnyPublicKey { - pub fn key_alg(&self) -> KeyAlg { - self.alg - } - - pub fn key_data(&self) -> &[u8] { - self.data.as_ref() - } -} - -impl KeyCapVerify for AnyPublicKey { - fn key_verify( - &self, - data: &[u8], - signature: &[u8], - sig_type: Option, - sig_format: Option, - ) -> Result { - match self.alg { - KeyAlg::Ed25519 => Ed25519PublicKey::try_from(self) - .and_then(|k| k.key_verify(data, signature, sig_type, sig_format)), - KeyAlg::Ecdsa(EcCurves::Secp256k1) => K256VerifyingKey::try_from(self) - .and_then(|k| k.key_verify(data, signature, sig_type, sig_format)), - KeyAlg::Ecdsa(EcCurves::Secp256r1) => P256VerifyingKey::try_from(self) - .and_then(|k| k.key_verify(data, signature, sig_type, sig_format)), - _ => Err(err_msg!( - Unsupported, - "Signature verification not supported for this key type" - )), - } - } -} - -#[derive(Debug)] -pub struct AnyPrivateKey { - pub alg: KeyAlg, - pub data: SecretBytes, -} - -impl KeyCapGetPublic for AnyPrivateKey { - fn key_get_public(&self, alg: Option) -> Result { - unimplemented!(); - } -} - -impl KeyCapSign for AnyPrivateKey { - fn key_sign(&self, data: &[u8], sig_type: Option) -> Result, Error> { - match self.alg { - KeyAlg::Ed25519 => { - Ed25519KeyPair::try_from(self).and_then(|k| k.key_sign(data, sig_type, sig_format)) - } - KeyAlg::Ecdsa(EcCurves::Secp256k1) => { - K256SigningKey::try_from(self).and_then(|k| k.key_sign(data, sig_type, sig_format)) - } - KeyAlg::Ecdsa(EcCurves::Secp256r1) => { - P256SigningKey::try_from(self).and_then(|k| k.key_sign(data, sig_type, sig_format)) - } - _ => Err(err_msg!( - Unsupported, - "Signing not supported for this key type" - )), - } - } -} - -impl KeyCapVerify for AnyPrivateKey { - fn key_verify( - &self, - data: &[u8], - signature: &[u8], - sig_type: Option, - sig_format: Option, - ) -> Result { - match self.alg { - KeyAlg::Ed25519 => Ed25519KeyPair::try_from(self) - .and_then(|k| k.key_verify(data, signature, sig_type, sig_format)), - KeyAlg::Ecdsa(EcCurves::Secp256k1) => K256SigningKey::try_from(self) - .and_then(|k| k.key_verify(data, signature, sig_type, sig_format)), - KeyAlg::Ecdsa(EcCurves::Secp256r1) => P256SigningKey::try_from(self) - .and_then(|k| k.key_verify(data, signature, sig_type, sig_format)), - _ => Err(err_msg!( - Unsupported, - "Signature verification not supported for this key type" - )), - } - } -} diff --git a/askar-crypto/src/buffer/hash.rs b/askar-crypto/src/buffer/hash.rs index a3c8bbe2..a84df250 100644 --- a/askar-crypto/src/buffer/hash.rs +++ b/askar-crypto/src/buffer/hash.rs @@ -4,8 +4,6 @@ use crate::generic_array::GenericArray; use crate::{buffer::WriteBuffer, error::Error}; -const BUFFER_SIZE: usize = 256; - pub struct HashBuffer(D); impl HashBuffer { @@ -19,26 +17,8 @@ impl HashBuffer { } impl WriteBuffer for HashBuffer { - fn write_slice(&mut self, data: &[u8]) -> Result<(), Error> { + fn buffer_write(&mut self, data: &[u8]) -> Result<(), Error> { self.0.update(data); Ok(()) } - - fn write_with( - &mut self, - max_len: usize, - f: impl FnOnce(&mut [u8]) -> Result, - ) -> Result { - // This could use a Vec to support larger inputs - // but for current purposes a small fixed buffer is fine. - // Could also accept the buffer (ResizeBuffer) as an argument - // when creating the hasher. - if max_len > BUFFER_SIZE { - return Err(err_msg!(Usage, "Exceeded hash buffer size")); - } - let mut buf = [0u8; BUFFER_SIZE]; - let written = f(&mut buf[..max_len])?; - self.write_slice(&buf[..written])?; - Ok(written) - } } diff --git a/askar-crypto/src/buffer/mod.rs b/askar-crypto/src/buffer/mod.rs index 22ee24b3..0ca1842b 100644 --- a/askar-crypto/src/buffer/mod.rs +++ b/askar-crypto/src/buffer/mod.rs @@ -1,7 +1,5 @@ #[cfg(feature = "alloc")] use alloc::vec::Vec; -#[cfg(feature = "alloc")] -use core::iter; use core::ops::Range; use crate::error::Error; @@ -24,77 +22,46 @@ mod writer; pub use self::writer::Writer; pub trait WriteBuffer { - fn write_slice(&mut self, data: &[u8]) -> Result<(), Error> { - let len = data.len(); - self.write_with(len, |ext| { - ext.copy_from_slice(data); - Ok(len) - })?; - Ok(()) - } - - fn write_with( - &mut self, - max_len: usize, - f: impl FnOnce(&mut [u8]) -> Result, - ) -> Result; + fn buffer_write(&mut self, data: &[u8]) -> Result<(), Error>; } pub trait ResizeBuffer: WriteBuffer + AsRef<[u8]> + AsMut<[u8]> { - fn buffer_insert_slice(&mut self, pos: usize, data: &[u8]) -> Result<(), Error> { - self.buffer_splice_with(pos..pos, data.len(), |ext| { - ext.copy_from_slice(data); - Ok(()) - }) - } + fn buffer_insert(&mut self, pos: usize, data: &[u8]) -> Result<(), Error>; - fn buffer_remove(&mut self, range: Range) -> Result<(), Error> { - self.buffer_splice_with(range, 0, |_| Ok(())) - } + fn buffer_remove(&mut self, range: Range) -> Result<(), Error>; fn buffer_resize(&mut self, len: usize) -> Result<(), Error>; - fn buffer_splice_with( - &mut self, - range: Range, - len: usize, - f: impl FnOnce(&mut [u8]) -> Result<(), Error>, - ) -> Result<(), Error>; + fn buffer_extend(&mut self, len: usize) -> Result<&mut [u8], Error> { + let pos = self.as_ref().len(); + let end = pos + len; + self.buffer_resize(end)?; + Ok(&mut self.as_mut()[pos..end]) + } } #[cfg(feature = "alloc")] impl WriteBuffer for Vec { - fn write_with( - &mut self, - max_len: usize, - f: impl FnOnce(&mut [u8]) -> Result, - ) -> Result { - let len = self.len(); - self.resize(len + max_len, 0u8); - let written = f(&mut self[len..(len + max_len)])?; - if written < max_len { - self.truncate(len + written); - } - Ok(written) + fn buffer_write(&mut self, data: &[u8]) -> Result<(), Error> { + self.extend_from_slice(data); + Ok(()) } } #[cfg(feature = "alloc")] impl ResizeBuffer for Vec { - fn buffer_resize(&mut self, len: usize) -> Result<(), Error> { - self.resize(len, 0u8); + fn buffer_insert(&mut self, pos: usize, data: &[u8]) -> Result<(), Error> { + self.splice(pos..pos, data.into_iter().cloned()); + Ok(()) + } + + fn buffer_remove(&mut self, range: Range) -> Result<(), Error> { + self.drain(range); Ok(()) } - fn buffer_splice_with( - &mut self, - range: Range, - len: usize, - f: impl FnOnce(&mut [u8]) -> Result<(), Error>, - ) -> Result<(), Error> { - let start = range.start; - self.splice(range, iter::repeat(0u8).take(len)); - f(&mut self[start..(start + len)])?; + fn buffer_resize(&mut self, len: usize) -> Result<(), Error> { + self.resize(len, 0u8); Ok(()) } } @@ -104,33 +71,21 @@ mod tests { use super::*; pub(crate) fn test_write_buffer>(mut w: B) { - w.write_with(5, |buf| { - buf.copy_from_slice(b"hello"); - Ok(2) - }) - .unwrap(); - w.write_slice(b"y").unwrap(); + w.buffer_write(b"he").unwrap(); + w.buffer_write(b"y").unwrap(); assert_eq!(&w.as_ref()[..], b"hey"); } pub(crate) fn test_resize_buffer(mut w: B) { - w.write_slice(b"hello").unwrap(); - w.buffer_splice_with(1..3, 5, |ext| { - ext.copy_from_slice(b"sugar"); - Ok(()) - }) - .unwrap(); - assert_eq!(&w.as_ref()[..], b"hsugarlo"); - w.buffer_splice_with(1..6, 2, |ext| { - ext.copy_from_slice(b"el"); - Ok(()) - }) - .unwrap(); - assert_eq!(&w.as_ref()[..], b"hello"); - w.buffer_resize(7).unwrap(); - assert_eq!(&w.as_ref()[..], b"hello\0\0"); - w.buffer_resize(5).unwrap(); - assert_eq!(&w.as_ref()[..], b"hello"); + w.buffer_write(b"hello").unwrap(); + w.buffer_insert(1, b"world").unwrap(); + assert_eq!(&w.as_ref()[..], b"hworldello"); + w.buffer_resize(12).unwrap(); + assert_eq!(&w.as_ref()[..], b"hworldello\0\0"); + w.buffer_resize(6).unwrap(); + assert_eq!(&w.as_ref()[..], b"hworld"); + w.buffer_insert(1, b"ello").unwrap(); + assert_eq!(&w.as_ref()[..], b"helloworld"); } #[test] diff --git a/askar-crypto/src/buffer/secret.rs b/askar-crypto/src/buffer/secret.rs index 6cb413f3..565f5e17 100644 --- a/askar-crypto/src/buffer/secret.rs +++ b/askar-crypto/src/buffer/secret.rs @@ -1,7 +1,7 @@ use alloc::{string::String, vec::Vec}; use core::{ fmt::{self, Debug, Formatter}, - iter, mem, + mem, ops::{Deref, Range}, }; @@ -76,6 +76,21 @@ impl SecretBytes { pub(crate) fn as_vec_mut(&mut self) -> &mut Vec { &mut self.0 } + + pub(crate) fn splice( + &mut self, + range: Range, + iter: impl Iterator + ExactSizeIterator, + ) -> Result<(), Error> { + assert!(range.end >= range.start); + let rem_len = range.len(); + let ins_len = iter.len(); + if ins_len > rem_len { + self.reserve(ins_len - rem_len); + } + self.0.splice(range, iter); + Ok(()) + } } impl Debug for SecretBytes { @@ -153,42 +168,28 @@ impl PartialEq> for SecretBytes { } impl WriteBuffer for SecretBytes { - fn write_with( - &mut self, - max_len: usize, - f: impl FnOnce(&mut [u8]) -> Result, - ) -> Result { - let len = self.0.len(); - let new_len = len + max_len; + fn buffer_write(&mut self, data: &[u8]) -> Result<(), Error> { + let pos = self.0.len(); + let new_len = pos + data.len(); self.buffer_resize(new_len)?; - let written = f(&mut self.0[len..new_len])?; - if written < max_len { - self.0.truncate(len + written); - } - Ok(written) + self.0[pos..new_len].copy_from_slice(data); + Ok(()) } } impl ResizeBuffer for SecretBytes { - fn buffer_resize(&mut self, len: usize) -> Result<(), Error> { - self.ensure_capacity(len); - self.0.resize(len, 0u8); + fn buffer_insert(&mut self, pos: usize, data: &[u8]) -> Result<(), Error> { + self.splice(pos..pos, data.into_iter().cloned()) + } + + fn buffer_remove(&mut self, range: Range) -> Result<(), Error> { + self.0.drain(range); Ok(()) } - fn buffer_splice_with( - &mut self, - range: Range, - len: usize, - f: impl FnOnce(&mut [u8]) -> Result<(), Error>, - ) -> Result<(), Error> { - let rem_len = range.len(); - if len > rem_len { - self.reserve(len - rem_len); - } - let start = range.start; - self.0.splice(range, iter::repeat(0u8).take(len)); - f(&mut self.0[start..(start + len)])?; + fn buffer_resize(&mut self, len: usize) -> Result<(), Error> { + self.ensure_capacity(len); + self.0.resize(len, 0u8); Ok(()) } } @@ -230,48 +231,9 @@ impl<'de> de::Visitor<'de> for SecVisitor { #[cfg(test)] mod tests { + use super::super::tests::{test_resize_buffer, test_write_buffer}; use super::*; - pub(crate) fn test_write_buffer>(mut w: B) { - w.write_with(5, |buf| { - buf.copy_from_slice(b"hello"); - Ok(2) - }) - .unwrap(); - w.write_slice(b"y").unwrap(); - assert_eq!(&w.as_ref()[..], b"hey"); - } - - pub(crate) fn test_resize_buffer(mut w: B) { - w.write_slice(b"hello").unwrap(); - w.buffer_splice_with(1..3, 5, |ext| { - ext.copy_from_slice(b"sugar"); - Ok(()) - }) - .unwrap(); - assert_eq!(&w.as_ref()[..], b"hsugarlo"); - w.buffer_splice_with(1..6, 2, |ext| { - ext.copy_from_slice(b"el"); - Ok(()) - }) - .unwrap(); - assert_eq!(&w.as_ref()[..], b"hello"); - w.buffer_resize(7).unwrap(); - assert_eq!(&w.as_ref()[..], b"hello\0\0"); - w.buffer_resize(5).unwrap(); - assert_eq!(&w.as_ref()[..], b"hello"); - } - - #[test] - fn write_buffer_vec() { - test_write_buffer(Vec::new()); - } - - #[test] - fn resize_buffer_vec() { - test_resize_buffer(Vec::new()); - } - #[test] fn write_buffer_secret() { test_write_buffer(SecretBytes::with_capacity(10)); diff --git a/askar-crypto/src/buffer/writer.rs b/askar-crypto/src/buffer/writer.rs index cfb97a80..58f1e7b0 100644 --- a/askar-crypto/src/buffer/writer.rs +++ b/askar-crypto/src/buffer/writer.rs @@ -1,6 +1,6 @@ #[cfg(feature = "alloc")] use alloc::vec::Vec; -use core::ops::Range; +use core::{fmt, ops::Range}; use super::{ResizeBuffer, WriteBuffer}; use crate::error::Error; @@ -31,6 +31,36 @@ impl<'w> Writer<'w, [u8]> { } } +impl Writer<'_, [u8]> { + pub(crate) fn splice( + &mut self, + range: Range, + mut iter: impl Iterator + ExactSizeIterator, + ) -> Result<(), Error> { + assert!(range.end >= range.start); + let rem_len = range.len(); + let ins_len = iter.len(); + if ins_len > rem_len { + let diff = ins_len - rem_len; + if self.pos + diff > self.inner.len() { + return Err(err_msg!(ExceededBuffer)); + } + self.inner + .copy_within((range.end - diff)..self.pos, range.end); + self.pos += diff; + } else if ins_len < rem_len { + let diff = rem_len - ins_len; + self.inner + .copy_within(range.end..self.pos, range.end - diff); + self.pos -= diff; + } + for idx in 0..ins_len { + self.inner[range.start + idx] = iter.next().unwrap(); + } + Ok(()) + } +} + impl AsRef<[u8]> for Writer<'_, [u8]> { fn as_ref(&self) -> &[u8] { &self.inner[..self.pos] @@ -44,23 +74,31 @@ impl AsMut<[u8]> for Writer<'_, [u8]> { } impl WriteBuffer for Writer<'_, [u8]> { - fn write_with( - &mut self, - max_len: usize, - f: impl FnOnce(&mut [u8]) -> Result, - ) -> Result { + fn buffer_write(&mut self, data: &[u8]) -> Result<(), Error> { let total = self.inner.len(); - let end = max_len + self.pos; + let end = self.pos + data.len(); if end > total { return Err(err_msg!(ExceededBuffer)); } - let written = f(&mut self.inner[self.pos..end])?; - self.pos += written; - Ok(written) + self.inner[self.pos..end].copy_from_slice(data); + self.pos += data.len(); + Ok(()) } } impl ResizeBuffer for Writer<'_, [u8]> { + fn buffer_insert(&mut self, pos: usize, data: &[u8]) -> Result<(), Error> { + self.splice(pos..pos, data.into_iter().cloned()) + } + + fn buffer_remove(&mut self, range: Range) -> Result<(), Error> { + assert!(range.end >= range.start); + let diff = range.end - range.start; + self.inner.copy_within(range.end..self.pos, range.start); + self.pos -= diff; + Ok(()) + } + fn buffer_resize(&mut self, len: usize) -> Result<(), Error> { let len = self.pos + len; if len > self.inner.len() { @@ -69,38 +107,6 @@ impl ResizeBuffer for Writer<'_, [u8]> { self.pos = len; Ok(()) } - - fn buffer_splice_with( - &mut self, - range: Range, - len: usize, - f: impl FnOnce(&mut [u8]) -> Result<(), Error>, - ) -> Result<(), Error> { - let rem_len = range.len(); - if rem_len < len { - if self.pos + len - rem_len > self.inner.len() { - return Err(err_msg!(ExceededBuffer)); - } - let diff = len - rem_len; - self.pos += diff; - for p in (self.pos - 1)..=range.end { - self.inner[p] = self.inner[p - diff]; - } - } else if rem_len != len { - let diff = rem_len - len; - for p in range.end..self.pos { - self.inner[p] = self.inner[p + diff]; - } - self.pos -= diff; - } - let end = range.start + len; - let mslice = &mut self.inner[range.start..end]; - for p in 0..len { - mslice[p] = 0u8; - } - f(mslice)?; - Ok(()) - } } #[cfg(feature = "alloc")] @@ -116,40 +122,50 @@ impl<'w> Writer<'w, Vec> { } } -impl WriteBuffer for Writer<'_, B> { - fn write_with( - &mut self, - max_len: usize, - f: impl FnOnce(&mut [u8]) -> Result, - ) -> Result { - self.inner.write_with(max_len, f) +impl WriteBuffer for Writer<'_, B> { + fn buffer_write(&mut self, data: &[u8]) -> Result<(), Error> { + self.inner.buffer_write(data) } } -impl AsRef<[u8]> for Writer<'_, B> { +impl AsRef<[u8]> for Writer<'_, B> { fn as_ref(&self) -> &[u8] { &self.inner.as_ref()[self.pos..] } } -impl AsMut<[u8]> for Writer<'_, B> { +impl AsMut<[u8]> for Writer<'_, B> { fn as_mut(&mut self) -> &mut [u8] { &mut self.inner.as_mut()[self.pos..] } } -impl ResizeBuffer for Writer<'_, B> { +impl ResizeBuffer for Writer<'_, B> { + fn buffer_insert(&mut self, pos: usize, data: &[u8]) -> Result<(), Error> { + self.inner.buffer_insert(pos, data) + } + + fn buffer_remove(&mut self, range: Range) -> Result<(), Error> { + self.inner.buffer_remove(range) + } + fn buffer_resize(&mut self, len: usize) -> Result<(), Error> { self.inner.buffer_resize(self.pos + len) } +} + +impl<'b, B: ?Sized> Writer<'b, B> { + pub fn from_buffer(buf: &'b mut B) -> Writer<'b, B> { + Writer { inner: buf, pos: 0 } + } +} - fn buffer_splice_with( - &mut self, - range: Range, - len: usize, - f: impl FnOnce(&mut [u8]) -> Result<(), Error>, - ) -> Result<(), Error> { - self.inner.buffer_splice_with(range, len, f) +impl<'b, B: ?Sized> fmt::Write for Writer<'b, B> +where + Writer<'b, B>: WriteBuffer, +{ + fn write_str(&mut self, s: &str) -> fmt::Result { + self.buffer_write(s.as_bytes()).map_err(|_| fmt::Error) } } @@ -161,12 +177,8 @@ mod tests { fn write_buffer_slice() { let mut buf = [0u8; 10]; let mut w = Writer::from_slice(&mut buf); - w.write_with(5, |buf| { - buf.copy_from_slice(b"hello"); - Ok(2) - }) - .unwrap(); - w.write_slice(b"y").unwrap(); + w.buffer_write(b"he").unwrap(); + w.buffer_write(b"y").unwrap(); assert_eq!(w.position(), 3); assert_eq!(&buf[..3], b"hey"); } diff --git a/askar-crypto/src/encrypt/nacl_box.rs b/askar-crypto/src/encrypt/nacl_box.rs index 7ba3f426..88f29508 100644 --- a/askar-crypto/src/encrypt/nacl_box.rs +++ b/askar-crypto/src/encrypt/nacl_box.rs @@ -62,7 +62,7 @@ pub fn crypto_box( let tag = box_inst .encrypt_in_place_detached(nonce, &[], buffer.as_mut()) .map_err(|_| err_msg!(Encryption, "Crypto box AEAD encryption error"))?; - buffer.write_slice(&tag[..])?; + buffer.buffer_write(&tag[..])?; Ok(()) } @@ -97,8 +97,8 @@ pub fn crypto_box_seal(recip_pk: &X25519KeyPair, message: &[u8]) -> Result::USIZE; let mut buffer = SecretBytes::with_capacity(buf_len); - buffer.write_slice(ephem_pk_bytes)?; - buffer.write_slice(message)?; + buffer.buffer_write(ephem_pk_bytes)?; + buffer.buffer_write(message)?; let mut writer = Writer::from_vec_skip(buffer.as_vec_mut(), CBOX_KEY_SIZE); let nonce = crypto_box_nonce(ephem_pk_bytes, recip_pk.public.as_bytes())?.to_vec(); crypto_box(recip_pk, &ephem_kp, &mut writer, &nonce[..])?; diff --git a/askar-crypto/src/jwk/encode.rs b/askar-crypto/src/jwk/encode.rs index 4a65063d..8ea88694 100644 --- a/askar-crypto/src/jwk/encode.rs +++ b/askar-crypto/src/jwk/encode.rs @@ -1,23 +1,11 @@ -use crate::{buffer::WriteBuffer, error::Error}; +use core::fmt::Write; -use super::ops::KeyOpsSet; - -struct JwkBuffer<'s, B>(&'s mut B); +use crate::{ + buffer::{WriteBuffer, Writer}, + error::Error, +}; -impl bs58::encode::EncodeTarget for JwkBuffer<'_, B> { - fn encode_with( - &mut self, - max_len: usize, - f: impl FnOnce(&mut [u8]) -> Result, - ) -> Result { - self.0 - .write_with(max_len, |buf| { - // this cannot fail - there is enough space allocated - Ok(f(buf).unwrap()) - }) - .map_err(|_| bs58::encode::Error::BufferTooSmall) - } -} +use super::ops::KeyOpsSet; #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum JwkEncoderMode { @@ -26,53 +14,56 @@ pub enum JwkEncoderMode { Thumbprint, } -pub struct JwkEncoder<'b, B: WriteBuffer> { - buffer: &'b mut B, +pub struct JwkEncoder<'b> { + buffer: &'b mut dyn WriteBuffer, empty: bool, mode: JwkEncoderMode, } -impl<'b, B: WriteBuffer> JwkEncoder<'b, B> { - pub fn new(buffer: &'b mut B, mode: JwkEncoderMode) -> Result { +impl<'b> JwkEncoder<'b> { + pub fn new(buffer: &'b mut B, mode: JwkEncoderMode) -> Result { Ok(Self { buffer, empty: true, mode, }) } +} +impl JwkEncoder<'_> { fn start_attr(&mut self, key: &str) -> Result<(), Error> { let buffer = &mut *self.buffer; if self.empty { - buffer.write_slice(b"{\"")?; + buffer.buffer_write(b"{\"")?; self.empty = false; } else { - buffer.write_slice(b",\"")?; + buffer.buffer_write(b",\"")?; } - buffer.write_slice(key.as_bytes())?; - buffer.write_slice(b"\":")?; + buffer.buffer_write(key.as_bytes())?; + buffer.buffer_write(b"\":")?; Ok(()) } pub fn add_str(&mut self, key: &str, value: &str) -> Result<(), Error> { self.start_attr(key)?; let buffer = &mut *self.buffer; - buffer.write_slice(b"\"")?; - buffer.write_slice(value.as_bytes())?; - buffer.write_slice(b"\"")?; + buffer.buffer_write(b"\"")?; + buffer.buffer_write(value.as_bytes())?; + buffer.buffer_write(b"\"")?; Ok(()) } pub fn add_as_base64(&mut self, key: &str, value: &[u8]) -> Result<(), Error> { self.start_attr(key)?; let buffer = &mut *self.buffer; - let enc_size = ((value.len() << 2) + 2) / 3; - buffer.write_slice(b"\"")?; - buffer.write_with(enc_size, |mbuf| { - let len = base64::encode_config_slice(value, base64::URL_SAFE_NO_PAD, mbuf); - Ok(len) - })?; - buffer.write_slice(b"\"")?; + buffer.buffer_write(b"\"")?; + write!( + Writer::from_buffer(&mut *buffer), + "{}", + base64::display::Base64Display::with_config(value, base64::URL_SAFE_NO_PAD) + ) + .map_err(|_| err_msg!(Unexpected, "Error writing to JWK buffer"))?; + buffer.buffer_write(b"\"")?; Ok(()) } @@ -81,14 +72,14 @@ impl<'b, B: WriteBuffer> JwkEncoder<'b, B> { let buffer = &mut *self.buffer; for (idx, op) in ops.into().into_iter().enumerate() { if idx > 0 { - buffer.write_slice(b",\"")?; + buffer.buffer_write(b",\"")?; } else { - buffer.write_slice(b"\"")?; + buffer.buffer_write(b"\"")?; } - buffer.write_slice(op.as_str().as_bytes())?; - buffer.write_slice(b"\"")?; + buffer.buffer_write(op.as_str().as_bytes())?; + buffer.buffer_write(b"\"")?; } - buffer.write_slice(b"]")?; + buffer.buffer_write(b"]")?; Ok(()) } @@ -110,7 +101,7 @@ impl<'b, B: WriteBuffer> JwkEncoder<'b, B> { pub fn finalize(self) -> Result<(), Error> { if !self.empty { - self.buffer.write_slice(b"}")?; + self.buffer.buffer_write(b"}")?; } Ok(()) } diff --git a/askar-crypto/src/jwk/mod.rs b/askar-crypto/src/jwk/mod.rs index e8a7dffc..dc16e334 100644 --- a/askar-crypto/src/jwk/mod.rs +++ b/askar-crypto/src/jwk/mod.rs @@ -4,7 +4,7 @@ use alloc::{borrow::Cow, string::String, vec::Vec}; use sha2::Sha256; use crate::{ - buffer::{HashBuffer, WriteBuffer}, + buffer::{HashBuffer, ResizeBuffer}, error::Error, }; @@ -23,28 +23,12 @@ mod parts; pub use self::parts::JwkParts; pub trait ToJwk { - fn to_jwk_encoder(&self, enc: &mut JwkEncoder) -> Result<(), Error>; - - fn to_jwk_thumbprint_buffer(&self, output: &mut B) -> Result<(), Error> { - let mut hasher = HashBuffer::::new(); - let mut buf = JwkEncoder::new(&mut hasher, JwkEncoderMode::Thumbprint)?; - self.to_jwk_encoder(&mut buf)?; - buf.finalize()?; - let hash = hasher.finalize(); - output.write_with(43, |buf| { - Ok(base64::encode_config_slice( - &hash, - base64::URL_SAFE_NO_PAD, - buf, - )) - })?; - Ok(()) - } + fn to_jwk_encoder(&self, enc: &mut JwkEncoder<'_>) -> Result<(), Error>; #[cfg(feature = "alloc")] fn to_jwk_thumbprint(&self) -> Result { let mut v = Vec::with_capacity(43); - self.to_jwk_thumbprint_buffer(&mut v)?; + jwk_thumbprint_buffer(self, &mut v)?; Ok(String::from_utf8(v).unwrap()) } @@ -67,6 +51,19 @@ pub trait ToJwk { } } +pub fn jwk_thumbprint_buffer( + key: &K, + output: &mut dyn ResizeBuffer, +) -> Result<(), Error> { + let mut hasher = HashBuffer::::new(); + let mut buf = JwkEncoder::new(&mut hasher, JwkEncoderMode::Thumbprint)?; + key.to_jwk_encoder(&mut buf)?; + buf.finalize()?; + let hash = hasher.finalize(); + base64::encode_config_slice(&hash, base64::URL_SAFE_NO_PAD, output.buffer_extend(43)?); + Ok(()) +} + pub trait FromJwk: Sized { fn from_jwk(jwk: &str) -> Result { let parts = diff --git a/askar-crypto/src/jwk/parts.rs b/askar-crypto/src/jwk/parts.rs index 608508cd..a5ee676c 100644 --- a/askar-crypto/src/jwk/parts.rs +++ b/askar-crypto/src/jwk/parts.rs @@ -60,12 +60,6 @@ impl OptAttr<'_> { } } -impl AsRef<[u8]> for OptAttr<'_> { - fn as_ref(&self) -> &[u8] { - self.0.unwrap_or_default().as_bytes() - } -} - impl Debug for OptAttr<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self.0 { diff --git a/askar-crypto/src/kdf/concat.rs b/askar-crypto/src/kdf/concat.rs index 7994fc39..f7b3a1da 100644 --- a/askar-crypto/src/kdf/concat.rs +++ b/askar-crypto/src/kdf/concat.rs @@ -88,29 +88,11 @@ impl ConcatKDFHash { } } -const HASH_BUFFER_SIZE: usize = 128; - impl WriteBuffer for ConcatKDFHash { - fn write_slice(&mut self, data: &[u8]) -> Result<(), Error> { + fn buffer_write(&mut self, data: &[u8]) -> Result<(), Error> { self.hasher.update(data); Ok(()) } - - fn write_with( - &mut self, - max_len: usize, - f: impl FnOnce(&mut [u8]) -> Result, - ) -> Result { - // this could use a Vec to support larger inputs - // but for current purposes a small fixed buffer is fine - if max_len > HASH_BUFFER_SIZE { - return Err(err_msg!(Usage, "Exceeded hash buffer size")); - } - let mut buf = [0u8; HASH_BUFFER_SIZE]; - let written = f(&mut buf[..max_len])?; - self.write_slice(&buf[..written])?; - Ok(written) - } } #[cfg(test)] diff --git a/askar-crypto/src/lib.rs b/askar-crypto/src/lib.rs index 59f0b49d..c179369b 100644 --- a/askar-crypto/src/lib.rs +++ b/askar-crypto/src/lib.rs @@ -20,17 +20,13 @@ pub use self::error::{Error, ErrorKind}; // re-export pub use aead::generic_array; -#[cfg(feature = "any")] -pub mod any; -#[cfg(feature = "any")] -pub use self::any::{AnyPrivateKey, AnyPublicKey}; - pub mod alg; pub use self::alg::KeyAlg; pub mod buffer; pub mod encrypt; +pub use self::encrypt::{KeyAeadInPlace, KeyAeadMeta}; pub mod jwk; @@ -42,4 +38,6 @@ pub mod sign; pub use self::sign::{KeySigVerify, KeySign, SignatureType}; pub mod repr; -pub use self::repr::{KeyGen, KeyGenInPlace, KeySecretBytes}; +pub use self::repr::{ + KeyGen, KeyGenInPlace, KeyMeta, KeyPublicBytes, KeySecretBytes, KeypairBytes, KeypairMeta, +}; diff --git a/askar-crypto/src/repr.rs b/askar-crypto/src/repr.rs index 51e3fcaa..7f69189f 100644 --- a/askar-crypto/src/repr.rs +++ b/askar-crypto/src/repr.rs @@ -1,6 +1,6 @@ #[cfg(feature = "alloc")] use crate::buffer::SecretBytes; -use crate::{buffer::WriteBuffer, error::Error, generic_array::ArrayLength}; +use crate::{alg::KeyAlg, buffer::WriteBuffer, error::Error, generic_array::ArrayLength}; /// Generate a new random key. pub trait KeyGen: Sized { @@ -24,7 +24,7 @@ pub trait KeySecretBytes { fn to_secret_bytes_buffer(&self, out: &mut B) -> Result<(), Error> { self.with_secret_bytes(|buf| { if let Some(buf) = buf { - out.write_slice(buf) + out.buffer_write(buf) } else { Err(err_msg!(MissingSecretKey)) } @@ -48,7 +48,7 @@ pub trait KeyPublicBytes { fn with_public_bytes(&self, f: impl FnOnce(&[u8]) -> O) -> O; fn to_public_bytes_buffer(&self, out: &mut B) -> Result<(), Error> { - self.with_public_bytes(|buf| out.write_slice(buf)) + self.with_public_bytes(|buf| out.buffer_write(buf)) } #[cfg(feature = "alloc")] @@ -70,7 +70,7 @@ pub trait KeypairBytes { fn to_keypair_bytes_buffer(&self, out: &mut B) -> Result<(), Error> { self.with_keypair_bytes(|buf| { if let Some(buf) = buf { - out.write_slice(buf) + out.buffer_write(buf) } else { Err(err_msg!(MissingSecretKey)) } @@ -87,6 +87,8 @@ pub trait KeypairBytes { /// For concrete secret key types pub trait KeyMeta { + const ALG: KeyAlg; + type KeySize: ArrayLength; } diff --git a/askar-crypto/src/sign.rs b/askar-crypto/src/sign.rs index e9a0a557..05951bbe 100644 --- a/askar-crypto/src/sign.rs +++ b/askar-crypto/src/sign.rs @@ -3,25 +3,29 @@ use crate::buffer::SecretBytes; use crate::{alg::BlsGroups, buffer::WriteBuffer, error::Error}; pub trait KeySign: KeySigVerify { - fn key_sign_buffer( + fn write_signature( &self, - data: &[u8], + message: &[u8], sig_type: Option, - out: &mut B, + out: &mut dyn WriteBuffer, ) -> Result<(), Error>; #[cfg(feature = "alloc")] - fn key_sign(&self, data: &[u8], sig_type: Option) -> Result { + fn create_signature( + &self, + message: &[u8], + sig_type: Option, + ) -> Result { let mut buf = SecretBytes::with_capacity(128); - self.key_sign_buffer(data, sig_type, &mut buf)?; + self.write_signature(message, sig_type, &mut buf)?; Ok(buf) } } pub trait KeySigVerify { - fn key_verify( + fn verify_signature( &self, - data: &[u8], + message: &[u8], signature: &[u8], sig_type: Option, ) -> Result; From 9f52fc13f81508a92b4e9bc6d1b17dd98a603c2f Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Sun, 18 Apr 2021 23:40:59 -0700 Subject: [PATCH 046/116] update any key implementation; JWK cleanups Signed-off-by: Andrew Whitehead --- askar-crypto/benches/enc.rs | 2 +- askar-crypto/src/alg/aesgcm.rs | 12 +- askar-crypto/src/alg/any.rs | 348 ++++++++++++++++++++++--------- askar-crypto/src/alg/chacha20.rs | 12 +- askar-crypto/src/alg/ed25519.rs | 13 +- askar-crypto/src/alg/k256.rs | 19 +- askar-crypto/src/alg/mod.rs | 10 +- askar-crypto/src/alg/p256.rs | 20 +- askar-crypto/src/alg/x25519.rs | 21 +- askar-crypto/src/jwk/borrow.rs | 63 ------ askar-crypto/src/jwk/mod.rs | 27 ++- askar-crypto/src/jwk/parts.rs | 10 + askar-crypto/src/lib.rs | 1 - askar-crypto/src/repr.rs | 6 +- 14 files changed, 345 insertions(+), 219 deletions(-) delete mode 100644 askar-crypto/src/jwk/borrow.rs diff --git a/askar-crypto/benches/enc.rs b/askar-crypto/benches/enc.rs index 1f9590d3..8286eef2 100644 --- a/askar-crypto/benches/enc.rs +++ b/askar-crypto/benches/enc.rs @@ -33,7 +33,7 @@ fn criterion_benchmark(c: &mut Criterion) { b.iter(|| { let key = Chacha20Key::::from_secret_bytes(&key[..]).unwrap(); let mut buffer = SecretBytes::with_capacity(255); - buffer.write_slice(black_box(&message[..])).unwrap(); + buffer.buffer_write(black_box(&message[..])).unwrap(); let nonce = Chacha20Key::::random_nonce(); key.encrypt_in_place(&mut buffer, &nonce, &[]).unwrap(); }) diff --git a/askar-crypto/src/alg/aesgcm.rs b/askar-crypto/src/alg/aesgcm.rs index 9697aef5..7c08299d 100644 --- a/askar-crypto/src/alg/aesgcm.rs +++ b/askar-crypto/src/alg/aesgcm.rs @@ -5,7 +5,7 @@ use aes_gcm::{Aes128Gcm, Aes256Gcm}; use serde::{Deserialize, Serialize}; use zeroize::Zeroize; -use super::{AesTypes, KeyAlg}; +use super::{AesTypes, HasKeyAlg, KeyAlg}; use crate::{ buffer::{ArrayKey, ResizeBuffer, Writer}, encrypt::{KeyAeadInPlace, KeyAeadMeta}, @@ -18,7 +18,7 @@ use crate::{ pub static JWK_KEY_TYPE: &'static str = "oct"; -pub trait AesGcmType { +pub trait AesGcmType: 'static { type Aead: NewAead + Aead + AeadInPlace; const ALG_TYPE: AesTypes; @@ -94,9 +94,13 @@ impl PartialEq for AesGcmKey { impl Eq for AesGcmKey {} -impl KeyMeta for AesGcmKey { - const ALG: KeyAlg = KeyAlg::Aes(T::ALG_TYPE); +impl HasKeyAlg for AesGcmKey { + fn algorithm(&self) -> KeyAlg { + KeyAlg::Aes(T::ALG_TYPE) + } +} +impl KeyMeta for AesGcmKey { type KeySize = ::KeySize; } diff --git a/askar-crypto/src/alg/any.rs b/askar-crypto/src/alg/any.rs index 96f43917..c5cca0cf 100644 --- a/askar-crypto/src/alg/any.rs +++ b/askar-crypto/src/alg/any.rs @@ -1,9 +1,8 @@ -#[cfg(not(any(test, feature = "std")))] -use alloc::boxed::Box; -#[cfg(any(test, feature = "std"))] -use std::boxed::Box; - -use core::any::Any; +use alloc::{boxed::Box, sync::Arc}; +use core::{ + any::{Any, TypeId}, + fmt::Debug, +}; use super::aesgcm::{AesGcmKey, A128, A256}; use super::chacha20::{Chacha20Key, C20P, XC20P}; @@ -11,115 +10,229 @@ use super::ed25519::{self, Ed25519KeyPair}; use super::k256::{self, K256KeyPair}; use super::p256::{self, P256KeyPair}; use super::x25519::{self, X25519KeyPair}; -use super::{AesTypes, Chacha20Types, EcCurves, KeyAlg}; +use super::{AesTypes, Chacha20Types, EcCurves, HasKeyAlg, KeyAlg}; use crate::{ buffer::WriteBuffer, error::Error, jwk::{FromJwk, JwkEncoder, JwkParts, ToJwk}, - repr::KeyMeta, + repr::{KeyGen, KeyPublicBytes, KeySecretBytes}, sign::{KeySigVerify, KeySign, SignatureType}, }; -type BoxKey = Box; - #[derive(Debug)] -pub struct AnyKey { - pub(crate) alg: KeyAlg, - pub(crate) inst: BoxKey, -} +pub struct KeyT(T); + +pub type AnyKey = KeyT; impl AnyKey { - pub fn key_alg(&self) -> KeyAlg { - self.alg + pub fn algorithm(&self) -> KeyAlg { + self.0.algorithm() } - pub fn downcast(self) -> Result, Self> { - match BoxKey::downcast(self.inst) { - Ok(key) => Ok(key), - Err(inst) => Err(Self { - alg: self.alg, - inst, - }), - } + fn assume(&self) -> &K { + self.downcast_ref().expect("Error assuming key type") } - pub fn downcast_ref(&self) -> Option<&T> { - self.inst.downcast_ref() + #[inline] + pub fn downcast_ref(&self) -> Option<&K> { + self.0.as_any().downcast_ref() } - pub fn from_key(key: K) -> Self - where - K: KeyMeta + Send + 'static, - { - Self { - alg: K::ALG, - inst: Box::new(key) as BoxKey, + #[inline] + fn key_type_id(&self) -> TypeId { + self.0.as_any().type_id() + } +} + +// key instances are immutable +#[cfg(feature = "std")] +impl std::panic::UnwindSafe for AnyKey {} +#[cfg(feature = "std")] +impl std::panic::RefUnwindSafe for AnyKey {} + +pub trait AnyKeyCreate: Sized { + fn generate(alg: KeyAlg) -> Result; + + fn from_public_bytes(alg: KeyAlg, public: &[u8]) -> Result; + + fn from_secret_bytes(alg: KeyAlg, secret: &[u8]) -> Result; + + fn from_key(key: K) -> Self; +} + +impl AnyKeyCreate for Box { + fn generate(alg: KeyAlg) -> Result { + generate_any(alg) + } + + fn from_public_bytes(alg: KeyAlg, public: &[u8]) -> Result { + from_public_any(alg, public) + } + + fn from_secret_bytes(alg: KeyAlg, secret: &[u8]) -> Result { + from_secret_any(alg, secret) + } + + #[inline(always)] + fn from_key(key: K) -> Self { + Box::new(KeyT(key)) + } +} + +impl AnyKeyCreate for Arc { + fn generate(alg: KeyAlg) -> Result { + generate_any(alg) + } + + fn from_public_bytes(alg: KeyAlg, public: &[u8]) -> Result { + from_public_any(alg, public) + } + + fn from_secret_bytes(alg: KeyAlg, secret: &[u8]) -> Result { + from_secret_any(alg, secret) + } + + #[inline(always)] + fn from_key(key: K) -> Self { + Arc::new(KeyT(key)) + } +} + +#[inline] +fn generate_any(alg: KeyAlg) -> Result { + match alg { + KeyAlg::Aes(AesTypes::A128GCM) => AesGcmKey::::generate().map(R::alloc_key), + KeyAlg::Aes(AesTypes::A256GCM) => AesGcmKey::::generate().map(R::alloc_key), + KeyAlg::Chacha20(Chacha20Types::C20P) => Chacha20Key::::generate().map(R::alloc_key), + KeyAlg::Chacha20(Chacha20Types::XC20P) => { + Chacha20Key::::generate().map(R::alloc_key) + } + KeyAlg::Ed25519 => Ed25519KeyPair::generate().map(R::alloc_key), + KeyAlg::X25519 => X25519KeyPair::generate().map(R::alloc_key), + KeyAlg::EcCurve(EcCurves::Secp256k1) => K256KeyPair::generate().map(R::alloc_key), + KeyAlg::EcCurve(EcCurves::Secp256r1) => P256KeyPair::generate().map(R::alloc_key), + #[allow(unreachable_patterns)] + _ => { + return Err(err_msg!( + Unsupported, + "Unsupported algorithm for key generation" + )) } } +} - pub fn from_boxed_key(key: Box) -> Self - where - K: KeyMeta + Send + 'static, - { - Self { - alg: K::ALG, - inst: Box::new(key) as BoxKey, +#[inline] +fn from_public_any(alg: KeyAlg, public: &[u8]) -> Result { + match alg { + KeyAlg::Ed25519 => Ed25519KeyPair::from_public_bytes(public).map(R::alloc_key), + KeyAlg::X25519 => X25519KeyPair::from_public_bytes(public).map(R::alloc_key), + KeyAlg::EcCurve(EcCurves::Secp256k1) => { + K256KeyPair::from_public_bytes(public).map(R::alloc_key) + } + KeyAlg::EcCurve(EcCurves::Secp256r1) => { + P256KeyPair::from_public_bytes(public).map(R::alloc_key) + } + #[allow(unreachable_patterns)] + _ => { + return Err(err_msg!( + Unsupported, + "Unsupported algorithm for key import" + )) } } } -fn assume<'r, T: 'static>(inst: &'r (dyn Any + Send + 'static)) -> &'r T { - if let Some(t) = inst.downcast_ref::() { - t - } else { - panic!("Invalid any key state"); +#[inline] +fn from_secret_any(alg: KeyAlg, secret: &[u8]) -> Result { + match alg { + KeyAlg::Aes(AesTypes::A128GCM) => { + AesGcmKey::::from_secret_bytes(secret).map(R::alloc_key) + } + KeyAlg::Aes(AesTypes::A256GCM) => { + AesGcmKey::::from_secret_bytes(secret).map(R::alloc_key) + } + KeyAlg::Chacha20(Chacha20Types::C20P) => { + Chacha20Key::::from_secret_bytes(secret).map(R::alloc_key) + } + KeyAlg::Chacha20(Chacha20Types::XC20P) => { + Chacha20Key::::from_secret_bytes(secret).map(R::alloc_key) + } + KeyAlg::Ed25519 => Ed25519KeyPair::from_secret_bytes(secret).map(R::alloc_key), + KeyAlg::X25519 => X25519KeyPair::from_secret_bytes(secret).map(R::alloc_key), + KeyAlg::EcCurve(EcCurves::Secp256k1) => { + K256KeyPair::from_secret_bytes(secret).map(R::alloc_key) + } + KeyAlg::EcCurve(EcCurves::Secp256r1) => { + P256KeyPair::from_secret_bytes(secret).map(R::alloc_key) + } + #[allow(unreachable_patterns)] + _ => { + return Err(err_msg!( + Unsupported, + "Unsupported algorithm for key import" + )) + } } } -impl FromJwk for AnyKey { +impl FromJwk for Box { fn from_jwk_parts(jwk: JwkParts<'_>) -> Result { - let (alg, inst) = match (jwk.kty, jwk.crv.as_ref()) { - ("EC", c) if c == k256::JWK_CURVE => ( - KeyAlg::EcCurve(EcCurves::Secp256k1), - Box::new(K256KeyPair::from_jwk_parts(jwk)?) as BoxKey, - ), - ("EC", c) if c == p256::JWK_CURVE => ( - KeyAlg::EcCurve(EcCurves::Secp256r1), - Box::new(P256KeyPair::from_jwk_parts(jwk)?) as BoxKey, - ), - ("OKP", c) if c == ed25519::JWK_CURVE => ( - KeyAlg::Ed25519, - Box::new(Ed25519KeyPair::from_jwk_parts(jwk)?) as BoxKey, - ), - ("OKP", c) if c == x25519::JWK_CURVE => ( - KeyAlg::X25519, - Box::new(X25519KeyPair::from_jwk_parts(jwk)?) as BoxKey, - ), - // "oct" - _ => return Err(err_msg!(Unsupported, "Unsupported JWK for key import")), - }; - Ok(Self { alg, inst }) + from_jwk_any(jwk) } } -impl ToJwk for AnyKey { - fn to_jwk_encoder(&self, enc: &mut JwkEncoder<'_>) -> Result<(), Error> { - let key: &dyn ToJwk = match self.alg { - KeyAlg::Aes(AesTypes::A128GCM) => assume::>(&self.inst), - KeyAlg::Aes(AesTypes::A256GCM) => assume::>(&self.inst), - KeyAlg::Chacha20(Chacha20Types::C20P) => assume::>(&self.inst), - KeyAlg::Chacha20(Chacha20Types::XC20P) => assume::>(&self.inst), - KeyAlg::Ed25519 => assume::(&self.inst), - KeyAlg::X25519 => assume::(&self.inst), - KeyAlg::EcCurve(EcCurves::Secp256k1) => assume::(&self.inst), - KeyAlg::EcCurve(EcCurves::Secp256r1) => assume::(&self.inst), +impl FromJwk for Arc { + fn from_jwk_parts(jwk: JwkParts<'_>) -> Result { + from_jwk_any(jwk) + } +} + +#[inline] +fn from_jwk_any(jwk: JwkParts<'_>) -> Result { + match (jwk.kty, jwk.crv.as_ref()) { + ("OKP", c) if c == ed25519::JWK_CURVE => { + Ed25519KeyPair::from_jwk_parts(jwk).map(R::alloc_key) + } + ("OKP", c) if c == x25519::JWK_CURVE => { + X25519KeyPair::from_jwk_parts(jwk).map(R::alloc_key) + } + ("EC", c) if c == k256::JWK_CURVE => K256KeyPair::from_jwk_parts(jwk).map(R::alloc_key), + ("EC", c) if c == p256::JWK_CURVE => P256KeyPair::from_jwk_parts(jwk).map(R::alloc_key), + // "oct" + _ => Err(err_msg!(Unsupported, "Unsupported JWK for key import")), + } +} + +macro_rules! match_key_types { + ($slf:expr, $( $t:ty ),+; $errmsg:expr) => { + match $slf.key_type_id() { + $( + t if t == TypeId::of::<$t>() => $slf.assume::<$t>(), + )+ #[allow(unreachable_patterns)] _ => { return Err(err_msg!( Unsupported, - "JWK export is not supported for this key type" + $errmsg )) } + } + }; +} + +impl ToJwk for AnyKey { + fn to_jwk_encoder(&self, enc: &mut JwkEncoder<'_>) -> Result<(), Error> { + let key: &dyn ToJwk = match_key_types! { + self, + AesGcmKey, + AesGcmKey, + Chacha20Key, + Chacha20Key, + Ed25519KeyPair, + X25519KeyPair, + K256KeyPair, + P256KeyPair; + "JWK export is not supported for this key type" }; key.to_jwk_encoder(enc) } @@ -132,16 +245,12 @@ impl KeySign for AnyKey { sig_type: Option, out: &mut dyn WriteBuffer, ) -> Result<(), Error> { - let key: &dyn KeySign = match self.alg { - KeyAlg::Ed25519 => assume::(&self.inst), - KeyAlg::EcCurve(EcCurves::Secp256k1) => assume::(&self.inst), - KeyAlg::EcCurve(EcCurves::Secp256r1) => assume::(&self.inst), - _ => { - return Err(err_msg!( - Unsupported, - "Signing is not supported for this key type" - )) - } + let key: &dyn KeySign = match_key_types! { + self, + Ed25519KeyPair, + K256KeyPair, + P256KeyPair; + "Signing is not supported for this key type" }; key.write_signature(message, sig_type, out) } @@ -154,17 +263,58 @@ impl KeySigVerify for AnyKey { signature: &[u8], sig_type: Option, ) -> Result { - let key: &dyn KeySigVerify = match self.alg { - KeyAlg::Ed25519 => assume::(&self.inst), - KeyAlg::EcCurve(EcCurves::Secp256k1) => assume::(&self.inst), - KeyAlg::EcCurve(EcCurves::Secp256r1) => assume::(&self.inst), - _ => { - return Err(err_msg!( - Unsupported, - "Signature verification not supported for this key type" - )) - } + let key: &dyn KeySigVerify = match_key_types! { + self, + Ed25519KeyPair, + K256KeyPair, + P256KeyPair; + "Signature verification is not supported for this key type" }; key.verify_signature(message, signature, sig_type) } } + +// may want to implement in-place initialization to avoid copies +trait AllocKey { + fn alloc_key(key: K) -> Self; +} + +impl AllocKey for Arc { + #[inline(always)] + fn alloc_key(key: K) -> Self { + Self::from_key(key) + } +} + +impl AllocKey for Box { + #[inline(always)] + fn alloc_key(key: K) -> Self { + Self::from_key(key) + } +} + +pub trait KeyAsAny: HasKeyAlg + 'static { + fn as_any(&self) -> &dyn Any; +} + +// implement for all concrete key types +impl KeyAsAny for K { + fn as_any(&self) -> &dyn Any { + self + } +} + +#[cfg(test)] +mod tests { + use super::*; + + // FIXME - add a custom key type to test the wrapper + + #[test] + fn ed25519_as_any() { + let key = Box::::generate(KeyAlg::Ed25519).unwrap(); + assert_eq!(key.algorithm(), KeyAlg::Ed25519); + assert_eq!(key.key_type_id(), TypeId::of::()); + let _ = key.to_jwk_public().unwrap(); + } +} diff --git a/askar-crypto/src/alg/chacha20.rs b/askar-crypto/src/alg/chacha20.rs index dab3e94a..202eb4d7 100644 --- a/askar-crypto/src/alg/chacha20.rs +++ b/askar-crypto/src/alg/chacha20.rs @@ -5,7 +5,7 @@ use chacha20poly1305::{ChaCha20Poly1305, XChaCha20Poly1305}; use serde::{Deserialize, Serialize}; use zeroize::Zeroize; -use super::{Chacha20Types, KeyAlg}; +use super::{Chacha20Types, HasKeyAlg, KeyAlg}; use crate::{ buffer::{ArrayKey, ResizeBuffer, Writer}, encrypt::{KeyAeadInPlace, KeyAeadMeta}, @@ -19,7 +19,7 @@ use crate::{ pub static JWK_KEY_TYPE: &'static str = "oct"; -pub trait Chacha20Type { +pub trait Chacha20Type: 'static { type Aead: NewAead + Aead + AeadInPlace; const ALG_TYPE: Chacha20Types; @@ -103,9 +103,13 @@ impl PartialEq for Chacha20Key { impl Eq for Chacha20Key {} -impl KeyMeta for Chacha20Key { - const ALG: KeyAlg = KeyAlg::Chacha20(T::ALG_TYPE); +impl HasKeyAlg for Chacha20Key { + fn algorithm(&self) -> KeyAlg { + KeyAlg::Chacha20(T::ALG_TYPE) + } +} +impl KeyMeta for Chacha20Key { type KeySize = ::KeySize; } diff --git a/askar-crypto/src/alg/ed25519.rs b/askar-crypto/src/alg/ed25519.rs index 87e21d2f..5ede41bc 100644 --- a/askar-crypto/src/alg/ed25519.rs +++ b/askar-crypto/src/alg/ed25519.rs @@ -8,7 +8,7 @@ use ed25519_dalek::{ExpandedSecretKey, PublicKey, SecretKey, Signature}; use sha2::{self, Digest}; use x25519_dalek::{PublicKey as XPublicKey, StaticSecret as XSecretKey}; -use super::{x25519::X25519KeyPair, KeyAlg}; +use super::{x25519::X25519KeyPair, HasKeyAlg, KeyAlg}; use crate::{ buffer::{ArrayKey, WriteBuffer}, error::Error, @@ -124,8 +124,13 @@ impl KeyGen for Ed25519KeyPair { } } +impl HasKeyAlg for Ed25519KeyPair { + fn algorithm(&self) -> KeyAlg { + KeyAlg::Ed25519 + } +} + impl KeyMeta for Ed25519KeyPair { - const ALG: KeyAlg = KeyAlg::Ed25519; type KeySize = U32; } @@ -333,7 +338,7 @@ mod tests { let jwk = kp .to_jwk_public() .expect("Error converting public key to JWK"); - let jwk = jwk.to_parts().expect("Error parsing JWK output"); + let jwk = JwkParts::from_str(&jwk).expect("Error parsing JWK output"); assert_eq!(jwk.kty, JWK_KEY_TYPE); assert_eq!(jwk.crv, JWK_CURVE); assert_eq!(jwk.x, "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"); @@ -343,7 +348,7 @@ mod tests { let jwk = kp .to_jwk_secret() .expect("Error converting private key to JWK"); - let jwk = jwk.to_parts().expect("Error parsing JWK output"); + let jwk = JwkParts::from_slice(&jwk).expect("Error parsing JWK output"); assert_eq!(jwk.kty, "OKP"); assert_eq!(jwk.crv, JWK_CURVE); assert_eq!(jwk.x, test_pub_b64); diff --git a/askar-crypto/src/alg/k256.rs b/askar-crypto/src/alg/k256.rs index f35ff7ee..87aaad3a 100644 --- a/askar-crypto/src/alg/k256.rs +++ b/askar-crypto/src/alg/k256.rs @@ -9,7 +9,7 @@ use k256::{ EncodedPoint, PublicKey, SecretKey, }; -use super::{EcCurves, KeyAlg}; +use super::{EcCurves, HasKeyAlg, KeyAlg}; use crate::{ buffer::{ArrayKey, WriteBuffer}, error::Error, @@ -73,17 +73,22 @@ impl K256KeyPair { } } -impl KeyGen for K256KeyPair { - fn generate() -> Result { - Ok(Self::from_secret_key(with_rng(|r| SecretKey::random(r)))) +impl HasKeyAlg for K256KeyPair { + fn algorithm(&self) -> KeyAlg { + KeyAlg::EcCurve(EcCurves::Secp256k1) } } impl KeyMeta for K256KeyPair { - const ALG: KeyAlg = KeyAlg::EcCurve(EcCurves::Secp256k1); type KeySize = U32; } +impl KeyGen for K256KeyPair { + fn generate() -> Result { + Ok(Self::from_secret_key(with_rng(|r| SecretKey::random(r)))) + } +} + impl KeySecretBytes for K256KeyPair { fn from_secret_bytes(key: &[u8]) -> Result { Ok(Self::from_secret_key( @@ -290,7 +295,7 @@ mod tests { let sk = K256KeyPair::from_secret_bytes(&test_pvt).expect("Error creating signing key"); let jwk = sk.to_jwk_public().expect("Error converting key to JWK"); - let jwk = jwk.to_parts().expect("Error parsing JWK"); + let jwk = JwkParts::from_str(&jwk).expect("Error parsing JWK"); assert_eq!(jwk.kty, "EC"); assert_eq!(jwk.crv, JWK_CURVE); assert_eq!(jwk.x, test_pub_b64.0); @@ -300,7 +305,7 @@ mod tests { assert_eq!(sk.to_public_bytes(), pk_load.to_public_bytes()); let jwk = sk.to_jwk_secret().expect("Error converting key to JWK"); - let jwk = jwk.to_parts().expect("Error parsing JWK"); + let jwk = JwkParts::from_slice(&jwk).expect("Error parsing JWK"); assert_eq!(jwk.kty, "EC"); assert_eq!(jwk.crv, JWK_CURVE); assert_eq!(jwk.x, test_pub_b64.0); diff --git a/askar-crypto/src/alg/mod.rs b/askar-crypto/src/alg/mod.rs index 396407f0..492ab4ed 100644 --- a/askar-crypto/src/alg/mod.rs +++ b/askar-crypto/src/alg/mod.rs @@ -1,5 +1,5 @@ use core::{ - fmt::{self, Display, Formatter}, + fmt::{self, Debug, Display, Formatter}, str::FromStr, }; @@ -10,7 +10,7 @@ use crate::error::Error; #[cfg(any(test, feature = "any_key"))] mod any; #[cfg(any(test, feature = "any_key"))] -pub use any::AnyKey; +pub use any::{AnyKey, AnyKeyCreate}; // pub mod bls; @@ -44,7 +44,7 @@ pub enum KeyAlg { impl KeyAlg { /// Get a reference to a string representing the `KeyAlg` - pub fn as_str(&self) -> &str { + pub fn as_str(&self) -> &'static str { match self { Self::Aes(AesTypes::A128GCM) => "a128gcm", Self::Aes(AesTypes::A256GCM) => "a256gcm", @@ -126,3 +126,7 @@ pub enum EcCurves { /// Koblitz 256 curve Secp256k1, } + +pub trait HasKeyAlg: Debug { + fn algorithm(&self) -> KeyAlg; +} diff --git a/askar-crypto/src/alg/p256.rs b/askar-crypto/src/alg/p256.rs index 073d06df..50cf8cc7 100644 --- a/askar-crypto/src/alg/p256.rs +++ b/askar-crypto/src/alg/p256.rs @@ -9,7 +9,7 @@ use p256::{ EncodedPoint, PublicKey, SecretKey, }; -use super::{EcCurves, KeyAlg}; +use super::{EcCurves, HasKeyAlg, KeyAlg}; use crate::{ buffer::{ArrayKey, WriteBuffer}, error::Error, @@ -73,16 +73,22 @@ impl P256KeyPair { } } -impl KeyGen for P256KeyPair { - fn generate() -> Result { - Ok(Self::from_secret_key(with_rng(|r| SecretKey::random(r)))) +impl HasKeyAlg for P256KeyPair { + fn algorithm(&self) -> KeyAlg { + KeyAlg::EcCurve(EcCurves::Secp256r1) } } + impl KeyMeta for P256KeyPair { - const ALG: KeyAlg = KeyAlg::EcCurve(EcCurves::Secp256r1); type KeySize = U32; } +impl KeyGen for P256KeyPair { + fn generate() -> Result { + Ok(Self::from_secret_key(with_rng(|r| SecretKey::random(r)))) + } +} + impl KeySecretBytes for P256KeyPair { fn from_secret_bytes(key: &[u8]) -> Result { Ok(Self::from_secret_key( @@ -287,7 +293,7 @@ mod tests { let sk = P256KeyPair::from_secret_bytes(&test_pvt).expect("Error creating signing key"); let jwk = sk.to_jwk_public().expect("Error converting key to JWK"); - let jwk = jwk.to_parts().expect("Error parsing JWK"); + let jwk = JwkParts::from_str(&jwk).expect("Error parsing JWK"); assert_eq!(jwk.kty, "EC"); assert_eq!(jwk.crv, JWK_CURVE); assert_eq!(jwk.x, test_pub_b64.0); @@ -297,7 +303,7 @@ mod tests { assert_eq!(sk.to_public_bytes(), pk_load.to_public_bytes()); let jwk = sk.to_jwk_secret().expect("Error converting key to JWK"); - let jwk = jwk.to_parts().expect("Error parsing JWK"); + let jwk = JwkParts::from_slice(&jwk).expect("Error parsing JWK"); assert_eq!(jwk.kty, "EC"); assert_eq!(jwk.crv, JWK_CURVE); assert_eq!(jwk.x, test_pub_b64.0); diff --git a/askar-crypto/src/alg/x25519.rs b/askar-crypto/src/alg/x25519.rs index b02e7d6f..93e6c691 100644 --- a/askar-crypto/src/alg/x25519.rs +++ b/askar-crypto/src/alg/x25519.rs @@ -6,7 +6,7 @@ use core::{ use x25519_dalek::{PublicKey, StaticSecret as SecretKey}; use zeroize::Zeroizing; -use super::KeyAlg; +use super::{HasKeyAlg, KeyAlg}; use crate::{ buffer::{ArrayKey, WriteBuffer}, error::Error, @@ -66,6 +66,16 @@ impl Debug for X25519KeyPair { } } +impl HasKeyAlg for X25519KeyPair { + fn algorithm(&self) -> KeyAlg { + KeyAlg::X25519 + } +} + +impl KeyMeta for X25519KeyPair { + type KeySize = U32; +} + impl KeyGen for X25519KeyPair { fn generate() -> Result { let mut sk = ArrayKey::::default(); @@ -78,11 +88,6 @@ impl KeyGen for X25519KeyPair { } } -impl KeyMeta for X25519KeyPair { - const ALG: KeyAlg = KeyAlg::X25519; - type KeySize = U32; -} - impl KeySecretBytes for X25519KeyPair { fn from_secret_bytes(key: &[u8]) -> Result { if key.len() != SECRET_KEY_LENGTH { @@ -248,7 +253,7 @@ mod tests { let jwk = kp .to_jwk_public() .expect("Error converting public key to JWK"); - let jwk = jwk.to_parts().expect("Error parsing JWK output"); + let jwk = JwkParts::from_str(&jwk).expect("Error parsing JWK output"); assert_eq!(jwk.kty, "OKP"); assert_eq!(jwk.crv, JWK_CURVE); assert_eq!(jwk.x, "tGskN_ae61DP4DLY31_fjkbvnKqf-ze7kA6Cj2vyQxU"); @@ -259,7 +264,7 @@ mod tests { let jwk = kp .to_jwk_secret() .expect("Error converting private key to JWK"); - let jwk = jwk.to_parts().expect("Error parsing JWK output"); + let jwk = JwkParts::from_slice(&jwk).expect("Error parsing JWK output"); assert_eq!(jwk.kty, JWK_KEY_TYPE); assert_eq!(jwk.crv, JWK_CURVE); assert_eq!(jwk.x, "tGskN_ae61DP4DLY31_fjkbvnKqf-ze7kA6Cj2vyQxU"); diff --git a/askar-crypto/src/jwk/borrow.rs b/askar-crypto/src/jwk/borrow.rs deleted file mode 100644 index ffa3ca8f..00000000 --- a/askar-crypto/src/jwk/borrow.rs +++ /dev/null @@ -1,63 +0,0 @@ -use alloc::{borrow::Cow, string::String}; - -use zeroize::Zeroize; - -use super::parts::JwkParts; -use crate::error::Error; - -#[derive(Clone, Debug)] -pub enum Jwk<'a> { - Encoded(Cow<'a, str>), - Parts(JwkParts<'a>), -} - -impl Jwk<'_> { - pub fn to_parts(&self) -> Result, Error> { - match self { - Self::Encoded(s) => Ok(serde_json::from_str(s.as_ref()) - .map_err(err_map!(InvalidData, "Error deserializing JWK"))?), - Self::Parts(p) => Ok(*p), - } - } - - pub fn as_opt_str(&self) -> Option<&str> { - match self { - Self::Encoded(s) => Some(s.as_ref()), - Self::Parts(_) => None, - } - } -} - -impl<'a> From> for Jwk<'a> { - fn from(jwk: Cow<'a, str>) -> Self { - Jwk::Encoded(jwk) - } -} - -impl<'a> From<&'a str> for Jwk<'a> { - fn from(jwk: &'a str) -> Self { - Jwk::Encoded(Cow::Borrowed(jwk)) - } -} - -impl<'a> From for Jwk<'a> { - fn from(jwk: String) -> Self { - Jwk::Encoded(Cow::Owned(jwk)) - } -} - -impl<'a> From> for Jwk<'a> { - fn from(jwk: JwkParts<'a>) -> Self { - Jwk::Parts(jwk) - } -} - -impl Zeroize for Jwk<'_> { - fn zeroize(&mut self) { - match self { - Self::Encoded(Cow::Owned(s)) => s.zeroize(), - Self::Encoded(_) => (), - Self::Parts(..) => (), - } - } -} diff --git a/askar-crypto/src/jwk/mod.rs b/askar-crypto/src/jwk/mod.rs index dc16e334..df263ac9 100644 --- a/askar-crypto/src/jwk/mod.rs +++ b/askar-crypto/src/jwk/mod.rs @@ -1,18 +1,15 @@ #[cfg(feature = "alloc")] -use alloc::{borrow::Cow, string::String, vec::Vec}; +use alloc::{string::String, vec::Vec}; use sha2::Sha256; +#[cfg(feature = "alloc")] +use crate::buffer::SecretBytes; use crate::{ buffer::{HashBuffer, ResizeBuffer}, error::Error, }; -#[cfg(feature = "alloc")] -mod borrow; -#[cfg(feature = "alloc")] -pub use self::borrow::Jwk; - mod encode; pub use self::encode::{JwkEncoder, JwkEncoderMode}; @@ -33,21 +30,21 @@ pub trait ToJwk { } #[cfg(feature = "alloc")] - fn to_jwk_public(&self) -> Result, Error> { + fn to_jwk_public(&self) -> Result { let mut v = Vec::with_capacity(128); let mut buf = JwkEncoder::new(&mut v, JwkEncoderMode::PublicKey)?; self.to_jwk_encoder(&mut buf)?; buf.finalize()?; - Ok(Jwk::Encoded(Cow::Owned(String::from_utf8(v).unwrap()))) + Ok(String::from_utf8(v).unwrap()) } #[cfg(feature = "alloc")] - fn to_jwk_secret(&self) -> Result, Error> { - let mut v = Vec::with_capacity(128); + fn to_jwk_secret(&self) -> Result { + let mut v = SecretBytes::with_capacity(128); let mut buf = JwkEncoder::new(&mut v, JwkEncoderMode::SecretKey)?; self.to_jwk_encoder(&mut buf)?; buf.finalize()?; - Ok(Jwk::Encoded(Cow::Owned(String::from_utf8(v).unwrap()))) + Ok(v) } } @@ -66,9 +63,11 @@ pub fn jwk_thumbprint_buffer( pub trait FromJwk: Sized { fn from_jwk(jwk: &str) -> Result { - let parts = - serde_json::from_str(jwk).map_err(err_map!(InvalidData, "Error parsing JWK"))?; - Self::from_jwk_parts(parts) + JwkParts::from_str(jwk).and_then(Self::from_jwk_parts) + } + + fn from_jwk_slice(jwk: &[u8]) -> Result { + JwkParts::from_slice(jwk).and_then(Self::from_jwk_parts) } fn from_jwk_parts(jwk: JwkParts<'_>) -> Result; diff --git a/askar-crypto/src/jwk/parts.rs b/askar-crypto/src/jwk/parts.rs index a5ee676c..bb0da0be 100644 --- a/askar-crypto/src/jwk/parts.rs +++ b/askar-crypto/src/jwk/parts.rs @@ -28,6 +28,16 @@ pub struct JwkParts<'a> { pub key_ops: Option, } +impl<'de> JwkParts<'de> { + pub fn from_str(jwk: &'de str) -> Result { + serde_json::from_str(jwk).map_err(err_map!(InvalidData, "Error parsing JWK")) + } + + pub fn from_slice(jwk: &'de [u8]) -> Result { + serde_json::from_slice(jwk).map_err(err_map!(InvalidData, "Error parsing JWK")) + } +} + #[derive(Copy, Clone, Default, PartialEq, Eq)] #[repr(transparent)] pub struct OptAttr<'a>(Option<&'a str>); diff --git a/askar-crypto/src/lib.rs b/askar-crypto/src/lib.rs index c179369b..12e3cc1a 100644 --- a/askar-crypto/src/lib.rs +++ b/askar-crypto/src/lib.rs @@ -21,7 +21,6 @@ pub use self::error::{Error, ErrorKind}; pub use aead::generic_array; pub mod alg; -pub use self::alg::KeyAlg; pub mod buffer; diff --git a/askar-crypto/src/repr.rs b/askar-crypto/src/repr.rs index 7f69189f..ec49ccad 100644 --- a/askar-crypto/src/repr.rs +++ b/askar-crypto/src/repr.rs @@ -1,6 +1,6 @@ #[cfg(feature = "alloc")] use crate::buffer::SecretBytes; -use crate::{alg::KeyAlg, buffer::WriteBuffer, error::Error, generic_array::ArrayLength}; +use crate::{buffer::WriteBuffer, error::Error, generic_array::ArrayLength}; /// Generate a new random key. pub trait KeyGen: Sized { @@ -10,7 +10,7 @@ pub trait KeyGen: Sized { /// Allows a key to be created uninitialized and populated later, /// for instance when nested inside another struct. pub trait KeyGenInPlace { - fn generate_in_place(&mut self) -> Result<(), Error>; + unsafe fn generate_in_place(this: *mut Self) -> Result<(), Error>; } /// Convert between key instance and key secret bytes. @@ -87,8 +87,6 @@ pub trait KeypairBytes { /// For concrete secret key types pub trait KeyMeta { - const ALG: KeyAlg; - type KeySize: ArrayLength; } From e76f09a0cca34e4cb42670a14f205efd7450371a Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Sun, 18 Apr 2021 23:42:52 -0700 Subject: [PATCH 047/116] update key representation; start adding FFI key operations Signed-off-by: Andrew Whitehead --- src/backend/db_utils.rs | 12 +- src/backend/postgres/mod.rs | 15 +- src/backend/sqlite/mod.rs | 2 +- src/ffi/handle.rs | 101 ++++++++ src/ffi/key.rs | 119 +++++++++ src/ffi/mod.rs | 41 +--- src/ffi/store.rs | 306 +++++++++++------------- src/key.rs | 198 +++++++++++++++ src/lib.rs | 3 +- src/protect/profile_key.rs | 16 +- src/protect/store_key.rs | 2 +- src/storage/entry.rs | 43 +++- src/storage/key.rs | 146 ----------- src/storage/mod.rs | 2 - src/storage/types.rs | 204 ++++++++++------ tests/utils/mod.rs | 76 +++--- wrappers/python/aries_askar/bindings.py | 58 ++++- wrappers/python/demo/perf.py | 35 +-- wrappers/python/demo/test.py | 10 +- 19 files changed, 864 insertions(+), 525 deletions(-) create mode 100644 src/ffi/handle.rs create mode 100644 src/ffi/key.rs create mode 100644 src/key.rs delete mode 100644 src/storage/key.rs diff --git a/src/backend/db_utils.rs b/src/backend/db_utils.rs index 77e045e0..b1325c55 100644 --- a/src/backend/db_utils.rs +++ b/src/backend/db_utils.rs @@ -338,7 +338,7 @@ where pub struct EncScanEntry { pub name: Vec, pub value: Vec, - pub tags: Option>, + pub tags: Vec, } pub struct QueryParams<'q, DB: Database> { @@ -517,13 +517,9 @@ pub fn decrypt_scan_entry( ) -> Result { let name = key.decrypt_entry_name(enc_entry.name)?; let value = key.decrypt_entry_value(category.as_bytes(), name.as_bytes(), enc_entry.value)?; - let tags = if let Some(enc_tags) = enc_entry.tags { - Some(key.decrypt_entry_tags( - decode_tags(enc_tags).map_err(|_| err_msg!(Unexpected, "Error decoding tags"))?, - )?) - } else { - None - }; + let tags = key.decrypt_entry_tags( + decode_tags(enc_entry.tags).map_err(|_| err_msg!(Unexpected, "Error decoding tags"))?, + )?; Ok(Entry::new(category.to_string(), name, value, tags)) } diff --git a/src/backend/postgres/mod.rs b/src/backend/postgres/mod.rs index d5a26fbf..9d0e62c6 100644 --- a/src/backend/postgres/mod.rs +++ b/src/backend/postgres/mod.rs @@ -331,14 +331,12 @@ impl QueryBackend for DbSession { let (category, name, value, tags) = unblock(move || { let value = key.decrypt_entry_value(category.as_ref(), name.as_ref(), value)?; let tags = if let Some(enc_tags) = tags { - Some( - key.decrypt_entry_tags( - decode_tags(enc_tags) - .map_err(|_| err_msg!(Unexpected, "Error decoding tags"))?, - )?, - ) + key.decrypt_entry_tags( + decode_tags(enc_tags) + .map_err(|_| err_msg!(Unexpected, "Error decoding tags"))?, + )? } else { - None + Vec::new() }; Result::<_, Error>::Ok((category, name, value, tags)) }) @@ -694,8 +692,9 @@ fn perform_scan<'q>( let mut acquired = acquire_session(&mut *active).await?; let mut rows = sqlx::query_with(query.as_str(), params).fetch(acquired.connection_mut()); while let Some(row) = rows.try_next().await? { + let tags = row.try_get::, _>(3)?.map(String::into_bytes).unwrap_or_default(); batch.push(EncScanEntry { - name: row.try_get(1)?, value: row.try_get(2)?, tags: row.try_get::, _>(3)?.map(String::into_bytes) + name: row.try_get(1)?, value: row.try_get(2)?, tags }); if batch.len() == PAGE_SIZE { yield batch.split_off(0); diff --git a/src/backend/sqlite/mod.rs b/src/backend/sqlite/mod.rs index 2dcebe49..46217b73 100644 --- a/src/backend/sqlite/mod.rs +++ b/src/backend/sqlite/mod.rs @@ -307,7 +307,7 @@ impl QueryBackend for DbSession { let value = key.decrypt_entry_value(category.as_ref(), name.as_ref(), value)?; let enc_tags = decode_tags(tags) .map_err(|_| err_msg!(Unexpected, "Error decoding entry tags"))?; - let tags = Some(key.decrypt_entry_tags(enc_tags)?); + let tags = key.decrypt_entry_tags(enc_tags)?; Result::<_, Error>::Ok((category, name, value, tags)) }) .await?; diff --git a/src/ffi/handle.rs b/src/ffi/handle.rs new file mode 100644 index 00000000..a0421ba7 --- /dev/null +++ b/src/ffi/handle.rs @@ -0,0 +1,101 @@ +use std::{marker::PhantomData, mem, sync::Arc}; + +use crate::error::Error; + +#[repr(transparent)] +pub struct ArcHandle(usize, PhantomData); + +impl ArcHandle { + pub fn invalid() -> Self { + Self(0, PhantomData) + } + + pub fn create(value: T) -> Self { + let results = Arc::into_raw(Arc::new(value)); + Self(results as usize, PhantomData) + } + + pub fn load(&self) -> Result, Error> { + self.validate()?; + let slf = unsafe { Arc::from_raw(self.0 as *const T) }; + let copy = slf.clone(); + mem::forget(slf); // Arc::increment_strong_count(..) in 1.51 + Ok(copy) + } + + pub fn remove(&self) { + if self.0 != 0 { + unsafe { + // Drop the initial reference. There could be others outstanding. + Arc::from_raw(self.0 as *const T); + } + } + } + + #[inline] + pub fn validate(&self) -> Result<(), Error> { + if self.0 == 0 { + Err(err_msg!("Invalid handle")) + } else { + Ok(()) + } + } +} + +impl std::fmt::Display for ArcHandle { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Handle({:p})", self.0 as *const T) + } +} + +/// Derive a new handle type having an atomically increasing sequence number +#[macro_export] +macro_rules! new_sequence_handle (($newtype:ident, $counter:ident) => ( + static $counter: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0); + + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] + #[repr(transparent)] + pub struct $newtype(pub usize); + + impl $newtype { + #[allow(dead_code)] + pub fn invalid() -> $newtype { + $newtype(0) + } + + #[allow(dead_code)] + pub fn next() -> $newtype { + $newtype($counter.fetch_add(1, std::sync::atomic::Ordering::SeqCst) + 1) + } + } + + impl std::fmt::Display for $newtype { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}({})", stringify!($newtype), self.0) + } + } + + impl std::ops::Deref for $newtype { + type Target = usize; + fn deref(&self) -> &usize { + &self.0 + } + } + + impl PartialEq for $newtype { + fn eq(&self, other: &usize) -> bool { + self.0 == *other + } + } +)); + +#[cfg(test)] +mod tests { + new_sequence_handle!(TestHandle, TEST_HANDLE_CTR); + + #[test] + fn test_handle_seq() { + assert_eq!(TestHandle::next(), 1); + assert_eq!(TestHandle::next(), 2); + } +} diff --git a/src/ffi/key.rs b/src/ffi/key.rs new file mode 100644 index 00000000..7e3b7d00 --- /dev/null +++ b/src/ffi/key.rs @@ -0,0 +1,119 @@ +use std::os::raw::c_char; + +use ffi_support::{rust_string_to_c, ByteBuffer, FfiStr}; + +use super::{handle::ArcHandle, ErrorCode}; +use crate::key::LocalKey; + +pub type LocalKeyHandle = ArcHandle; + +#[no_mangle] +pub extern "C" fn askar_key_generate( + alg: FfiStr<'_>, + ephemeral: i8, + out: *mut LocalKeyHandle, +) -> ErrorCode { + catch_err! { + trace!("Generate key: {}", alg.as_str()); + check_useful_c_ptr!(out); + let key = LocalKey::generate(alg.as_str(), ephemeral != 0)?; + let handle = LocalKeyHandle::create(key); + unsafe { *out = handle }; + Ok(ErrorCode::Success) + } +} + +#[no_mangle] +pub extern "C" fn askar_key_from_jwk(jwk: FfiStr<'_>, out: *mut LocalKeyHandle) -> ErrorCode { + catch_err! { + trace!("Load key from JWK"); + check_useful_c_ptr!(out); + let key = LocalKey::from_jwk(jwk.as_str())?; + let handle = LocalKeyHandle::create(key); + unsafe { *out = handle }; + Ok(ErrorCode::Success) + } +} + +#[no_mangle] +pub extern "C" fn askar_key_from_public_bytes( + alg: FfiStr<'_>, + public: ByteBuffer, + out: *mut LocalKeyHandle, +) -> ErrorCode { + catch_err! { + trace!("Load key from public: {}", alg.as_str()); + check_useful_c_ptr!(out); + let key = LocalKey::from_public_bytes(alg.as_str(), public.as_slice())?; + let handle = LocalKeyHandle::create(key); + unsafe { *out = handle }; + Ok(ErrorCode::Success) + } +} + +#[no_mangle] +pub extern "C" fn askar_key_from_secret_bytes( + alg: FfiStr<'_>, + secret: ByteBuffer, + out: *mut LocalKeyHandle, +) -> ErrorCode { + catch_err! { + trace!("Load key from secret: {}", alg.as_str()); + check_useful_c_ptr!(out); + let key = LocalKey::from_secret_bytes(alg.as_str(), secret.as_slice())?; + let handle = LocalKeyHandle::create(key); + unsafe { *out = handle }; + Ok(ErrorCode::Success) + } +} + +#[no_mangle] +pub extern "C" fn askar_key_free(handle: LocalKeyHandle) { + handle.remove(); +} + +#[no_mangle] +pub extern "C" fn askar_key_get_algorithm( + handle: LocalKeyHandle, + out: *mut *const c_char, +) -> ErrorCode { + catch_err! { + trace!("Get key algorithm: {}", handle); + check_useful_c_ptr!(out); + let key = handle.load()?; + unsafe { *out = rust_string_to_c(key.algorithm()) }; + Ok(ErrorCode::Success) + } +} + +#[no_mangle] +pub extern "C" fn askar_key_get_jwk_public( + handle: LocalKeyHandle, + out: *mut *const c_char, +) -> ErrorCode { + catch_err! { + trace!("Get key JWK public: {}", handle); + handle.validate()?; + check_useful_c_ptr!(out); + let key = handle.load()?; + let jwk = key.to_jwk_public()?; + unsafe { *out = rust_string_to_c(jwk) }; + Ok(ErrorCode::Success) + } +} + +// #[no_mangle] +// pub extern "C" fn askar_key_get_jwk_secret( +// handle: LocalKeyHandle, +// out: *mut FfiSecret, +// ) -> ErrorCode { +// catch_err! { +// trace!("Get key JWK secret: {}", handle); +// handle.validate()?; +// check_useful_c_ptr!(out); +// let key = handle.load()?; +// let jwk = key.to_jwk_secret()?; +// unsafe { *out = FfiSecret::from(jwk) }; +// Ok(ErrorCode::Success) +// } +// } diff --git a/src/ffi/mod.rs b/src/ffi/mod.rs index 48461c77..571e5f25 100644 --- a/src/ffi/mod.rs +++ b/src/ffi/mod.rs @@ -1,9 +1,8 @@ use std::marker::PhantomData; use std::os::raw::c_char; -use std::ptr; use ffi_support::{rust_string_to_c, ByteBuffer}; -use zeroize::{Zeroize, Zeroizing}; +use zeroize::Zeroize; #[cfg(feature = "jemalloc")] #[global_allocator] @@ -11,19 +10,22 @@ static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc; pub static LIB_VERSION: &str = env!("CARGO_PKG_VERSION"); +#[macro_use] +mod handle; + #[macro_use] mod macros; mod error; +mod key; + mod log; mod store; -use self::error::{set_last_error, ErrorCode}; +use self::error::ErrorCode; use crate::error::Error; -use crate::future::{spawn_ok, unblock}; -use crate::protect::generate_raw_store_key; pub type CallbackId = i64; @@ -62,35 +64,6 @@ impl)> Drop for EnsureCallback { } } -#[no_mangle] -pub extern "C" fn askar_generate_raw_key( - seed: ByteBuffer, - cb: Option, - cb_id: CallbackId, -) -> ErrorCode { - catch_err! { - trace!("Create raw key"); - let seed = match seed.as_slice() { - s if s.is_empty() => None, - s => Some(Zeroizing::new(s.to_vec())) - }; - let cb = cb.ok_or_else(|| err_msg!("No callback provided"))?; - let cb = EnsureCallback::new(move |result| - match result { - Ok(key) => cb(cb_id, ErrorCode::Success, rust_string_to_c(key)), - Err(err) => cb(cb_id, set_last_error(Some(err)), ptr::null()), - } - ); - spawn_ok(async move { - let result = unblock(move || generate_raw_store_key( - seed.as_ref().map(|s| s.as_slice()) - ).map(|p| p.to_string())).await; - cb.resolve(result); - }); - Ok(ErrorCode::Success) - } -} - // #[no_mangle] // pub extern "C" fn askar_verify_signature( // signer_vk: FfiStr<'_>, diff --git a/src/ffi/store.rs b/src/ffi/store.rs index 4ef63ec2..b98e5859 100644 --- a/src/ffi/store.rs +++ b/src/ffi/store.rs @@ -1,7 +1,6 @@ use std::{ collections::BTreeMap, ffi::CString, - mem, os::raw::c_char, ptr, str::FromStr, @@ -13,26 +12,25 @@ use std::{ use async_mutex::{Mutex, MutexGuardArc}; use ffi_support::{rust_string_to_c, ByteBuffer, FfiStr}; -use indy_utils::new_handle_type; use once_cell::sync::Lazy; use zeroize::Zeroize; -use super::{error::set_last_error, CallbackId, EnsureCallback, ErrorCode}; +use super::{error::set_last_error, handle::ArcHandle, CallbackId, EnsureCallback, ErrorCode}; use crate::{ backend::any::{AnySession, AnyStore}, error::Error, future::spawn_ok, - protect::{PassKey, StoreKeyMethod}, + protect::{generate_raw_store_key, PassKey, StoreKeyMethod}, storage::{ entry::{Entry, EntryOperation, EntryTagSet, TagFilter}, - key::{KeyCategory, KeyEntry}, + // key::{KeyCategory, KeyEntry}, types::{ManageBackend, Scan}, }, }; -new_handle_type!(StoreHandle, FFI_STORE_COUNTER); -new_handle_type!(SessionHandle, FFI_SESSION_COUNTER); -new_handle_type!(ScanHandle, FFI_SCAN_COUNTER); +new_sequence_handle!(StoreHandle, FFI_STORE_COUNTER); +new_sequence_handle!(SessionHandle, FFI_SESSION_COUNTER); +new_sequence_handle!(ScanHandle, FFI_SCAN_COUNTER); static FFI_STORES: Lazy>>> = Lazy::new(|| Mutex::new(BTreeMap::new())); @@ -137,34 +135,7 @@ impl ScanHandle { } } -#[repr(transparent)] -pub struct EntrySetHandle(u64); - -impl EntrySetHandle { - pub fn invalid() -> Self { - Self(0) - } - - pub fn create(value: FfiEntrySet) -> Self { - let results = Box::into_raw(Box::new(value)); - Self(results as u64) - } - - pub fn enter(&self, f: impl FnOnce(&mut FfiEntrySet) -> T) -> T { - let mut slf = mem::ManuallyDrop::new(unsafe { - Box::from_raw(self.0 as *const FfiEntrySet as *mut FfiEntrySet) - }); - f(&mut *slf) - } - - pub fn remove(&self) { - if self.0 != 0 { - unsafe { - Box::from_raw(self.0 as *const FfiEntrySet as *mut FfiEntrySet); - } - } - } -} +pub type EntrySetHandle = ArcHandle; pub struct FfiEntrySet { pos: AtomicUsize, @@ -243,12 +214,11 @@ impl FfiEntry { let category = CString::new(category).unwrap().into_raw(); let name = CString::new(name).unwrap().into_raw(); let value = ByteBuffer::from_vec(value.into_vec()); - let tags = match tags { - Some(tags) => { - let tags = serde_json::to_vec(&EntryTagSet::new(tags)).unwrap(); - CString::new(tags).unwrap().into_raw() - } - None => ptr::null(), + let tags = if tags.is_empty() { + ptr::null() + } else { + let tags = serde_json::to_vec(&EntryTagSet::new(tags)).unwrap(); + CString::new(tags).unwrap().into_raw() }; Self { category, @@ -270,6 +240,23 @@ impl FfiEntry { } } +#[no_mangle] +pub extern "C" fn askar_store_generate_raw_key( + seed: ByteBuffer, + out: *mut *const c_char, +) -> ErrorCode { + catch_err! { + trace!("Create raw store key"); + let seed = match seed.as_slice() { + s if s.is_empty() => None, + s => Some(s) + }; + let key = generate_raw_store_key(seed)?; + unsafe { *out = rust_string_to_c(key.to_string()); } + Ok(ErrorCode::Success) + } +} + #[no_mangle] pub extern "C" fn askar_store_provision( spec_uri: FfiStr<'_>, @@ -636,14 +623,13 @@ pub extern "C" fn askar_entry_set_next( catch_err! { check_useful_c_ptr!(entry); check_useful_c_ptr!(found); - handle.enter(|results| { - if let Some(next) = results.next() { - unsafe { *entry = next }; - unsafe { *found = 1 }; - } else { - unsafe { *found = 0 }; - } - }); + let results = handle.load()?; + if let Some(next) = results.next() { + unsafe { *entry = next }; + unsafe { *found = 1 }; + } else { + unsafe { *found = 0 }; + } Ok(ErrorCode::Success) } } @@ -726,7 +712,7 @@ pub extern "C" fn askar_session_fetch( category: FfiStr<'_>, name: FfiStr<'_>, for_update: i8, - cb: Option, + cb: Option, cb_id: CallbackId, ) -> ErrorCode { catch_err! { @@ -737,11 +723,11 @@ pub extern "C" fn askar_session_fetch( let cb = EnsureCallback::new(move |result: Result,Error>| match result { Ok(Some(entry)) => { - let results = Box::into_raw(Box::new(FfiEntrySet::from(entry))); + let results = EntrySetHandle::create(FfiEntrySet::from(entry)); cb(cb_id, ErrorCode::Success, results) }, - Ok(None) => cb(cb_id, ErrorCode::Success, ptr::null()), - Err(err) => cb(cb_id, set_last_error(Some(err)), ptr::null()), + Ok(None) => cb(cb_id, ErrorCode::Success, EntrySetHandle::invalid()), + Err(err) => cb(cb_id, set_last_error(Some(err)), EntrySetHandle::invalid()), } ); spawn_ok(async move { @@ -762,7 +748,7 @@ pub extern "C" fn askar_session_fetch_all( tag_filter: FfiStr<'_>, limit: i64, for_update: i8, - cb: Option, + cb: Option, cb_id: CallbackId, ) -> ErrorCode { catch_err! { @@ -774,10 +760,10 @@ pub extern "C" fn askar_session_fetch_all( let cb = EnsureCallback::new(move |result| match result { Ok(rows) => { - let results = Box::into_raw(Box::new(FfiEntrySet::from(rows))); + let results = EntrySetHandle::create(FfiEntrySet::from(rows)); cb(cb_id, ErrorCode::Success, results) } - Err(err) => cb(cb_id, set_last_error(Some(err)), ptr::null()), + Err(err) => cb(cb_id, set_last_error(Some(err)), EntrySetHandle::invalid()), } ); spawn_ok(async move { @@ -879,97 +865,97 @@ pub extern "C" fn askar_session_update( } } -#[no_mangle] -pub extern "C" fn askar_session_fetch_keypair( - handle: SessionHandle, - ident: FfiStr<'_>, - for_update: i8, - cb: Option, - cb_id: CallbackId, -) -> ErrorCode { - catch_err! { - trace!("Fetch keypair"); - let cb = cb.ok_or_else(|| err_msg!("No callback provided"))?; - let ident = ident.into_opt_string().ok_or_else(|| err_msg!("No key ident provided"))?; - - let cb = EnsureCallback::new(move |result| - match result { - Ok(Some(entry)) => { - let results = Box::into_raw(Box::new(FfiEntrySet::from(entry))); - cb(cb_id, ErrorCode::Success, results) - } - Ok(None) => { - cb(cb_id, ErrorCode::Success, ptr::null()) - } - Err(err) => cb(cb_id, set_last_error(Some(err)), ptr::null()), - } - ); - - spawn_ok(async move { - let result = async { - let mut session = handle.load().await?; - let key_entry = session.fetch_key( - KeyCategory::PrivateKey, - &ident, - for_update != 0 - ).await?; - Ok(key_entry.map(export_key_entry).transpose()?) - }.await; - cb.resolve(result); - }); - Ok(ErrorCode::Success) - } -} - -#[no_mangle] -pub extern "C" fn askar_session_update_keypair( - handle: SessionHandle, - ident: FfiStr<'_>, - metadata: FfiStr<'_>, - tags: FfiStr<'_>, - cb: Option, - cb_id: CallbackId, -) -> ErrorCode { - catch_err! { - trace!("Update keypair"); - let cb = cb.ok_or_else(|| err_msg!("No callback provided"))?; - let ident = ident.into_opt_string().ok_or_else(|| err_msg!("No key ident provided"))?; - let metadata = metadata.into_opt_string(); - let tags = if let Some(tags) = tags.as_opt_str() { - Some( - serde_json::from_str::(tags) - .map_err(err_map!("Error decoding tags"))? - .into_inner(), - ) - } else { - None - }; - - let cb = EnsureCallback::new(move |result| - match result { - Ok(_) => { - cb(cb_id, ErrorCode::Success) - } - Err(err) => cb(cb_id, set_last_error(Some(err))), - } - ); - - spawn_ok(async move { - let result = async { - let mut session = handle.load().await?; - session.update_key( - KeyCategory::PrivateKey, - &ident, - metadata.as_ref().map(String::as_str), - tags.as_ref().map(Vec::as_slice) - ).await?; - Ok(()) - }.await; - cb.resolve(result); - }); - Ok(ErrorCode::Success) - } -} +// #[no_mangle] +// pub extern "C" fn askar_session_fetch_keypair( +// handle: SessionHandle, +// ident: FfiStr<'_>, +// for_update: i8, +// cb: Option, +// cb_id: CallbackId, +// ) -> ErrorCode { +// catch_err! { +// trace!("Fetch keypair"); +// let cb = cb.ok_or_else(|| err_msg!("No callback provided"))?; +// let ident = ident.into_opt_string().ok_or_else(|| err_msg!("No key ident provided"))?; + +// let cb = EnsureCallback::new(move |result| +// match result { +// Ok(Some(entry)) => { +// let results = Box::into_raw(Box::new(FfiEntrySet::from(entry))); +// cb(cb_id, ErrorCode::Success, results) +// } +// Ok(None) => { +// cb(cb_id, ErrorCode::Success, ptr::null()) +// } +// Err(err) => cb(cb_id, set_last_error(Some(err)), ptr::null()), +// } +// ); + +// spawn_ok(async move { +// let result = async { +// let mut session = handle.load().await?; +// let key_entry = session.fetch_key( +// KeyCategory::PrivateKey, +// &ident, +// for_update != 0 +// ).await?; +// Ok(key_entry.map(export_key_entry).transpose()?) +// }.await; +// cb.resolve(result); +// }); +// Ok(ErrorCode::Success) +// } +// } + +// #[no_mangle] +// pub extern "C" fn askar_session_update_keypair( +// handle: SessionHandle, +// ident: FfiStr<'_>, +// metadata: FfiStr<'_>, +// tags: FfiStr<'_>, +// cb: Option, +// cb_id: CallbackId, +// ) -> ErrorCode { +// catch_err! { +// trace!("Update keypair"); +// let cb = cb.ok_or_else(|| err_msg!("No callback provided"))?; +// let ident = ident.into_opt_string().ok_or_else(|| err_msg!("No key ident provided"))?; +// let metadata = metadata.into_opt_string(); +// let tags = if let Some(tags) = tags.as_opt_str() { +// Some( +// serde_json::from_str::(tags) +// .map_err(err_map!("Error decoding tags"))? +// .into_inner(), +// ) +// } else { +// None +// }; + +// let cb = EnsureCallback::new(move |result| +// match result { +// Ok(_) => { +// cb(cb_id, ErrorCode::Success) +// } +// Err(err) => cb(cb_id, set_last_error(Some(err))), +// } +// ); + +// spawn_ok(async move { +// let result = async { +// let mut session = handle.load().await?; +// session.update_key( +// KeyCategory::PrivateKey, +// &ident, +// metadata.as_ref().map(String::as_str), +// tags.as_ref().map(Vec::as_slice) +// ).await?; +// Ok(()) +// }.await; +// cb.resolve(result); +// }); +// Ok(ErrorCode::Success) +// } +// } #[no_mangle] pub extern "C" fn askar_session_close( @@ -1018,15 +1004,15 @@ pub extern "C" fn askar_session_close( } } -fn export_key_entry(key_entry: KeyEntry) -> Result { - let KeyEntry { - category, - ident, - params, - tags, - } = key_entry; - let value = serde_json::to_string(¶ms) - .map_err(err_map!("Error converting key entry to JSON"))? - .into_bytes(); - Ok(Entry::new(category.to_string(), ident, value, tags)) -} +// fn export_key_entry(key_entry: KeyEntry) -> Result { +// let KeyEntry { +// category, +// ident, +// params, +// tags, +// } = key_entry; +// let value = serde_json::to_string(¶ms) +// .map_err(err_map!("Error converting key entry to JSON"))? +// .into_bytes(); +// Ok(Entry::new(category.to_string(), ident, value, tags)) +// } diff --git a/src/key.rs b/src/key.rs new file mode 100644 index 00000000..48538b61 --- /dev/null +++ b/src/key.rs @@ -0,0 +1,198 @@ +use std::str::FromStr; + +use crate::{ + crypto::{ + alg::{AnyKey, AnyKeyCreate, KeyAlg}, + buffer::SecretBytes, + jwk::{FromJwk, ToJwk}, + }, + error::Error, + storage::entry::{Entry, EntryTag}, +}; + +/// Parameters defining a stored key +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub struct KeyParams { + /// Associated key metadata + #[serde(default, rename = "meta", skip_serializing_if = "Option::is_none")] + pub metadata: Option, + + /// An optional external reference for the key + #[serde(default, rename = "ref", skip_serializing_if = "Option::is_none")] + pub reference: Option, + + /// The associated key data (JWK) + #[serde(default, skip_serializing_if = "Option::is_none")] + pub data: Option, +} + +impl KeyParams { + pub(crate) fn to_bytes(&self) -> Result { + serde_cbor::to_vec(self) + .map(SecretBytes::from) + .map_err(|e| err_msg!(Unexpected, "Error serializing key params: {}", e)) + } + + pub(crate) fn from_slice(params: &[u8]) -> Result { + let result = serde_cbor::from_slice(params) + .map_err(|e| err_msg!(Unexpected, "Error deserializing key params: {}", e)); + result + } +} + +/// A stored key entry +#[derive(Debug)] +pub struct LocalKey { + pub(crate) inner: Box, + pub(crate) ephemeral: bool, +} + +impl LocalKey { + /// Create a new random key or keypair + pub fn generate(alg: &str, ephemeral: bool) -> Result { + let alg = KeyAlg::from_str(alg)?; + let inner = Box::::generate(alg)?; + Ok(Self { inner, ephemeral }) + } + + pub fn from_jwk(jwk: &str) -> Result { + let inner = Box::::from_jwk(jwk)?; + Ok(Self { + inner, + ephemeral: false, + }) + } + + pub fn from_public_bytes(alg: &str, public: &[u8]) -> Result { + let alg = KeyAlg::from_str(alg)?; + let inner = Box::::from_public_bytes(alg, public)?; + Ok(Self { + inner, + ephemeral: false, + }) + } + + pub fn from_secret_bytes(alg: &str, secret: &[u8]) -> Result { + let alg = KeyAlg::from_str(alg)?; + let inner = Box::::from_secret_bytes(alg, secret)?; + Ok(Self { + inner, + ephemeral: false, + }) + } + + pub(crate) fn encode(&self) -> Result { + Ok(self.inner.to_jwk_secret()?) + } + + pub fn algorithm(&self) -> &str { + self.inner.algorithm().as_str() + } + + pub fn to_jwk_public(&self) -> Result { + Ok(self.inner.to_jwk_public()?) + } + + pub fn to_jwk_secret(&self) -> Result { + Ok(self.inner.to_jwk_secret()?) + } + + pub fn to_jwk_thumbprint(&self) -> Result { + // FIXME add special case for BLS G1+G2 (two prints) + Ok(self.inner.to_jwk_thumbprint()?) + } +} + +/// A stored key entry +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct KeyEntry { + /// The key entry identifier + pub(crate) name: String, + /// The parameters defining the key + pub(crate) params: KeyParams, + /// Key algorithm + pub(crate) alg: Option, + /// Thumbprints for the key + pub(crate) thumbprints: Vec, + /// Thumbprints for the key + pub(crate) tags: Vec, +} + +impl KeyEntry { + /// Accessor for the key identity + pub fn algorithm(&self) -> Option<&str> { + self.alg.as_ref().map(String::as_ref) + } + + /// Accessor for the key identity + pub fn name(&self) -> &str { + self.name.as_str() + } + + /// Determine if a key entry refers to a local or external key + pub fn is_local(&self) -> bool { + self.params.reference.is_none() + } + + pub(crate) fn from_entry(entry: Entry) -> Result { + let params = KeyParams::from_slice(&entry.value)?; + let mut alg = None; + let mut thumbprints = Vec::new(); + let mut tags = entry.tags; + let mut idx = 0; + while idx < tags.len() { + let tag = &mut tags[idx]; + let name = tag.name(); + if name.starts_with("user:") { + tag.update_name(|tag| tag.replace_range(0..5, "")); + idx += 1; + } else if name == "alg" { + alg.replace(tags.remove(idx).into_value()); + } else if name == "thumb" { + thumbprints.push(tags.remove(idx).into_value()); + } else { + // unrecognized tag + tags.remove(idx).into_value(); + } + } + // keep sorted for checking equality + thumbprints.sort(); + tags.sort(); + Ok(Self { + name: entry.name, + params, + alg, + thumbprints, + tags, + }) + } + + fn load_key(&mut self) -> Result { + if let Some(key_data) = self.params.data.as_ref() { + let inner = Box::::from_jwk_slice(key_data.as_ref())?; + Ok(LocalKey { + inner, + ephemeral: false, + }) + } else { + Err(err_msg!("Missing key data")) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn key_params_roundtrip() { + let params = KeyParams { + metadata: Some("meta".to_string()), + reference: None, + data: Some(SecretBytes::from(vec![0, 0, 0, 0])), + }; + let enc_params = params.to_bytes().unwrap(); + let p2 = KeyParams::from_slice(&enc_params).unwrap(); + assert_eq!(p2, params); + } +} diff --git a/src/lib.rs b/src/lib.rs index 18f1a79e..1274b9f1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,12 +40,13 @@ extern crate serde_json; #[cfg(feature = "ffi")] mod ffi; +mod key; + mod protect; pub use protect::{generate_raw_store_key, PassKey, StoreKeyMethod}; mod storage; pub use storage::{ entry::{Entry, EntryTag, TagFilter}, - key::KeyAlg, types::{Backend, ManageBackend, Store}, }; diff --git a/src/protect/profile_key.rs b/src/protect/profile_key.rs index b67070de..24be85a8 100644 --- a/src/protect/profile_key.rs +++ b/src/protect/profile_key.rs @@ -89,14 +89,14 @@ where let mut nonce = ArrayKey::::default(); hmac_key.hmac_to(&[buffer.as_ref()], nonce.as_mut())?; enc_key.encrypt_in_place(&mut buffer, nonce.as_ref(), &[])?; - buffer.buffer_insert_slice(0, nonce.as_ref())?; + buffer.buffer_insert(0, nonce.as_ref())?; Ok(buffer.into_vec()) } pub fn encrypt(mut buffer: SecretBytes, enc_key: &Key) -> Result, Error> { let nonce = ArrayKey::::random(); enc_key.encrypt_in_place(&mut buffer, nonce.as_ref(), &[])?; - buffer.buffer_insert_slice(0, nonce.as_ref())?; + buffer.buffer_insert(0, nonce.as_ref())?; Ok(buffer.into_vec()) } @@ -165,7 +165,7 @@ where { fn prepare_input(input: &[u8]) -> SecretBytes { let mut buf = SecretBytes::with_capacity(Self::encrypted_size(input.len())); - buf.write_slice(input).unwrap(); + buf.buffer_write(input).unwrap(); buf } @@ -265,10 +265,10 @@ mod tests { "category", "name", "value", - Some(vec![ + vec![ EntryTag::Plaintext("plain".to_string(), "tag".to_string()), EntryTag::Encrypted("enctag".to_string(), "envtagval".to_string()), - ]), + ], ); let enc_category = key .encrypt_entry_category(test_record.category.clone().into()) @@ -283,9 +283,7 @@ mod tests { test_record.value.clone().into(), ) .unwrap(); - let enc_tags = key - .encrypt_entry_tags(test_record.tags.clone().unwrap()) - .unwrap(); + let enc_tags = key.encrypt_entry_tags(test_record.tags.clone()).unwrap(); assert_ne!(test_record.category.as_bytes(), &enc_category[..]); assert_ne!(test_record.name.as_bytes(), &enc_name[..]); assert_ne!(test_record.value, enc_value); @@ -299,7 +297,7 @@ mod tests { enc_value, ) .unwrap(), - Some(key.decrypt_entry_tags(enc_tags).unwrap()), + key.decrypt_entry_tags(enc_tags).unwrap(), ); assert_eq!(test_record, cmp_record); } diff --git a/src/protect/store_key.rs b/src/protect/store_key.rs index 8be9e1c3..db5616a1 100644 --- a/src/protect/store_key.rs +++ b/src/protect/store_key.rs @@ -64,7 +64,7 @@ impl StoreKey { Some(key) => { let nonce = StoreKeyNonce::random(); key.encrypt_in_place(&mut data, nonce.as_ref(), &[])?; - data.buffer_insert_slice(0, nonce.as_ref())?; + data.buffer_insert(0, nonce.as_ref())?; Ok(data.into_vec()) } None => Ok(data.into_vec()), diff --git a/src/storage/entry.rs b/src/storage/entry.rs index 65fd2a3c..03ab3ebf 100644 --- a/src/storage/entry.rs +++ b/src/storage/entry.rs @@ -11,13 +11,13 @@ use zeroize::Zeroize; use super::wql; use crate::{crypto::buffer::SecretBytes, error::Error}; -pub(crate) fn sorted_tags(tags: &Vec) -> Option> { +pub(crate) fn sorted_tags(tags: &Vec) -> Vec<&EntryTag> { if tags.len() > 0 { let mut tags = tags.iter().collect::>(); tags.sort(); - Some(tags) + tags } else { - None + Vec::new() } } @@ -34,7 +34,7 @@ pub struct Entry { pub value: SecretBytes, /// Tags associated with the entry record - pub tags: Option>, + pub tags: Vec, } impl Entry { @@ -44,7 +44,7 @@ impl Entry { category: C, name: N, value: V, - tags: Option>, + tags: Vec, ) -> Self { Self { category: category.into(), @@ -54,8 +54,8 @@ impl Entry { } } - pub(crate) fn sorted_tags(&self) -> Option> { - self.tags.as_ref().and_then(sorted_tags) + pub(crate) fn sorted_tags(&self) -> Vec<&EntryTag> { + sorted_tags(&self.tags) } } @@ -70,7 +70,7 @@ impl PartialEq for Entry { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum EntryKind { - Key = 1, + Kms = 1, Item = 2, } @@ -102,12 +102,39 @@ impl EntryTag { } } + pub(crate) fn map_ref(&self, f: impl FnOnce(&str, &str) -> (String, String)) -> Self { + match self { + Self::Encrypted(name, val) => { + let (name, val) = f(name.as_str(), val.as_str()); + Self::Encrypted(name, val) + } + Self::Plaintext(name, val) => { + let (name, val) = f(name.as_str(), val.as_str()); + Self::Plaintext(name, val) + } + } + } + + /// Setter for the tag name + pub(crate) fn update_name(&mut self, f: impl FnOnce(&mut String)) { + match self { + Self::Encrypted(name, _) | Self::Plaintext(name, _) => f(name), + } + } + /// Accessor for the tag value pub fn value(&self) -> &str { match self { Self::Encrypted(_, val) | Self::Plaintext(_, val) => val, } } + + /// Unwrap the tag value + pub(crate) fn into_value(self) -> String { + match self { + Self::Encrypted(_, value) | Self::Plaintext(_, value) => value, + } + } } impl Debug for EntryTag { diff --git a/src/storage/key.rs b/src/storage/key.rs deleted file mode 100644 index d6ded048..00000000 --- a/src/storage/key.rs +++ /dev/null @@ -1,146 +0,0 @@ -use std::{ - fmt::{self, Display, Formatter}, - str::FromStr, -}; - -use zeroize::Zeroize; - -pub use crate::crypto::KeyAlg; - -use super::entry::{sorted_tags, EntryTag}; -use crate::{crypto::buffer::SecretBytes, error::Error}; - -/// Categories of keys supported by the default KMS -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Zeroize)] -pub enum KeyCategory { - /// A private key or keypair - PrivateKey, - /// A public key - PublicKey, -} - -impl KeyCategory { - /// Get a reference to a string representing the `KeyCategory` - pub fn as_str(&self) -> &str { - match self { - Self::PrivateKey => "private", - Self::PublicKey => "public", - } - } - - /// Convert the `KeyCategory` into an owned string - pub fn to_string(&self) -> String { - self.as_str().to_string() - } -} - -// serde_as_str_impl!(KeyCategory); - -impl AsRef for KeyCategory { - fn as_ref(&self) -> &str { - self.as_str() - } -} - -impl FromStr for KeyCategory { - type Err = Error; - - fn from_str(s: &str) -> Result { - Ok(match s { - "private" => Self::PrivateKey, - "public" => Self::PublicKey, - _ => return Err(err_msg!("Unknown key category: {}", s)), - }) - } -} - -impl Display for KeyCategory { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.write_str(self.as_str()) - } -} - -/// Parameters defining a stored key -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] -pub struct KeyParams { - /// Associated key metadata - #[serde(default, rename = "meta", skip_serializing_if = "Option::is_none")] - pub metadata: Option, - - /// An optional external reference for the key - #[serde(default, rename = "ref", skip_serializing_if = "Option::is_none")] - pub reference: Option, - - /// The associated key data in JWK format - #[serde(default, skip_serializing_if = "Option::is_none")] - pub data: Option, -} - -impl KeyParams { - pub(crate) fn to_bytes(&self) -> Result { - serde_cbor::to_vec(self) - .map(SecretBytes::from) - .map_err(|e| err_msg!(Unexpected, "Error serializing key params: {}", e)) - } - - pub(crate) fn from_slice(params: &[u8]) -> Result { - let result = serde_cbor::from_slice(params) - .map_err(|e| err_msg!(Unexpected, "Error deserializing key params: {}", e)); - result - } -} - -/// A stored key entry -#[derive(Clone, Debug, Eq)] -pub struct KeyEntry { - /// The category of the key entry (public or private) - pub category: KeyCategory, - /// The key entry identifier - pub ident: String, - /// The parameters defining the key - pub params: KeyParams, - /// Tags associated with the key entry record - pub tags: Option>, -} - -impl KeyEntry { - /// Determine if a key entry refers to a local or external key - pub fn is_local(&self) -> bool { - self.params.reference.is_none() - } - - /// Fetch the associated key data - pub fn key_data(&self) -> Option<&[u8]> { - self.params.data.as_ref().map(AsRef::as_ref) - } - - pub(crate) fn sorted_tags(&self) -> Option> { - self.tags.as_ref().and_then(sorted_tags) - } -} - -impl PartialEq for KeyEntry { - fn eq(&self, rhs: &Self) -> bool { - self.category == rhs.category - && self.ident == rhs.ident - && self.params == rhs.params - && self.sorted_tags() == rhs.sorted_tags() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn key_params_roundtrip() { - let params = KeyParams { - metadata: Some("meta".to_string()), - reference: None, - data: Some(SecretBytes::from(vec![0, 0, 0, 0])), - }; - let enc_params = params.to_bytes().unwrap(); - let p2 = KeyParams::from_slice(&enc_params).unwrap(); - assert_eq!(p2, params); - } -} diff --git a/src/storage/mod.rs b/src/storage/mod.rs index f75ef3a0..e19115ff 100644 --- a/src/storage/mod.rs +++ b/src/storage/mod.rs @@ -1,7 +1,5 @@ pub mod entry; -pub mod key; - pub(crate) mod options; pub mod types; diff --git a/src/storage/types.rs b/src/storage/types.rs index 486b2698..d725d802 100644 --- a/src/storage/types.rs +++ b/src/storage/types.rs @@ -1,19 +1,18 @@ use std::{ - fmt::{self, Debug, Formatter}, + fmt::{self, Debug, Display, Formatter}, pin::Pin, str::FromStr, sync::Arc, }; use futures_lite::stream::{Stream, StreamExt}; +use zeroize::Zeroize; -use super::{ - entry::{Entry, EntryKind, EntryOperation, EntryTag, TagFilter}, - key::{KeyCategory, KeyEntry, KeyParams}, -}; +use super::entry::{Entry, EntryKind, EntryOperation, EntryTag, TagFilter}; use crate::{ error::Error, future::BoxFuture, + key::{KeyEntry, KeyParams, LocalKey}, protect::{PassKey, StoreKeyMethod}, }; @@ -380,57 +379,49 @@ impl Session { .await?) } - // /// Create a new keypair in the store - // pub async fn create_keypair( - // &mut self, - // alg: KeyAlg, - // metadata: Option<&str>, - // seed: Option<&[u8]>, - // tags: Option<&[EntryTag]>, - // // backend - // ) -> Result { - // match alg { - // KeyAlg::Ed25519 => (), - // _ => return Err(err_msg!(Unsupported, "Unsupported key algorithm")), - // } - - // let keypair = match seed { - // None => Ed25519KeyPair::generate(), - // Some(s) => Ed25519KeyPair::from_seed(s), - // } - // .map_err(err_map!(Unexpected, "Error generating keypair"))?; - // let pk = keypair.public_key(); - - // let category = KeyCategory::PrivateKey; - // let ident = pk.to_string(); - - // let params = KeyParams { - // alg, - // metadata: metadata.map(str::to_string), - // reference: None, - // data: Some(keypair.to_bytes()), - // }; - // let value = Zeroizing::new(params.to_vec()?); - - // self.0 - // .update( - // EntryKind::Key, - // EntryOperation::Insert, - // category.as_str(), - // &ident, - // Some(value.as_slice()), - // tags.clone(), - // None, - // ) - // .await?; - - // Ok(KeyEntry { - // category, - // ident, - // params, - // tags: tags.map(|t| t.to_vec()), - // }) - // } + /// Insert a local key instance into the store + pub async fn insert_key( + &mut self, + name: &str, + key: &LocalKey, + metadata: Option<&str>, + tags: Option<&[EntryTag]>, + expiry_ms: Option, + ) -> Result<(), Error> { + let data = key.encode()?; + let params = KeyParams { + metadata: metadata.map(str::to_string), + reference: None, + data: Some(data), + }; + let value = params.to_bytes()?; + let mut ins_tags = Vec::with_capacity(10); + let alg = key.algorithm(); + if !alg.is_empty() { + ins_tags.push(EntryTag::Encrypted("alg".to_string(), alg.to_string())); + } + let thumb = key.to_jwk_thumbprint()?; + if !thumb.is_empty() { + ins_tags.push(EntryTag::Encrypted("thumb".to_string(), thumb)); + } + if let Some(tags) = tags { + for t in tags { + ins_tags.push(t.map_ref(|k, v| (format!("user:{}", k), v.to_string()))); + } + } + self.0 + .update( + EntryKind::Kms, + EntryOperation::Insert, + KmsCategory::CryptoKey.as_str(), + name, + Some(value.as_ref()), + Some(ins_tags.as_slice()), + expiry_ms, + ) + .await?; + Ok(()) + } /// Fetch an existing key from the store /// @@ -438,23 +429,21 @@ impl Session { /// associated record, if supported by the store backend pub async fn fetch_key( &mut self, - category: KeyCategory, - ident: &str, + name: &str, for_update: bool, ) -> Result, Error> { Ok( if let Some(row) = self .0 - .fetch(EntryKind::Key, category.as_str(), &ident, for_update) + .fetch( + EntryKind::Kms, + KmsCategory::CryptoKey.as_str(), + name, + for_update, + ) .await? { - let params = KeyParams::from_slice(&row.value)?; - Some(KeyEntry { - category: KeyCategory::from_str(&row.category).unwrap(), - ident: row.name.clone(), - params, - tags: row.tags.clone(), - }) + Some(KeyEntry::from_entry(row)?) } else { None }, @@ -462,13 +451,13 @@ impl Session { } /// Remove an existing key from the store - pub async fn remove_key(&mut self, category: KeyCategory, ident: &str) -> Result<(), Error> { + pub async fn remove_key(&mut self, name: &str) -> Result<(), Error> { self.0 .update( - EntryKind::Key, + EntryKind::Kms, EntryOperation::Remove, - category.as_str(), - &ident, + KmsCategory::CryptoKey.as_str(), + name, None, None, None, @@ -477,16 +466,16 @@ impl Session { } /// Replace the metadata and tags on an existing key in the store - pub async fn update_key( + pub async fn replace_key( &mut self, - category: KeyCategory, - ident: &str, + name: &str, metadata: Option<&str>, tags: Option<&[EntryTag]>, + expiry_ms: Option, ) -> Result<(), Error> { let row = self .0 - .fetch(EntryKind::Key, category.as_str(), &ident, true) + .fetch(EntryKind::Kms, KmsCategory::CryptoKey.as_str(), name, true) .await? .ok_or_else(|| err_msg!(NotFound, "Key entry not found"))?; @@ -494,15 +483,27 @@ impl Session { params.metadata = metadata.map(str::to_string); let value = params.to_bytes()?; + let mut upd_tags = Vec::with_capacity(10); + if let Some(tags) = tags { + for t in tags { + upd_tags.push(t.map_ref(|k, v| (format!("user:{}", k), v.to_string()))); + } + } + for t in row.tags { + if !t.name().starts_with("user:") { + upd_tags.push(t); + } + } + self.0 .update( - EntryKind::Key, + EntryKind::Kms, EntryOperation::Replace, - category.as_str(), - &ident, - Some(&value), + KmsCategory::CryptoKey.as_str(), + name, + Some(value.as_ref()), tags, - None, + expiry_ms, ) .await?; @@ -562,3 +563,48 @@ impl Debug for Scan<'_, S> { .finish() } } + +/// Supported categories of KMS entries +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Zeroize)] +pub(crate) enum KmsCategory { + /// A user managed key + CryptoKey, + // may want to manage certificates, passwords, etc +} + +impl KmsCategory { + /// Get a reference to a string representing the `KmsCategory` + pub fn as_str(&self) -> &str { + match self { + Self::CryptoKey => "cryptokey", + } + } + + /// Convert the `KmsCategory` into an owned string + pub fn to_string(&self) -> String { + self.as_str().to_string() + } +} + +impl AsRef for KmsCategory { + fn as_ref(&self) -> &str { + self.as_str() + } +} + +impl FromStr for KmsCategory { + type Err = Error; + + fn from_str(s: &str) -> Result { + Ok(match s { + "cryptokey" => Self::CryptoKey, + _ => return Err(err_msg!("Unknown KMS category: {}", s)), + }) + } +} + +impl Display for KmsCategory { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} diff --git a/tests/utils/mod.rs b/tests/utils/mod.rs index 4c2ff6a0..7eae053d 100644 --- a/tests/utils/mod.rs +++ b/tests/utils/mod.rs @@ -45,10 +45,10 @@ pub async fn db_insert_fetch(db: &Store) { "category", "name", "value", - Some(vec![ + vec![ EntryTag::Encrypted("t1".to_string(), "v1".to_string()), EntryTag::Plaintext("t2".to_string(), "v2".to_string()), - ]), + ], ); let mut conn = db.session(None).await.expect(ERR_SESSION); @@ -57,7 +57,7 @@ pub async fn db_insert_fetch(db: &Store) { &test_row.category, &test_row.name, &test_row.value, - test_row.tags.as_ref().map(|t| t.as_slice()), + Some(test_row.tags.as_slice()), None, ) .await @@ -79,7 +79,7 @@ pub async fn db_insert_fetch(db: &Store) { } pub async fn db_insert_duplicate(db: &Store) { - let test_row = Entry::new("category", "name", "value", None); + let test_row = Entry::new("category", "name", "value", Vec::new()); let mut conn = db.session(None).await.expect(ERR_SESSION); @@ -87,7 +87,7 @@ pub async fn db_insert_duplicate(db: &Store) { &test_row.category, &test_row.name, &test_row.value, - test_row.tags.as_ref().map(|t| t.as_slice()), + Some(test_row.tags.as_slice()), None, ) .await @@ -98,7 +98,7 @@ pub async fn db_insert_duplicate(db: &Store) { &test_row.category, &test_row.name, &test_row.value, - test_row.tags.as_ref().map(|t| t.as_slice()), + Some(test_row.tags.as_slice()), None, ) .await @@ -107,7 +107,7 @@ pub async fn db_insert_duplicate(db: &Store) { } pub async fn db_insert_remove(db: &Store) { - let test_row = Entry::new("category", "name", "value", None); + let test_row = Entry::new("category", "name", "value", Vec::new()); let mut conn = db.session(None).await.expect(ERR_SESSION); @@ -115,7 +115,7 @@ pub async fn db_insert_remove(db: &Store) { &test_row.category, &test_row.name, &test_row.value, - test_row.tags.as_ref().map(|t| t.as_slice()), + Some(test_row.tags.as_slice()), None, ) .await @@ -134,7 +134,7 @@ pub async fn db_remove_missing(db: &Store) { } pub async fn db_replace_fetch(db: &Store) { - let test_row = Entry::new("category", "name", "value", None); + let test_row = Entry::new("category", "name", "value", Vec::new()); let mut conn = db.session(None).await.expect(ERR_SESSION); @@ -142,7 +142,7 @@ pub async fn db_replace_fetch(db: &Store) { &test_row.category, &test_row.name, &test_row.value, - test_row.tags.as_ref().map(|t| t.as_slice()), + Some(test_row.tags.as_slice()), None, ) .await @@ -154,7 +154,7 @@ pub async fn db_replace_fetch(db: &Store) { &replace_row.category, &replace_row.name, &replace_row.value, - replace_row.tags.as_ref().map(|t| t.as_slice()), + Some(replace_row.tags.as_slice()), None, ) .await @@ -169,7 +169,7 @@ pub async fn db_replace_fetch(db: &Store) { } pub async fn db_replace_missing(db: &Store) { - let test_row = Entry::new("category", "name", "value", None); + let test_row = Entry::new("category", "name", "value", Vec::new()); let mut conn = db.session(None).await.expect(ERR_SESSION); @@ -178,7 +178,7 @@ pub async fn db_replace_missing(db: &Store) { &test_row.category, &test_row.name, &test_row.value, - test_row.tags.as_ref().map(|t| t.as_slice()), + Some(test_row.tags.as_slice()), None, ) .await @@ -188,7 +188,7 @@ pub async fn db_replace_missing(db: &Store) { pub async fn db_count(db: &Store) { let category = "category".to_string(); - let test_rows = vec![Entry::new(&category, "name", "value", None)]; + let test_rows = vec![Entry::new(&category, "name", "value", Vec::new())]; let mut conn = db.session(None).await.expect(ERR_SESSION); @@ -197,7 +197,7 @@ pub async fn db_count(db: &Store) { &upd.category, &upd.name, &upd.value, - upd.tags.as_ref().map(|t| t.as_slice()), + Some(upd.tags.as_slice()), None, ) .await @@ -218,10 +218,10 @@ pub async fn db_count_exist(db: &Store) { "category", "name", "value", - Some(vec![ + vec![ EntryTag::Encrypted("enc".to_string(), "v1".to_string()), EntryTag::Plaintext("plain".to_string(), "v2".to_string()), - ]), + ], ); let mut conn = db.session(None).await.expect(ERR_SESSION); @@ -230,7 +230,7 @@ pub async fn db_count_exist(db: &Store) { &test_row.category, &test_row.name, &test_row.value, - test_row.tags.as_ref().map(|t| t.as_slice()), + Some(test_row.tags.as_slice()), None, ) .await @@ -358,10 +358,10 @@ pub async fn db_scan(db: &Store) { &category, "name", "value", - Some(vec![ + vec![ EntryTag::Encrypted("t1".to_string(), "v1".to_string()), EntryTag::Plaintext("t2".to_string(), "v2".to_string()), - ]), + ], )]; let mut conn = db.session(None).await.expect(ERR_SESSION); @@ -371,7 +371,7 @@ pub async fn db_scan(db: &Store) { &upd.category, &upd.name, &upd.value, - upd.tags.as_ref().map(|t| t.as_slice()), + Some(upd.tags.as_slice()), None, ) .await @@ -406,28 +406,28 @@ pub async fn db_remove_all(db: &Store) { "category", "item1", "value", - Some(vec![ + vec![ EntryTag::Encrypted("t1".to_string(), "del".to_string()), EntryTag::Plaintext("t2".to_string(), "del".to_string()), - ]), + ], ), Entry::new( "category", "item2", "value", - Some(vec![ + vec![ EntryTag::Encrypted("t1".to_string(), "del".to_string()), EntryTag::Plaintext("t2".to_string(), "del".to_string()), - ]), + ], ), Entry::new( "category", "item3", "value", - Some(vec![ + vec![ EntryTag::Encrypted("t1".to_string(), "keep".to_string()), EntryTag::Plaintext("t2".to_string(), "keep".to_string()), - ]), + ], ), ]; @@ -438,7 +438,7 @@ pub async fn db_remove_all(db: &Store) { &test_row.category, &test_row.name, &test_row.value, - test_row.tags.as_ref().map(|t| t.as_slice()), + Some(test_row.tags.as_slice()), None, ) .await @@ -571,7 +571,7 @@ pub async fn db_remove_all(db: &Store) { // } pub async fn db_txn_rollback(db: &Store) { - let test_row = Entry::new("category", "name", "value", None); + let test_row = Entry::new("category", "name", "value", Vec::new()); let mut conn = db.transaction(None).await.expect(ERR_TRANSACTION); @@ -579,7 +579,7 @@ pub async fn db_txn_rollback(db: &Store) { &test_row.category, &test_row.name, &test_row.value, - test_row.tags.as_ref().map(|t| t.as_slice()), + Some(test_row.tags.as_slice()), None, ) .await @@ -599,7 +599,7 @@ pub async fn db_txn_rollback(db: &Store) { } pub async fn db_txn_drop(db: &Store) { - let test_row = Entry::new("category", "name", "value", None); + let test_row = Entry::new("category", "name", "value", Vec::new()); let mut conn = db .transaction(None) @@ -610,7 +610,7 @@ pub async fn db_txn_drop(db: &Store) { &test_row.category, &test_row.name, &test_row.value, - test_row.tags.as_ref().map(|t| t.as_slice()), + Some(test_row.tags.as_slice()), None, ) .await @@ -629,7 +629,7 @@ pub async fn db_txn_drop(db: &Store) { // test that session does NOT have transaction rollback behaviour pub async fn db_session_drop(db: &Store) { - let test_row = Entry::new("category", "name", "value", None); + let test_row = Entry::new("category", "name", "value", Vec::new()); let mut conn = db.session(None).await.expect(ERR_SESSION); @@ -637,7 +637,7 @@ pub async fn db_session_drop(db: &Store) { &test_row.category, &test_row.name, &test_row.value, - test_row.tags.as_ref().map(|t| t.as_slice()), + Some(test_row.tags.as_slice()), None, ) .await @@ -655,7 +655,7 @@ pub async fn db_session_drop(db: &Store) { } pub async fn db_txn_commit(db: &Store) { - let test_row = Entry::new("category", "name", "value", None); + let test_row = Entry::new("category", "name", "value", Vec::new()); let mut conn = db.transaction(None).await.expect(ERR_TRANSACTION); @@ -663,7 +663,7 @@ pub async fn db_txn_commit(db: &Store) { &test_row.category, &test_row.name, &test_row.value, - test_row.tags.as_ref().map(|t| t.as_slice()), + Some(test_row.tags.as_slice()), None, ) .await @@ -681,7 +681,7 @@ pub async fn db_txn_commit(db: &Store) { } pub async fn db_txn_fetch_for_update(db: &Store) { - let test_row = Entry::new("category", "name", "value", None); + let test_row = Entry::new("category", "name", "value", Vec::new()); let mut conn = db.transaction(None).await.expect(ERR_TRANSACTION); @@ -689,7 +689,7 @@ pub async fn db_txn_fetch_for_update(db: &Store) { &test_row.category, &test_row.name, &test_row.value, - test_row.tags.as_ref().map(|t| t.as_slice()), + Some(test_row.tags.as_slice()), None, ) .await diff --git a/wrappers/python/aries_askar/bindings.py b/wrappers/python/aries_askar/bindings.py index ec598c7b..042c99d2 100644 --- a/wrappers/python/aries_askar/bindings.py +++ b/wrappers/python/aries_askar/bindings.py @@ -16,6 +16,7 @@ c_int8, c_int32, c_int64, + c_size_t, c_void_p, c_ubyte, ) @@ -38,7 +39,7 @@ MODULE_NAME = __name__.split(".")[0] -class StoreHandle(c_int64): +class StoreHandle(c_size_t): """Index of an active Store instance.""" def __repr__(self) -> str: @@ -57,7 +58,7 @@ def __del__(self): do_call("askar_store_close", self, c_void_p()) -class SessionHandle(c_int64): +class SessionHandle(c_size_t): """Index of an active Session/Transaction instance.""" def __repr__(self) -> str: @@ -80,7 +81,7 @@ def __del__(self): do_call("askar_session_close", self, c_int8(0), c_void_p()) -class ScanHandle(c_int64): +class ScanHandle(c_size_t): """Index of an active Store scan instance.""" def __repr__(self) -> str: @@ -93,8 +94,8 @@ def __del__(self): get_library().askar_scan_free(self) -class EntrySetHandle(c_int64): - """Index of an active EntrySet instance.""" +class EntrySetHandle(c_size_t): + """Pointer to an active EntrySet instance.""" def __repr__(self) -> str: """Format entry set handle as a string.""" @@ -430,13 +431,11 @@ def get_current_error(expect: bool = False) -> Optional[StoreError]: return StoreError(StoreErrorCode.WRAPPER, "Unknown error") -async def generate_raw_key(seed: Union[str, bytes] = None) -> str: +def generate_raw_key(seed: Union[str, bytes] = None) -> str: """Generate a new raw store wrapping key.""" - return str( - await do_call_async( - "askar_generate_raw_key", encode_bytes(seed), return_type=StrBuffer - ) - ) + key = StrBuffer() + do_call("askar_store_generate_raw_key", encode_bytes(seed), byref(key)) + return str(key) def version() -> str: @@ -714,3 +713,40 @@ def entry_set_next(handle: EntrySetHandle) -> Optional[Entry]: if found: return ffi_entry.decode(handle) return None + + +class LocalKeyHandle(c_size_t): + """Pointer to an active LocalKey instance.""" + + def __repr__(self) -> str: + """Format key handle as a string.""" + return f"{self.__class__.__name__}({self.value})" + + def __del__(self): + """Free the key when there are no more references.""" + if self: + get_library().askar_key_free(self) + + +def key_generate(alg: str, ephemeral: bool = False) -> LocalKeyHandle: + handle = LocalKeyHandle() + do_call("askar_key_generate", encode_str(alg), c_int8(ephemeral), byref(handle)) + return handle + + +def key_from_jwk(jwk: str) -> LocalKeyHandle: + handle = LocalKeyHandle() + do_call("askar_key_from_jwk", encode_str(jwk), byref(handle)) + return handle + + +def key_get_algorithm(handle: LocalKeyHandle) -> str: + alg = StrBuffer() + do_call("askar_key_get_algorithm", handle, byref(alg)) + return str(alg) + + +def key_get_jwk_public(handle: LocalKeyHandle) -> str: + alg = StrBuffer() + do_call("askar_key_get_jwk_public", handle, byref(alg)) + return str(alg) diff --git a/wrappers/python/demo/perf.py b/wrappers/python/demo/perf.py index 10bd20dc..da31d974 100644 --- a/wrappers/python/demo/perf.py +++ b/wrappers/python/demo/perf.py @@ -27,7 +27,7 @@ def log(*args): async def perf_test(): - key = await generate_raw_key() + key = generate_raw_key() store = await Store.provision(REPO_URI, "raw", key, recreate=True) @@ -42,23 +42,24 @@ async def perf_test(): dur = time.perf_counter() - insert_start print(f"insert duration ({PERF_ROWS} rows): {dur:0.2f}s") - fetch_start = time.perf_counter() - async with store as session: + # fetch_start = time.perf_counter() + # async with store as session: + # tags = 0 + # for idx in range(PERF_ROWS): + # entry = await session.fetch("category", f"name-{idx}") + # tags += len(entry.tags) + # dur = time.perf_counter() - fetch_start + # print(f"fetch duration ({PERF_ROWS} rows, {tags} tags): {dur:0.2f}s") + + for _ in range(10): + rc = 0 tags = 0 - for idx in range(PERF_ROWS): - entry = await session.fetch("category", f"name-{idx}") - tags += len(entry.tags) - dur = time.perf_counter() - fetch_start - print(f"fetch duration ({PERF_ROWS} rows, {tags} tags): {dur:0.2f}s") - - rc = 0 - tags = 0 - scan_start = time.perf_counter() - async for row in store.scan("category", {"~plaintag": "a", "enctag": "b"}): - rc += 1 - tags += len(row.tags) - dur = time.perf_counter() - scan_start - print(f"scan duration ({rc} rows, {tags} tags): {dur:0.2f}s") + scan_start = time.perf_counter() + async for row in store.scan("category", {"~plaintag": "a", "enctag": "b"}): + rc += 1 + tags += len(row.tags) + dur = time.perf_counter() - scan_start + print(f"scan duration ({rc} rows, {tags} tags): {dur:0.2f}s") await store.close() diff --git a/wrappers/python/demo/test.py b/wrappers/python/demo/test.py index 060e6f58..59bb9259 100644 --- a/wrappers/python/demo/test.py +++ b/wrappers/python/demo/test.py @@ -2,6 +2,7 @@ import logging import os import sys +from aries_askar import bindings from aries_askar.bindings import ( generate_raw_key, @@ -27,7 +28,7 @@ def log(*args): async def basic_test(): if ENCRYPT: - key = await generate_raw_key(b"00000000000000000000000000000My1") + key = generate_raw_key(b"00000000000000000000000000000My1") key_method = "raw" log("Generated raw store key:", key) else: @@ -69,6 +70,11 @@ async def basic_test(): async for row in store.scan("category", {"~plaintag": "a", "enctag": "b"}): log("Scan result:", row) + key = bindings.key_generate("ed25519") + log("Created key:", key) + jwk = bindings.key_get_jwk_public(key) + log("JWK:", jwk) + # test key operations in a new session async with store as session: # # Create a new keypair @@ -104,7 +110,7 @@ async def basic_test(): log("Created profile:", profile) log("Removed profile:", await store.remove_profile(profile)) - key2 = await generate_raw_key(b"00000000000000000000000000000My2") + key2 = generate_raw_key(b"00000000000000000000000000000My2") await store.rekey("raw", key2) log("Re-keyed store") From 1aca26dde2146a7abbfbefd9ea14aebe9fb7486c Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Mon, 19 Apr 2021 00:50:23 -0700 Subject: [PATCH 048/116] start adding key exchange for AnyKey Signed-off-by: Andrew Whitehead --- askar-crypto/src/alg/aesgcm.rs | 3 +- askar-crypto/src/alg/any.rs | 88 ++++++++++++++++++++++++++++++++ askar-crypto/src/alg/chacha20.rs | 3 +- askar-crypto/src/kdf/ecdh_1pu.rs | 4 +- askar-crypto/src/kdf/ecdh_es.rs | 4 +- askar-crypto/src/kdf/mod.rs | 4 +- 6 files changed, 98 insertions(+), 8 deletions(-) diff --git a/askar-crypto/src/alg/aesgcm.rs b/askar-crypto/src/alg/aesgcm.rs index 7c08299d..cff5f75e 100644 --- a/askar-crypto/src/alg/aesgcm.rs +++ b/askar-crypto/src/alg/aesgcm.rs @@ -200,7 +200,8 @@ impl ToJwk for AesGcmKey { // for direct key agreement (not used currently) impl FromKeyExchange for AesGcmKey where - Lhs: KeyExchange, + Lhs: KeyExchange + ?Sized, + Rhs: ?Sized, T: AesGcmType, { fn from_key_exchange(lhs: &Lhs, rhs: &Rhs) -> Result { diff --git a/askar-crypto/src/alg/any.rs b/askar-crypto/src/alg/any.rs index c5cca0cf..5570b55f 100644 --- a/askar-crypto/src/alg/any.rs +++ b/askar-crypto/src/alg/any.rs @@ -15,6 +15,7 @@ use crate::{ buffer::WriteBuffer, error::Error, jwk::{FromJwk, JwkEncoder, JwkParts, ToJwk}, + kdf::{FromKeyExchange, KeyExchange}, repr::{KeyGen, KeyPublicBytes, KeySecretBytes}, sign::{KeySigVerify, KeySign, SignatureType}, }; @@ -58,6 +59,11 @@ pub trait AnyKeyCreate: Sized { fn from_secret_bytes(alg: KeyAlg, secret: &[u8]) -> Result; fn from_key(key: K) -> Self; + + fn from_key_exchange(alg: KeyAlg, secret: &Sk, public: &Pk) -> Result + where + Sk: KeyExchange + ?Sized, + Pk: ?Sized; } impl AnyKeyCreate for Box { @@ -77,6 +83,14 @@ impl AnyKeyCreate for Box { fn from_key(key: K) -> Self { Box::new(KeyT(key)) } + + fn from_key_exchange(alg: KeyAlg, secret: &Sk, public: &Pk) -> Result + where + Sk: KeyExchange + ?Sized, + Pk: ?Sized, + { + from_key_exchange_any(alg, secret, public) + } } impl AnyKeyCreate for Arc { @@ -96,6 +110,14 @@ impl AnyKeyCreate for Arc { fn from_key(key: K) -> Self { Arc::new(KeyT(key)) } + + fn from_key_exchange(alg: KeyAlg, secret: &Sk, public: &Pk) -> Result + where + Sk: KeyExchange + ?Sized, + Pk: ?Sized, + { + from_key_exchange_any(alg, secret, public) + } } #[inline] @@ -175,6 +197,59 @@ fn from_secret_any(alg: KeyAlg, secret: &[u8]) -> Result } } +#[inline] +fn from_key_exchange_any(alg: KeyAlg, secret: &Sk, public: &Pk) -> Result +where + R: AllocKey, + Sk: KeyExchange + ?Sized, + Pk: ?Sized, +{ + match alg { + KeyAlg::Aes(AesTypes::A128GCM) => { + AesGcmKey::::from_key_exchange(secret, public).map(R::alloc_key) + } + KeyAlg::Aes(AesTypes::A256GCM) => { + AesGcmKey::::from_key_exchange(secret, public).map(R::alloc_key) + } + KeyAlg::Chacha20(Chacha20Types::C20P) => { + Chacha20Key::::from_key_exchange(secret, public).map(R::alloc_key) + } + KeyAlg::Chacha20(Chacha20Types::XC20P) => { + Chacha20Key::::from_key_exchange(secret, public).map(R::alloc_key) + } + #[allow(unreachable_patterns)] + _ => { + return Err(err_msg!( + Unsupported, + "Unsupported algorithm for key import" + )) + } + } +} + +impl KeyExchange for AnyKey { + fn key_exchange_buffer( + &self, + other: &AnyKey, + out: &mut B, + ) -> Result<(), Error> { + match self.key_type_id() { + s if s != other.key_type_id() => Err(err_msg!(Unsupported, "Unsupported key exchange")), + s if s == TypeId::of::() => Ok(self + .assume::() + .key_exchange_buffer(other.assume::(), out)?), + s if s == TypeId::of::() => Ok(self + .assume::() + .key_exchange_buffer(other.assume::(), out)?), + s if s == TypeId::of::() => Ok(self + .assume::() + .key_exchange_buffer(other.assume::(), out)?), + #[allow(unreachable_patterns)] + _ => return Err(err_msg!(Unsupported, "Unsupported key exchange")), + } + } +} + impl FromJwk for Box { fn from_jwk_parts(jwk: JwkParts<'_>) -> Result { from_jwk_any(jwk) @@ -317,4 +392,17 @@ mod tests { assert_eq!(key.key_type_id(), TypeId::of::()); let _ = key.to_jwk_public().unwrap(); } + + #[test] + fn key_exchange_any() { + let alice = Box::::generate(KeyAlg::X25519).unwrap(); + let bob = Box::::generate(KeyAlg::X25519).unwrap(); + let exch_a = alice.key_exchange_bytes(&bob).unwrap(); + let exch_b = bob.key_exchange_bytes(&alice).unwrap(); + assert_eq!(exch_a, exch_b); + + let _aes_key = + Box::::from_key_exchange(KeyAlg::Aes(AesTypes::A256GCM), &*alice, &*bob) + .unwrap(); + } } diff --git a/askar-crypto/src/alg/chacha20.rs b/askar-crypto/src/alg/chacha20.rs index 202eb4d7..bb90a71a 100644 --- a/askar-crypto/src/alg/chacha20.rs +++ b/askar-crypto/src/alg/chacha20.rs @@ -222,7 +222,8 @@ impl ToJwk for Chacha20Key { // for direct key agreement (not used currently) impl FromKeyExchange for Chacha20Key where - Lhs: KeyExchange, + Lhs: KeyExchange + ?Sized, + Rhs: ?Sized, T: Chacha20Type, { fn from_key_exchange(lhs: &Lhs, rhs: &Rhs) -> Result { diff --git a/askar-crypto/src/kdf/ecdh_1pu.rs b/askar-crypto/src/kdf/ecdh_1pu.rs index 1190f394..b2549c1d 100644 --- a/askar-crypto/src/kdf/ecdh_1pu.rs +++ b/askar-crypto/src/kdf/ecdh_1pu.rs @@ -14,9 +14,9 @@ use crate::{ repr::KeyGen, }; -pub struct Ecdh1PU(PhantomData); +pub struct Ecdh1PU(PhantomData); -impl Ecdh1PU { +impl Ecdh1PU { fn derive_key_config( ephem_key: &Key, send_key: &Key, diff --git a/askar-crypto/src/kdf/ecdh_es.rs b/askar-crypto/src/kdf/ecdh_es.rs index 1e77fab7..a507c66b 100644 --- a/askar-crypto/src/kdf/ecdh_es.rs +++ b/askar-crypto/src/kdf/ecdh_es.rs @@ -14,9 +14,9 @@ use crate::{ repr::KeyGen, }; -pub struct EcdhEs(PhantomData); +pub struct EcdhEs(PhantomData); -impl EcdhEs { +impl EcdhEs { fn derive_key_config( ephem_key: &Key, recip_key: &Key, diff --git a/askar-crypto/src/kdf/mod.rs b/askar-crypto/src/kdf/mod.rs index 4252a4d0..fce782ab 100644 --- a/askar-crypto/src/kdf/mod.rs +++ b/askar-crypto/src/kdf/mod.rs @@ -10,7 +10,7 @@ pub mod ecdh_1pu; pub mod ecdh_es; -pub trait KeyExchange { +pub trait KeyExchange { fn key_exchange_buffer(&self, other: &Rhs, out: &mut B) -> Result<(), Error>; #[cfg(feature = "alloc")] @@ -21,6 +21,6 @@ pub trait KeyExchange { } } -pub trait FromKeyExchange: Sized { +pub trait FromKeyExchange: Sized { fn from_key_exchange(lhs: &Lhs, rhs: &Rhs) -> Result; } From 52b3cdb82abdd724c6fe6053f55d06c5d6aa5fff Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Mon, 19 Apr 2021 13:23:39 -0700 Subject: [PATCH 049/116] add AEAD encryption via python wrapper Signed-off-by: Andrew Whitehead --- askar-crypto/src/alg/aesgcm.rs | 21 +++--- askar-crypto/src/alg/any.rs | 60 ++++++++++++++++- askar-crypto/src/alg/chacha20.rs | 21 +++--- askar-crypto/src/buffer/secret.rs | 37 +++++++++-- askar-crypto/src/encrypt/mod.rs | 21 +++--- src/ffi/key.rs | 85 ++++++++++++++++++++----- src/ffi/mod.rs | 12 +--- src/ffi/secret.rs | 50 +++++++++++++++ src/key.rs | 34 ++++++++++ src/protect/profile_key.rs | 2 +- wrappers/python/aries_askar/bindings.py | 68 ++++++++++++++++---- wrappers/python/demo/test.py | 12 ++++ 12 files changed, 344 insertions(+), 79 deletions(-) create mode 100644 src/ffi/secret.rs diff --git a/askar-crypto/src/alg/aesgcm.rs b/askar-crypto/src/alg/aesgcm.rs index cff5f75e..3e070f68 100644 --- a/askar-crypto/src/alg/aesgcm.rs +++ b/askar-crypto/src/alg/aesgcm.rs @@ -8,7 +8,7 @@ use zeroize::Zeroize; use super::{AesTypes, HasKeyAlg, KeyAlg}; use crate::{ buffer::{ArrayKey, ResizeBuffer, Writer}, - encrypt::{KeyAeadInPlace, KeyAeadMeta}, + encrypt::{KeyAeadInPlace, KeyAeadMeta, KeyAeadParams}, error::Error, generic_array::{typenum::Unsigned, GenericArray}, jwk::{JwkEncoder, ToJwk}, @@ -130,9 +130,9 @@ impl KeyAeadMeta for AesGcmKey { impl KeyAeadInPlace for AesGcmKey { /// Encrypt a secret value in place, appending the verification tag - fn encrypt_in_place( + fn encrypt_in_place( &self, - buffer: &mut B, + buffer: &mut dyn ResizeBuffer, nonce: &[u8], aad: &[u8], ) -> Result<(), Error> { @@ -149,9 +149,9 @@ impl KeyAeadInPlace for AesGcmKey { } /// Decrypt an encrypted (verification tag appended) value in place - fn decrypt_in_place( + fn decrypt_in_place( &self, - buffer: &mut B, + buffer: &mut dyn ResizeBuffer, nonce: &[u8], aad: &[u8], ) -> Result<(), Error> { @@ -174,12 +174,11 @@ impl KeyAeadInPlace for AesGcmKey { Ok(()) } - fn nonce_length() -> usize { - NonceSize::::USIZE - } - - fn tag_length() -> usize { - TagSize::::USIZE + fn aead_params(&self) -> KeyAeadParams { + KeyAeadParams { + nonce_length: NonceSize::::USIZE, + tag_length: TagSize::::USIZE, + } } } diff --git a/askar-crypto/src/alg/any.rs b/askar-crypto/src/alg/any.rs index 5570b55f..60137d21 100644 --- a/askar-crypto/src/alg/any.rs +++ b/askar-crypto/src/alg/any.rs @@ -12,7 +12,8 @@ use super::p256::{self, P256KeyPair}; use super::x25519::{self, X25519KeyPair}; use super::{AesTypes, Chacha20Types, EcCurves, HasKeyAlg, KeyAlg}; use crate::{ - buffer::WriteBuffer, + buffer::{ResizeBuffer, WriteBuffer}, + encrypt::{KeyAeadInPlace, KeyAeadParams}, error::Error, jwk::{FromJwk, JwkEncoder, JwkParts, ToJwk}, kdf::{FromKeyExchange, KeyExchange}, @@ -295,6 +296,47 @@ macro_rules! match_key_types { }; } +impl AnyKey { + fn key_as_aead(&self) -> Result<&dyn KeyAeadInPlace, Error> { + Ok(match_key_types! { + self, + AesGcmKey, + AesGcmKey, + Chacha20Key, + Chacha20Key; + "AEAD is not supported for this key type" + }) + } +} + +impl KeyAeadInPlace for AnyKey { + fn encrypt_in_place( + &self, + buffer: &mut dyn ResizeBuffer, + nonce: &[u8], + aad: &[u8], + ) -> Result<(), Error> { + self.key_as_aead()?.encrypt_in_place(buffer, nonce, aad) + } + + fn decrypt_in_place( + &self, + buffer: &mut dyn ResizeBuffer, + nonce: &[u8], + aad: &[u8], + ) -> Result<(), Error> { + self.key_as_aead()?.decrypt_in_place(buffer, nonce, aad) + } + + fn aead_params(&self) -> KeyAeadParams { + if let Ok(key) = self.key_as_aead() { + key.aead_params() + } else { + KeyAeadParams::default() + } + } +} + impl ToJwk for AnyKey { fn to_jwk_encoder(&self, enc: &mut JwkEncoder<'_>) -> Result<(), Error> { let key: &dyn ToJwk = match_key_types! { @@ -383,7 +425,7 @@ impl KeyAsAny for K { mod tests { use super::*; - // FIXME - add a custom key type to test the wrapper + // FIXME - add a custom key type for testing, to allow feature independence #[test] fn ed25519_as_any() { @@ -405,4 +447,18 @@ mod tests { Box::::from_key_exchange(KeyAlg::Aes(AesTypes::A256GCM), &*alice, &*bob) .unwrap(); } + + #[test] + fn key_encrypt_any() { + use crate::buffer::SecretBytes; + let message = b"test message"; + let mut data = SecretBytes::from(&message[..]); + + let key = Box::::generate(KeyAlg::Chacha20(Chacha20Types::XC20P)).unwrap(); + let nonce = [0u8; 24]; // size varies by algorithm + key.encrypt_in_place(&mut data, &nonce, &[]).unwrap(); + assert_ne!(data, &message[..]); + key.decrypt_in_place(&mut data, &nonce, &[]).unwrap(); + assert_eq!(data, &message[..]); + } } diff --git a/askar-crypto/src/alg/chacha20.rs b/askar-crypto/src/alg/chacha20.rs index bb90a71a..bc764064 100644 --- a/askar-crypto/src/alg/chacha20.rs +++ b/askar-crypto/src/alg/chacha20.rs @@ -8,7 +8,7 @@ use zeroize::Zeroize; use super::{Chacha20Types, HasKeyAlg, KeyAlg}; use crate::{ buffer::{ArrayKey, ResizeBuffer, Writer}, - encrypt::{KeyAeadInPlace, KeyAeadMeta}, + encrypt::{KeyAeadInPlace, KeyAeadMeta, KeyAeadParams}, error::Error, generic_array::{typenum::Unsigned, GenericArray}, jwk::{JwkEncoder, ToJwk}, @@ -152,9 +152,9 @@ impl KeyAeadMeta for Chacha20Key { impl KeyAeadInPlace for Chacha20Key { /// Encrypt a secret value in place, appending the verification tag - fn encrypt_in_place( + fn encrypt_in_place( &self, - buffer: &mut B, + buffer: &mut dyn ResizeBuffer, nonce: &[u8], aad: &[u8], ) -> Result<(), Error> { @@ -171,9 +171,9 @@ impl KeyAeadInPlace for Chacha20Key { } /// Decrypt an encrypted (verification tag appended) value in place - fn decrypt_in_place( + fn decrypt_in_place( &self, - buffer: &mut B, + buffer: &mut dyn ResizeBuffer, nonce: &[u8], aad: &[u8], ) -> Result<(), Error> { @@ -196,12 +196,11 @@ impl KeyAeadInPlace for Chacha20Key { Ok(()) } - fn nonce_length() -> usize { - NonceSize::::USIZE - } - - fn tag_length() -> usize { - TagSize::::USIZE + fn aead_params(&self) -> KeyAeadParams { + KeyAeadParams { + nonce_length: NonceSize::::USIZE, + tag_length: TagSize::::USIZE, + } } } diff --git a/askar-crypto/src/buffer/secret.rs b/askar-crypto/src/buffer/secret.rs index 565f5e17..a6b9cab1 100644 --- a/askar-crypto/src/buffer/secret.rs +++ b/askar-crypto/src/buffer/secret.rs @@ -1,4 +1,4 @@ -use alloc::{string::String, vec::Vec}; +use alloc::{boxed::Box, string::String, vec::Vec}; use core::{ fmt::{self, Debug, Formatter}, mem, @@ -12,7 +12,7 @@ use super::{string::MaybeStr, ResizeBuffer, WriteBuffer}; use crate::error::Error; /// A heap-allocated, zeroized byte buffer -#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Zeroize)] +#[derive(Clone, Default, Hash, PartialEq, Eq, PartialOrd, Ord, Zeroize)] pub struct SecretBytes(Vec); impl SecretBytes { @@ -59,14 +59,34 @@ impl SecretBytes { } } + #[inline] + pub fn extend_from_slice(&mut self, data: &[u8]) { + self.reserve(data.len()); + self.0.extend_from_slice(data); + } + #[inline] pub fn reserve(&mut self, extra: usize) { self.ensure_capacity(self.len() + extra) } + pub fn into_boxed_slice(mut self) -> Box<[u8]> { + let len = self.0.len(); + if self.0.capacity() > len { + // copy to a smaller buffer (capacity is not tracked for boxed slice) + // and proceed with the normal zeroize on drop + let mut v = Vec::with_capacity(len); + v.append(&mut self.0); + v.into_boxed_slice() + } else { + // no realloc and copy needed + self.into_vec().into_boxed_slice() + } + } + #[inline] pub fn into_vec(mut self) -> Vec { - // FIXME zeroize extra capacity? + // FIXME zeroize extra capacity in case it was used previously? let mut v = Vec::new(); // note: no heap allocation for empty vec mem::swap(&mut v, &mut self.0); mem::forget(self); @@ -149,6 +169,12 @@ impl From for SecretBytes { } } +impl From> for SecretBytes { + fn from(inner: Box<[u8]>) -> Self { + Self(inner.into()) + } +} + impl From> for SecretBytes { fn from(inner: Vec) -> Self { Self(inner) @@ -169,10 +195,7 @@ impl PartialEq> for SecretBytes { impl WriteBuffer for SecretBytes { fn buffer_write(&mut self, data: &[u8]) -> Result<(), Error> { - let pos = self.0.len(); - let new_len = pos + data.len(); - self.buffer_resize(new_len)?; - self.0[pos..new_len].copy_from_slice(data); + self.extend_from_slice(data); Ok(()) } } diff --git a/askar-crypto/src/encrypt/mod.rs b/askar-crypto/src/encrypt/mod.rs index 4b973040..e78fbb65 100644 --- a/askar-crypto/src/encrypt/mod.rs +++ b/askar-crypto/src/encrypt/mod.rs @@ -11,26 +11,23 @@ pub mod nacl_box; /// Trait for key types which perform AEAD encryption pub trait KeyAeadInPlace { /// Encrypt a secret value in place, appending the verification tag - fn encrypt_in_place( + fn encrypt_in_place( &self, - buffer: &mut B, + buffer: &mut dyn ResizeBuffer, nonce: &[u8], aad: &[u8], ) -> Result<(), Error>; /// Decrypt an encrypted (verification tag appended) value in place - fn decrypt_in_place( + fn decrypt_in_place( &self, - buffer: &mut B, + buffer: &mut dyn ResizeBuffer, nonce: &[u8], aad: &[u8], ) -> Result<(), Error>; - /// Get the required nonce length for encryption - fn nonce_length() -> usize; - - /// Get the length of the verification tag - fn tag_length() -> usize; + /// Get the nonce and tag length for encryption + fn aead_params(&self) -> KeyAeadParams; } /// For concrete key types with fixed nonce and tag sizes @@ -44,3 +41,9 @@ pub trait KeyAeadMeta { nonce } } + +#[derive(Clone, Copy, Default, PartialEq, Eq)] +pub struct KeyAeadParams { + pub nonce_length: usize, + pub tag_length: usize, +} diff --git a/src/ffi/key.rs b/src/ffi/key.rs index 7e3b7d00..fd712609 100644 --- a/src/ffi/key.rs +++ b/src/ffi/key.rs @@ -2,7 +2,7 @@ use std::os::raw::c_char; use ffi_support::{rust_string_to_c, ByteBuffer, FfiStr}; -use super::{handle::ArcHandle, ErrorCode}; +use super::{handle::ArcHandle, secret::SecretBuffer, ErrorCode}; use crate::key::LocalKey; pub type LocalKeyHandle = ArcHandle; @@ -93,7 +93,6 @@ pub extern "C" fn askar_key_get_jwk_public( ) -> ErrorCode { catch_err! { trace!("Get key JWK public: {}", handle); - handle.validate()?; check_useful_c_ptr!(out); let key = handle.load()?; let jwk = key.to_jwk_public()?; @@ -102,18 +101,70 @@ pub extern "C" fn askar_key_get_jwk_public( } } -// #[no_mangle] -// pub extern "C" fn askar_key_get_jwk_secret( -// handle: LocalKeyHandle, -// out: *mut FfiSecret, -// ) -> ErrorCode { -// catch_err! { -// trace!("Get key JWK secret: {}", handle); -// handle.validate()?; -// check_useful_c_ptr!(out); -// let key = handle.load()?; -// let jwk = key.to_jwk_secret()?; -// unsafe { *out = FfiSecret::from(jwk) }; -// Ok(ErrorCode::Success) -// } -// } +#[no_mangle] +pub extern "C" fn askar_key_get_jwk_secret( + handle: LocalKeyHandle, + out: *mut SecretBuffer, +) -> ErrorCode { + catch_err! { + trace!("Get key JWK secret: {}", handle); + check_useful_c_ptr!(out); + let key = handle.load()?; + let jwk = key.to_jwk_secret()?; + unsafe { *out = SecretBuffer::from_secret(jwk) }; + Ok(ErrorCode::Success) + } +} + +// key_aead_get_params (nonce len, tag len) + +#[no_mangle] +pub extern "C" fn askar_key_aead_random_nonce( + handle: LocalKeyHandle, + out: *mut SecretBuffer, +) -> ErrorCode { + catch_err! { + trace!("AEAD create nonce: {}", handle); + check_useful_c_ptr!(out); + let key = handle.load()?; + let nonce = key.aead_random_nonce()?; + unsafe { *out = SecretBuffer::from_secret(nonce) }; + Ok(ErrorCode::Success) + } +} + +#[no_mangle] +pub extern "C" fn askar_key_aead_encrypt( + handle: LocalKeyHandle, + input: ByteBuffer, + nonce: ByteBuffer, + aad: ByteBuffer, + out: *mut SecretBuffer, +) -> ErrorCode { + catch_err! { + trace!("AEAD encrypt: {}", handle); + check_useful_c_ptr!(out); + let key = handle.load()?; + let enc = key.aead_encrypt(input.as_slice(), nonce.as_slice(), aad.as_slice())?; + unsafe { *out = SecretBuffer::from_secret(enc) }; + Ok(ErrorCode::Success) + } +} + +#[no_mangle] +pub extern "C" fn askar_key_aead_decrypt( + handle: LocalKeyHandle, + input: ByteBuffer, + nonce: ByteBuffer, + aad: ByteBuffer, + out: *mut SecretBuffer, +) -> ErrorCode { + catch_err! { + trace!("AEAD decrypt: {}", handle); + check_useful_c_ptr!(out); + let key = handle.load()?; + let dec = key.aead_decrypt(input.as_slice(), nonce.as_slice(), aad.as_slice())?; + unsafe { *out = SecretBuffer::from_secret(dec) }; + Ok(ErrorCode::Success) + } +} diff --git a/src/ffi/mod.rs b/src/ffi/mod.rs index 571e5f25..f120a856 100644 --- a/src/ffi/mod.rs +++ b/src/ffi/mod.rs @@ -1,8 +1,7 @@ use std::marker::PhantomData; use std::os::raw::c_char; -use ffi_support::{rust_string_to_c, ByteBuffer}; -use zeroize::Zeroize; +use ffi_support::rust_string_to_c; #[cfg(feature = "jemalloc")] #[global_allocator] @@ -22,6 +21,8 @@ mod key; mod log; +mod secret; + mod store; use self::error::ErrorCode; @@ -31,13 +32,6 @@ pub type CallbackId = i64; ffi_support::define_string_destructor!(askar_string_free); -#[no_mangle] -pub extern "C" fn askar_buffer_free(buffer: ByteBuffer) { - ffi_support::abort_on_panic::with_abort_on_panic(|| { - drop(buffer.destroy_into_vec().zeroize()); - }) -} - pub struct EnsureCallback)> { f: F, _pd: PhantomData, diff --git a/src/ffi/secret.rs b/src/ffi/secret.rs new file mode 100644 index 00000000..e5f1b9e8 --- /dev/null +++ b/src/ffi/secret.rs @@ -0,0 +1,50 @@ +use std::{convert::TryFrom, mem, ptr}; + +use crate::crypto::buffer::SecretBytes; + +#[no_mangle] +pub extern "C" fn askar_buffer_free(buffer: SecretBuffer) { + ffi_support::abort_on_panic::with_abort_on_panic(|| { + drop(buffer.destroy_into_secret()); + }) +} + +// Structure consistent with ffi_support ByteBuffer, but zeroized on drop +#[repr(C)] +pub struct SecretBuffer { + // must be >= 0, signed int was chosen for compatibility + len: i64, + // nullable + data: *mut u8, +} + +impl Default for SecretBuffer { + fn default() -> Self { + Self { + len: 0, + data: ptr::null_mut(), + } + } +} + +impl SecretBuffer { + pub fn from_secret(buffer: impl Into) -> Self { + let mut buf = buffer.into().into_boxed_slice(); + let len = i64::try_from(buf.len()).expect("secret length exceeds i64::MAX"); + let data = buf.as_mut_ptr(); + mem::forget(buf); + Self { len, data } + } + + pub fn destroy_into_secret(self) -> SecretBytes { + if self.data.is_null() { + SecretBytes::default() + } else { + if self.len < 0 { + panic!("found negative length for secret buffer"); + } + let len = self.len as usize; + SecretBytes::from(unsafe { Vec::from_raw_parts(self.data, len, len) }) + } + } +} diff --git a/src/key.rs b/src/key.rs index 48538b61..58d24335 100644 --- a/src/key.rs +++ b/src/key.rs @@ -4,7 +4,9 @@ use crate::{ crypto::{ alg::{AnyKey, AnyKeyCreate, KeyAlg}, buffer::SecretBytes, + encrypt::KeyAeadInPlace, jwk::{FromJwk, ToJwk}, + random::fill_random, }, error::Error, storage::entry::{Entry, EntryTag}, @@ -101,6 +103,38 @@ impl LocalKey { // FIXME add special case for BLS G1+G2 (two prints) Ok(self.inner.to_jwk_thumbprint()?) } + + pub fn aead_random_nonce(&self) -> Result, Error> { + let nonce_len = self.inner.aead_params().nonce_length; + if nonce_len == 0 { + return Err(err_msg!( + Unsupported, + "Key type does not support AEAD encryption" + )); + } + let buf = SecretBytes::new_with(nonce_len, fill_random); + Ok(buf.into_vec()) + } + + pub fn aead_encrypt(&self, message: &[u8], nonce: &[u8], aad: &[u8]) -> Result, Error> { + let params = self.inner.aead_params(); + let enc_size = message.len() + params.nonce_length + params.tag_length; + let mut buf = SecretBytes::with_capacity(enc_size); + buf.extend_from_slice(message); + self.inner.encrypt_in_place(&mut buf, nonce, aad)?; + Ok(buf.into_vec()) + } + + pub fn aead_decrypt( + &self, + message: &[u8], + nonce: &[u8], + aad: &[u8], + ) -> Result { + let mut buf = SecretBytes::from_slice(message); + self.inner.decrypt_in_place(&mut buf, nonce, aad)?; + Ok(buf) + } } /// A stored key entry diff --git a/src/protect/profile_key.rs b/src/protect/profile_key.rs index 24be85a8..f2cc7eb8 100644 --- a/src/protect/profile_key.rs +++ b/src/protect/profile_key.rs @@ -101,7 +101,7 @@ where } pub fn decrypt(ciphertext: Vec, enc_key: &Key) -> Result { - let nonce_len = Key::nonce_length(); + let nonce_len = Key::NonceSize::USIZE; if ciphertext.len() < nonce_len { return Err(err_msg!(Encryption, "invalid encrypted value")); } diff --git a/wrappers/python/aries_askar/bindings.py b/wrappers/python/aries_askar/bindings.py index 042c99d2..1e14faee 100644 --- a/wrappers/python/aries_askar/bindings.py +++ b/wrappers/python/aries_askar/bindings.py @@ -195,14 +195,6 @@ def __del__(self): get_library().askar_string_free(self) -class lib_unpack_result(Structure): - _fields_ = [ - ("unpacked", ByteBuffer), - ("recipient", StrBuffer), - ("sender", StrBuffer), - ] - - def get_library() -> CDLL: """Return the CDLL instance, loading it if necessary.""" global LIB @@ -389,7 +381,11 @@ def encode_str(arg: Optional[Union[str, bytes]]) -> c_char_p: return c_char_p(arg) -def encode_bytes(arg: Optional[Union[str, bytes]]) -> FfiByteBuffer: +def encode_bytes( + arg: Optional[Union[str, bytes, ByteBuffer, FfiByteBuffer]] +) -> Union[FfiByteBuffer, ByteBuffer]: + if isinstance(arg, ByteBuffer) or isinstance(arg, FfiByteBuffer): + return arg buf = FfiByteBuffer() if isinstance(arg, memoryview): buf.len = arg.nbytes @@ -747,6 +743,54 @@ def key_get_algorithm(handle: LocalKeyHandle) -> str: def key_get_jwk_public(handle: LocalKeyHandle) -> str: - alg = StrBuffer() - do_call("askar_key_get_jwk_public", handle, byref(alg)) - return str(alg) + jwk = StrBuffer() + do_call("askar_key_get_jwk_public", handle, byref(jwk)) + return str(jwk) + + +def key_get_jwk_secret(handle: LocalKeyHandle) -> ByteBuffer: + sec = ByteBuffer() + do_call("askar_key_get_jwk_public", handle, byref(sec)) + return sec + + +def key_aead_random_nonce(handle: LocalKeyHandle) -> ByteBuffer: + nonce = ByteBuffer() + do_call("askar_key_aead_random_nonce", handle, byref(nonce)) + return nonce + + +def key_aead_encrypt( + handle: LocalKeyHandle, + input: Union[bytes, str, ByteBuffer], + nonce: Union[bytes, ByteBuffer], + aad: Union[bytes, ByteBuffer] = None, +) -> ByteBuffer: + enc = ByteBuffer() + do_call( + "askar_key_aead_encrypt", + handle, + encode_bytes(input), + encode_bytes(nonce), + encode_bytes(aad), + byref(enc), + ) + return enc + + +def key_aead_decrypt( + handle: LocalKeyHandle, + input: Union[bytes, ByteBuffer], + nonce: Union[bytes, ByteBuffer], + aad: Union[bytes, ByteBuffer] = None, +) -> ByteBuffer: + dec = ByteBuffer() + do_call( + "askar_key_aead_decrypt", + handle, + encode_bytes(input), + encode_bytes(nonce), + encode_bytes(aad), + byref(dec), + ) + return dec diff --git a/wrappers/python/demo/test.py b/wrappers/python/demo/test.py index 59bb9259..ce506f9b 100644 --- a/wrappers/python/demo/test.py +++ b/wrappers/python/demo/test.py @@ -72,9 +72,21 @@ async def basic_test(): key = bindings.key_generate("ed25519") log("Created key:", key) + + log("Key algorithm:", bindings.key_get_algorithm(key)) + jwk = bindings.key_get_jwk_public(key) log("JWK:", jwk) + key = bindings.key_generate("aes256gcm") + log("Key algorithm:", bindings.key_get_algorithm(key)) + + data = b"test message" + nonce = bindings.key_aead_random_nonce(key) + enc = bindings.key_aead_encrypt(key, data, nonce, b"aad") + dec = bindings.key_aead_decrypt(key, enc, nonce, b"aad") + assert data == bytes(dec) + # test key operations in a new session async with store as session: # # Create a new keypair From 5241030d29ee89742d6d5140811e05e2e588d165 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Wed, 21 Apr 2021 08:44:23 -0700 Subject: [PATCH 050/116] expose key derivation ops via python wrapper Signed-off-by: Andrew Whitehead --- askar-crypto/src/alg/aesgcm.rs | 13 ++- askar-crypto/src/alg/any.rs | 46 +++++++- askar-crypto/src/alg/chacha20.rs | 26 ++--- askar-crypto/src/alg/k256.rs | 2 +- askar-crypto/src/alg/mod.rs | 72 +++++++++--- askar-crypto/src/alg/p256.rs | 2 +- askar-crypto/src/alg/x25519.rs | 2 +- askar-crypto/src/kdf/ecdh_1pu.rs | 101 +++++++---------- askar-crypto/src/kdf/ecdh_es.rs | 95 +++++++--------- askar-crypto/src/kdf/mod.rs | 12 +- askar-crypto/src/lib.rs | 4 +- askar-crypto/src/repr.rs | 12 +- src/ffi/key.rs | 141 ++++++++++++++++++++++-- src/key.rs | 58 ++++++++-- wrappers/python/aries_askar/__init__.py | 4 + wrappers/python/aries_askar/bindings.py | 98 +++++++++++++++- wrappers/python/aries_askar/key.py | 90 +++++++++++++++ wrappers/python/aries_askar/types.py | 13 ++- wrappers/python/demo/test.py | 62 +++++++---- 19 files changed, 641 insertions(+), 212 deletions(-) create mode 100644 wrappers/python/aries_askar/key.py diff --git a/askar-crypto/src/alg/aesgcm.rs b/askar-crypto/src/alg/aesgcm.rs index 3e070f68..7f975279 100644 --- a/askar-crypto/src/alg/aesgcm.rs +++ b/askar-crypto/src/alg/aesgcm.rs @@ -12,7 +12,7 @@ use crate::{ error::Error, generic_array::{typenum::Unsigned, GenericArray}, jwk::{JwkEncoder, ToJwk}, - kdf::{FromKeyExchange, KeyExchange}, + kdf::{FromKeyDerivation, FromKeyExchange, KeyDerivation, KeyExchange}, repr::{KeyGen, KeyMeta, KeySecretBytes}, }; @@ -123,6 +123,17 @@ impl KeySecretBytes for AesGcmKey { } } +impl FromKeyDerivation for AesGcmKey { + fn from_key_derivation(mut derive: D) -> Result + where + Self: Sized, + { + let mut key = KeyType::::default(); + derive.derive_key_bytes(key.as_mut())?; + Ok(Self(key)) + } +} + impl KeyAeadMeta for AesGcmKey { type NonceSize = NonceSize; type TagSize = TagSize; diff --git a/askar-crypto/src/alg/any.rs b/askar-crypto/src/alg/any.rs index 60137d21..b3a3789a 100644 --- a/askar-crypto/src/alg/any.rs +++ b/askar-crypto/src/alg/any.rs @@ -16,7 +16,7 @@ use crate::{ encrypt::{KeyAeadInPlace, KeyAeadParams}, error::Error, jwk::{FromJwk, JwkEncoder, JwkParts, ToJwk}, - kdf::{FromKeyExchange, KeyExchange}, + kdf::{FromKeyDerivation, FromKeyExchange, KeyDerivation, KeyExchange}, repr::{KeyGen, KeyPublicBytes, KeySecretBytes}, sign::{KeySigVerify, KeySign, SignatureType}, }; @@ -65,6 +65,8 @@ pub trait AnyKeyCreate: Sized { where Sk: KeyExchange + ?Sized, Pk: ?Sized; + + fn from_key_derivation(alg: KeyAlg, derive: impl KeyDerivation) -> Result; } impl AnyKeyCreate for Box { @@ -92,6 +94,10 @@ impl AnyKeyCreate for Box { { from_key_exchange_any(alg, secret, public) } + + fn from_key_derivation(alg: KeyAlg, derive: impl KeyDerivation) -> Result { + from_key_derivation_any(alg, derive) + } } impl AnyKeyCreate for Arc { @@ -119,6 +125,10 @@ impl AnyKeyCreate for Arc { { from_key_exchange_any(alg, secret, public) } + + fn from_key_derivation(alg: KeyAlg, derive: impl KeyDerivation) -> Result { + from_key_derivation_any(alg, derive) + } } #[inline] @@ -228,12 +238,36 @@ where } } +#[inline] +fn from_key_derivation_any( + alg: KeyAlg, + derive: impl KeyDerivation, +) -> Result { + match alg { + KeyAlg::Aes(AesTypes::A128GCM) => { + AesGcmKey::::from_key_derivation(derive).map(R::alloc_key) + } + KeyAlg::Aes(AesTypes::A256GCM) => { + AesGcmKey::::from_key_derivation(derive).map(R::alloc_key) + } + KeyAlg::Chacha20(Chacha20Types::C20P) => { + Chacha20Key::::from_key_derivation(derive).map(R::alloc_key) + } + KeyAlg::Chacha20(Chacha20Types::XC20P) => { + Chacha20Key::::from_key_derivation(derive).map(R::alloc_key) + } + #[allow(unreachable_patterns)] + _ => { + return Err(err_msg!( + Unsupported, + "Unsupported algorithm for key import" + )) + } + } +} + impl KeyExchange for AnyKey { - fn key_exchange_buffer( - &self, - other: &AnyKey, - out: &mut B, - ) -> Result<(), Error> { + fn key_exchange_buffer(&self, other: &AnyKey, out: &mut dyn WriteBuffer) -> Result<(), Error> { match self.key_type_id() { s if s != other.key_type_id() => Err(err_msg!(Unsupported, "Unsupported key exchange")), s if s == TypeId::of::() => Ok(self diff --git a/askar-crypto/src/alg/chacha20.rs b/askar-crypto/src/alg/chacha20.rs index bc764064..cfc7d3de 100644 --- a/askar-crypto/src/alg/chacha20.rs +++ b/askar-crypto/src/alg/chacha20.rs @@ -12,7 +12,7 @@ use crate::{ error::Error, generic_array::{typenum::Unsigned, GenericArray}, jwk::{JwkEncoder, ToJwk}, - kdf::{FromKeyExchange, KeyExchange}, + kdf::{FromKeyDerivation, FromKeyExchange, KeyDerivation, KeyExchange}, random::fill_random_deterministic, repr::{KeyGen, KeyMeta, KeySecretBytes}, }; @@ -121,7 +121,7 @@ impl KeyGen for Chacha20Key { impl KeySecretBytes for Chacha20Key { fn from_secret_bytes(key: &[u8]) -> Result { - if key.len() != ::KeySize::USIZE { + if key.len() != KeyType::::SIZE { return Err(err_msg!(InvalidKeyData)); } Ok(Self(KeyType::::from_slice(key))) @@ -132,18 +132,16 @@ impl KeySecretBytes for Chacha20Key { } } -// impl KeySecretBytes for Box> { -// fn from_key_secret_bytes(key: &[u8]) -> Result { -// if key.len() != ::KeySize::USIZE { -// return Err(err_msg!("Invalid length for chacha20 key")); -// } -// Ok(init_boxed(|buf| buf.copy_from_slice(key))) -// } - -// fn to_key_secret_buffer(&self, out: &mut B) -> Result<(), Error> { -// out.write_slice(&self.0[..]) -// } -// } +impl FromKeyDerivation for Chacha20Key { + fn from_key_derivation(mut derive: D) -> Result + where + Self: Sized, + { + let mut key = KeyType::::default(); + derive.derive_key_bytes(key.as_mut())?; + Ok(Self(key)) + } +} impl KeyAeadMeta for Chacha20Key { type NonceSize = NonceSize; diff --git a/askar-crypto/src/alg/k256.rs b/askar-crypto/src/alg/k256.rs index 87aaad3a..b7b63aa8 100644 --- a/askar-crypto/src/alg/k256.rs +++ b/askar-crypto/src/alg/k256.rs @@ -259,7 +259,7 @@ impl FromJwk for K256KeyPair { } impl KeyExchange for K256KeyPair { - fn key_exchange_buffer(&self, other: &Self, out: &mut B) -> Result<(), Error> { + fn key_exchange_buffer(&self, other: &Self, out: &mut dyn WriteBuffer) -> Result<(), Error> { match self.secret.as_ref() { Some(sk) => { let xk = diffie_hellman(sk.secret_scalar(), other.public.as_affine()); diff --git a/askar-crypto/src/alg/mod.rs b/askar-crypto/src/alg/mod.rs index 492ab4ed..efb544a8 100644 --- a/askar-crypto/src/alg/mod.rs +++ b/askar-crypto/src/alg/mod.rs @@ -68,22 +68,64 @@ impl FromStr for KeyAlg { type Err = Error; fn from_str(s: &str) -> Result { - Ok(match s { - "aes128gcm" => Self::Aes(AesTypes::A128GCM), - "aes256gcm" => Self::Aes(AesTypes::A256GCM), - "chacha20poly1305" => Self::Chacha20(Chacha20Types::C20P), - "xchacha20poly1305" => Self::Chacha20(Chacha20Types::XC20P), - "c20p" => Self::Chacha20(Chacha20Types::C20P), - "xc20p" => Self::Chacha20(Chacha20Types::XC20P), - "ed25519" => Self::Ed25519, - "x25519" => Self::X25519, - "k256" => Self::EcCurve(EcCurves::Secp256k1), - "p256" => Self::EcCurve(EcCurves::Secp256r1), - "secp256k1" => Self::EcCurve(EcCurves::Secp256k1), - "secp256r1" => Self::EcCurve(EcCurves::Secp256r1), - _ => return Err(err_msg!(Unsupported, "Unknown key algorithm")), - }) + if match_alg(s, &["a128gcm", "aes128gcm"]) { + return Ok(Self::Aes(AesTypes::A128GCM)); + } + if match_alg(s, &["a256gcm", "aes256gcm"]) { + return Ok(Self::Aes(AesTypes::A256GCM)); + } + if match_alg(s, &["c20p", "chacha20poly1305"]) { + return Ok(Self::Chacha20(Chacha20Types::C20P)); + } + if match_alg(s, &["xc20p", "xchacha20poly1305"]) { + return Ok(Self::Chacha20(Chacha20Types::XC20P)); + } + if match_alg(s, &["ed25519"]) { + return Ok(Self::Ed25519); + } + if match_alg(s, &["x25519"]) { + return Ok(Self::X25519); + } + if match_alg(s, &["k256", "secp256k1"]) { + return Ok(Self::EcCurve(EcCurves::Secp256k1)); + } + if match_alg(s, &["p256", "secp256r1"]) { + return Ok(Self::EcCurve(EcCurves::Secp256r1)); + } + Err(err_msg!(Unsupported, "Unknown key algorithm")) + } +} + +struct NormalizeAlg<'a> { + chars: core::str::Chars<'a>, +} + +fn normalize_alg(alg: &str) -> NormalizeAlg<'_> { + NormalizeAlg { chars: alg.chars() } +} + +impl Iterator for NormalizeAlg<'_> { + type Item = char; + fn next(&mut self) -> Option { + while let Some(c) = self.chars.next() { + if c != '-' && c != '_' { + return Some(c.to_ascii_lowercase()); + } + } + None + } +} + +fn match_alg(alg: &str, pats: &[&str]) -> bool { + 'pats: for pat in pats { + for (a, b) in pat.chars().zip(normalize_alg(alg)) { + if a != b { + continue 'pats; + } + } + return true; } + false } impl Display for KeyAlg { diff --git a/askar-crypto/src/alg/p256.rs b/askar-crypto/src/alg/p256.rs index 50cf8cc7..ec0242f6 100644 --- a/askar-crypto/src/alg/p256.rs +++ b/askar-crypto/src/alg/p256.rs @@ -259,7 +259,7 @@ impl FromJwk for P256KeyPair { } impl KeyExchange for P256KeyPair { - fn key_exchange_buffer(&self, other: &Self, out: &mut B) -> Result<(), Error> { + fn key_exchange_buffer(&self, other: &Self, out: &mut dyn WriteBuffer) -> Result<(), Error> { match self.secret.as_ref() { Some(sk) => { let xk = diffie_hellman(sk.secret_scalar(), other.public.as_affine()); diff --git a/askar-crypto/src/alg/x25519.rs b/askar-crypto/src/alg/x25519.rs index 93e6c691..2995a159 100644 --- a/askar-crypto/src/alg/x25519.rs +++ b/askar-crypto/src/alg/x25519.rs @@ -221,7 +221,7 @@ impl FromJwk for X25519KeyPair { } impl KeyExchange for X25519KeyPair { - fn key_exchange_buffer(&self, other: &Self, out: &mut B) -> Result<(), Error> { + fn key_exchange_buffer(&self, other: &Self, out: &mut dyn WriteBuffer) -> Result<(), Error> { match self.secret.as_ref() { Some(sk) => { let xk = sk.diffie_hellman(&other.public); diff --git a/askar-crypto/src/kdf/ecdh_1pu.rs b/askar-crypto/src/kdf/ecdh_1pu.rs index b2549c1d..b7787817 100644 --- a/askar-crypto/src/kdf/ecdh_1pu.rs +++ b/askar-crypto/src/kdf/ecdh_1pu.rs @@ -1,31 +1,43 @@ -use core::marker::PhantomData; - use sha2::Sha256; use zeroize::Zeroize; use super::{ concat::{ConcatKDFHash, ConcatKDFParams}, - KeyExchange, -}; -use crate::{ - buffer::WriteBuffer, - error::Error, - jwk::{JwkEncoder, JwkEncoderMode, ToJwk}, - repr::KeyGen, + KeyDerivation, KeyExchange, }; +use crate::error::Error; + +pub struct Ecdh1PU<'d, Key: KeyExchange + ?Sized> { + ephem_key: &'d Key, + send_key: &'d Key, + recip_key: &'d Key, + alg: &'d [u8], + apu: &'d [u8], + apv: &'d [u8], +} -pub struct Ecdh1PU(PhantomData); +impl<'d, Key: KeyExchange + ?Sized> Ecdh1PU<'d, Key> { + pub fn new( + ephem_key: &'d Key, + send_key: &'d Key, + recip_key: &'d Key, + alg: &'d [u8], + apu: &'d [u8], + apv: &'d [u8], + ) -> Self { + Self { + ephem_key, + send_key, + recip_key, + alg, + apu, + apv, + } + } +} -impl Ecdh1PU { - fn derive_key_config( - ephem_key: &Key, - send_key: &Key, - recip_key: &Key, - alg: &[u8], - apu: &[u8], - apv: &[u8], - key_output: &mut [u8], - ) -> Result<(), Error> { +impl KeyDerivation for Ecdh1PU<'_, Key> { + fn derive_key_bytes(&mut self, key_output: &mut [u8]) -> Result<(), Error> { let output_len = key_output.len(); // one-pass KDF only produces 256 bits of output assert!(output_len <= 32); @@ -33,13 +45,15 @@ impl Ecdh1PU { kdf.start_pass(); // hash Zs and Ze directly into the KDF - ephem_key.key_exchange_buffer(recip_key, &mut kdf)?; - send_key.key_exchange_buffer(recip_key, &mut kdf)?; + self.ephem_key + .key_exchange_buffer(self.recip_key, &mut kdf)?; + self.send_key + .key_exchange_buffer(self.recip_key, &mut kdf)?; kdf.hash_params(ConcatKDFParams { - alg, - apu, - apv, + alg: self.alg, + apu: self.apu, + apv: self.apv, pub_info: &(256u32).to_be_bytes(), // output length in bits prv_info: &[], }); @@ -50,30 +64,6 @@ impl Ecdh1PU { Ok(()) } - - pub fn derive_key( - send_key: &Key, - recip_key: &Key, - alg: &[u8], - apu: &[u8], - apv: &[u8], - key_output: &mut [u8], - jwk_output: &mut B, - ) -> Result<(), Error> - where - Key: KeyGen + ToJwk, - { - let ephem_key = Key::generate()?; - let mut encoder = JwkEncoder::new(jwk_output, JwkEncoderMode::PublicKey)?; - ephem_key.to_jwk_encoder(&mut encoder)?; - - Self::derive_key_config(&ephem_key, send_key, recip_key, alg, apu, apv, key_output)?; - - // SECURITY: keys must zeroize themselves on drop - drop(ephem_key); - - Ok(()) - } } #[cfg(test)] @@ -113,16 +103,9 @@ mod tests { let mut key_output = [0u8; 32]; - Ecdh1PU::derive_key_config( - &ephem_sk, - &alice_sk, - &bob_sk, - b"A256GCM", - b"Alice", - b"Bob", - &mut key_output, - ) - .unwrap(); + Ecdh1PU::new(&ephem_sk, &alice_sk, &bob_sk, b"A256GCM", b"Alice", b"Bob") + .derive_key_bytes(&mut key_output) + .unwrap(); assert_eq!( key_output, diff --git a/askar-crypto/src/kdf/ecdh_es.rs b/askar-crypto/src/kdf/ecdh_es.rs index a507c66b..fb61a618 100644 --- a/askar-crypto/src/kdf/ecdh_es.rs +++ b/askar-crypto/src/kdf/ecdh_es.rs @@ -1,30 +1,43 @@ -use core::marker::PhantomData; - use sha2::Sha256; use zeroize::Zeroize; use super::{ concat::{ConcatKDFHash, ConcatKDFParams}, - KeyExchange, -}; -use crate::{ - buffer::WriteBuffer, - error::Error, - jwk::{JwkEncoder, JwkEncoderMode, ToJwk}, - repr::KeyGen, + KeyDerivation, KeyExchange, }; +use crate::error::Error; + +pub struct EcdhEs<'d, Key> +where + Key: KeyExchange + ?Sized, +{ + ephem_key: &'d Key, + recip_key: &'d Key, + alg: &'d [u8], + apu: &'d [u8], + apv: &'d [u8], +} -pub struct EcdhEs(PhantomData); +impl<'d, Key: KeyExchange + ?Sized> EcdhEs<'d, Key> { + pub fn new( + ephem_key: &'d Key, + recip_key: &'d Key, + alg: &'d [u8], + apu: &'d [u8], + apv: &'d [u8], + ) -> Self { + Self { + ephem_key, + recip_key, + alg, + apu, + apv, + } + } +} -impl EcdhEs { - fn derive_key_config( - ephem_key: &Key, - recip_key: &Key, - alg: &[u8], - apu: &[u8], - apv: &[u8], - key_output: &mut [u8], - ) -> Result<(), Error> { +impl KeyDerivation for EcdhEs<'_, Key> { + fn derive_key_bytes(&mut self, key_output: &mut [u8]) -> Result<(), Error> { let output_len = key_output.len(); // one-pass KDF only produces 256 bits of output assert!(output_len <= 32); @@ -32,12 +45,13 @@ impl EcdhEs { kdf.start_pass(); // hash Z directly into the KDF - ephem_key.key_exchange_buffer(recip_key, &mut kdf)?; + self.ephem_key + .key_exchange_buffer(self.recip_key, &mut kdf)?; kdf.hash_params(ConcatKDFParams { - alg, - apu, - apv, + alg: self.alg, + apu: self.apu, + apv: self.apv, pub_info: &(256u32).to_be_bytes(), // output length in bits prv_info: &[], }); @@ -48,29 +62,6 @@ impl EcdhEs { Ok(()) } - - pub fn derive_key( - recip_key: &Key, - alg: &[u8], - apu: &[u8], - apv: &[u8], - key_output: &mut [u8], - jwk_output: &mut B, - ) -> Result<(), Error> - where - Key: KeyGen + ToJwk, - { - let ephem_key = Key::generate()?; - let mut encoder = JwkEncoder::new(jwk_output, JwkEncoderMode::PublicKey)?; - ephem_key.to_jwk_encoder(&mut encoder)?; - - Self::derive_key_config(&ephem_key, recip_key, alg, apu, apv, key_output)?; - - // SECURITY: keys must zeroize themselves on drop - drop(ephem_key); - - Ok(()) - } } #[cfg(test)] @@ -105,15 +96,9 @@ mod tests { let mut key_output = [0u8; 32]; - EcdhEs::derive_key_config( - &ephem_sk, - &bob_pk, - b"A256GCM", - b"Alice", - b"Bob", - &mut key_output, - ) - .unwrap(); + EcdhEs::new(&ephem_sk, &bob_pk, b"A256GCM", b"Alice", b"Bob") + .derive_key_bytes(&mut key_output) + .unwrap(); assert_eq!( key_output, diff --git a/askar-crypto/src/kdf/mod.rs b/askar-crypto/src/kdf/mod.rs index fce782ab..06fe548e 100644 --- a/askar-crypto/src/kdf/mod.rs +++ b/askar-crypto/src/kdf/mod.rs @@ -11,7 +11,7 @@ pub mod ecdh_1pu; pub mod ecdh_es; pub trait KeyExchange { - fn key_exchange_buffer(&self, other: &Rhs, out: &mut B) -> Result<(), Error>; + fn key_exchange_buffer(&self, other: &Rhs, out: &mut dyn WriteBuffer) -> Result<(), Error>; #[cfg(feature = "alloc")] fn key_exchange_bytes(&self, other: &Rhs) -> Result { @@ -24,3 +24,13 @@ pub trait KeyExchange { pub trait FromKeyExchange: Sized { fn from_key_exchange(lhs: &Lhs, rhs: &Rhs) -> Result; } + +pub trait KeyDerivation { + fn derive_key_bytes(&mut self, key_output: &mut [u8]) -> Result<(), Error>; +} + +pub trait FromKeyDerivation { + fn from_key_derivation(derive: D) -> Result + where + Self: Sized; +} diff --git a/askar-crypto/src/lib.rs b/askar-crypto/src/lib.rs index 12e3cc1a..ec8265f8 100644 --- a/askar-crypto/src/lib.rs +++ b/askar-crypto/src/lib.rs @@ -37,6 +37,4 @@ pub mod sign; pub use self::sign::{KeySigVerify, KeySign, SignatureType}; pub mod repr; -pub use self::repr::{ - KeyGen, KeyGenInPlace, KeyMeta, KeyPublicBytes, KeySecretBytes, KeypairBytes, KeypairMeta, -}; +pub use self::repr::{KeyGen, KeyMeta, KeyPublicBytes, KeySecretBytes, KeypairBytes, KeypairMeta}; diff --git a/askar-crypto/src/repr.rs b/askar-crypto/src/repr.rs index ec49ccad..9caeccb5 100644 --- a/askar-crypto/src/repr.rs +++ b/askar-crypto/src/repr.rs @@ -3,14 +3,10 @@ use crate::buffer::SecretBytes; use crate::{buffer::WriteBuffer, error::Error, generic_array::ArrayLength}; /// Generate a new random key. -pub trait KeyGen: Sized { - fn generate() -> Result; -} - -/// Allows a key to be created uninitialized and populated later, -/// for instance when nested inside another struct. -pub trait KeyGenInPlace { - unsafe fn generate_in_place(this: *mut Self) -> Result<(), Error>; +pub trait KeyGen { + fn generate() -> Result + where + Self: Sized; } /// Convert between key instance and key secret bytes. diff --git a/src/ffi/key.rs b/src/ffi/key.rs index fd712609..45986d15 100644 --- a/src/ffi/key.rs +++ b/src/ffi/key.rs @@ -1,9 +1,9 @@ -use std::os::raw::c_char; +use std::{os::raw::c_char, str::FromStr}; use ffi_support::{rust_string_to_c, ByteBuffer, FfiStr}; use super::{handle::ArcHandle, secret::SecretBuffer, ErrorCode}; -use crate::key::LocalKey; +use crate::key::{Ecdh1PU, EcdhEs, KeyAlg, LocalKey}; pub type LocalKeyHandle = ArcHandle; @@ -16,7 +16,8 @@ pub extern "C" fn askar_key_generate( catch_err! { trace!("Generate key: {}", alg.as_str()); check_useful_c_ptr!(out); - let key = LocalKey::generate(alg.as_str(), ephemeral != 0)?; + let alg = KeyAlg::from_str(alg.as_str())?; + let key = LocalKey::generate(alg, ephemeral != 0)?; let handle = LocalKeyHandle::create(key); unsafe { *out = handle }; Ok(ErrorCode::Success) @@ -44,7 +45,8 @@ pub extern "C" fn askar_key_from_public_bytes( catch_err! { trace!("Load key from public: {}", alg.as_str()); check_useful_c_ptr!(out); - let key = LocalKey::from_public_bytes(alg.as_str(), public.as_slice())?; + let alg = KeyAlg::from_str(alg.as_str())?; + let key = LocalKey::from_public_bytes(alg, public.as_slice())?; let handle = LocalKeyHandle::create(key); unsafe { *out = handle }; Ok(ErrorCode::Success) @@ -60,7 +62,8 @@ pub extern "C" fn askar_key_from_secret_bytes( catch_err! { trace!("Load key from secret: {}", alg.as_str()); check_useful_c_ptr!(out); - let key = LocalKey::from_secret_bytes(alg.as_str(), secret.as_slice())?; + let alg = KeyAlg::from_str(alg.as_str())?; + let key = LocalKey::from_secret_bytes(alg, secret.as_slice())?; let handle = LocalKeyHandle::create(key); unsafe { *out = handle }; Ok(ErrorCode::Success) @@ -86,6 +89,17 @@ pub extern "C" fn askar_key_get_algorithm( } } +#[no_mangle] +pub extern "C" fn askar_key_get_ephemeral(handle: LocalKeyHandle, out: *mut i8) -> ErrorCode { + catch_err! { + trace!("Get key ephemeral: {}", handle); + check_useful_c_ptr!(out); + let key = handle.load()?; + unsafe { *out = key.ephemeral as i8 }; + Ok(ErrorCode::Success) + } +} + #[no_mangle] pub extern "C" fn askar_key_get_jwk_public( handle: LocalKeyHandle, @@ -116,6 +130,21 @@ pub extern "C" fn askar_key_get_jwk_secret( } } +#[no_mangle] +pub extern "C" fn askar_key_get_jwk_thumbprint( + handle: LocalKeyHandle, + out: *mut *const c_char, +) -> ErrorCode { + catch_err! { + trace!("Get key JWK thumbprint: {}", handle); + check_useful_c_ptr!(out); + let key = handle.load()?; + let thumb = key.to_jwk_thumbprint()?; + unsafe { *out = rust_string_to_c(thumb) }; + Ok(ErrorCode::Success) + } +} + // key_aead_get_params (nonce len, tag len) #[no_mangle] @@ -136,7 +165,7 @@ pub extern "C" fn askar_key_aead_random_nonce( #[no_mangle] pub extern "C" fn askar_key_aead_encrypt( handle: LocalKeyHandle, - input: ByteBuffer, + message: ByteBuffer, nonce: ByteBuffer, aad: ByteBuffer, out: *mut SecretBuffer, @@ -145,7 +174,7 @@ pub extern "C" fn askar_key_aead_encrypt( trace!("AEAD encrypt: {}", handle); check_useful_c_ptr!(out); let key = handle.load()?; - let enc = key.aead_encrypt(input.as_slice(), nonce.as_slice(), aad.as_slice())?; + let enc = key.aead_encrypt(message.as_slice(), nonce.as_slice(), aad.as_slice())?; unsafe { *out = SecretBuffer::from_secret(enc) }; Ok(ErrorCode::Success) } @@ -154,7 +183,7 @@ pub extern "C" fn askar_key_aead_encrypt( #[no_mangle] pub extern "C" fn askar_key_aead_decrypt( handle: LocalKeyHandle, - input: ByteBuffer, + ciphertext: ByteBuffer, nonce: ByteBuffer, aad: ByteBuffer, out: *mut SecretBuffer, @@ -163,8 +192,102 @@ pub extern "C" fn askar_key_aead_decrypt( trace!("AEAD decrypt: {}", handle); check_useful_c_ptr!(out); let key = handle.load()?; - let dec = key.aead_decrypt(input.as_slice(), nonce.as_slice(), aad.as_slice())?; + let dec = key.aead_decrypt(ciphertext.as_slice(), nonce.as_slice(), aad.as_slice())?; unsafe { *out = SecretBuffer::from_secret(dec) }; Ok(ErrorCode::Success) } } + +#[no_mangle] +pub extern "C" fn askar_key_sign_message( + handle: LocalKeyHandle, + message: ByteBuffer, + sig_type: FfiStr<'_>, + out: *mut SecretBuffer, +) -> ErrorCode { + catch_err! { + trace!("Sign message: {}", handle); + check_useful_c_ptr!(out); + let key = handle.load()?; + let sig = key.sign_message(message.as_slice(), sig_type.as_opt_str())?; + unsafe { *out = SecretBuffer::from_secret(sig) }; + Ok(ErrorCode::Success) + } +} + +#[no_mangle] +pub extern "C" fn askar_key_verify_signature( + handle: LocalKeyHandle, + message: ByteBuffer, + signature: ByteBuffer, + sig_type: FfiStr<'_>, + out: *mut i8, +) -> ErrorCode { + catch_err! { + trace!("Verify signature: {}", handle); + check_useful_c_ptr!(out); + let key = handle.load()?; + let verify = key.verify_signature(message.as_slice(),signature.as_slice(), sig_type.as_opt_str())?; + unsafe { *out = verify as i8 }; + Ok(ErrorCode::Success) + } +} + +#[no_mangle] +pub extern "C" fn askar_key_derive_ecdh_es( + alg: FfiStr<'_>, + ephem_key: LocalKeyHandle, + recip_key: LocalKeyHandle, + apu: ByteBuffer, + apv: ByteBuffer, + out: *mut LocalKeyHandle, +) -> ErrorCode { + catch_err! { + trace!("ECDH-ES: {}", alg.as_str()); + check_useful_c_ptr!(out); + let key_alg = KeyAlg::from_str(alg.as_str())?; + let ephem_key = ephem_key.load()?; + let recip_key = recip_key.load()?; + let derive = EcdhEs::new( + &*ephem_key, + &*recip_key, + alg.as_str().as_bytes(), + apu.as_slice(), + apv.as_slice() + ); + let key = LocalKey::from_key_derivation(key_alg, derive)?; + unsafe { *out = LocalKeyHandle::create(key) }; + Ok(ErrorCode::Success) + } +} + +#[no_mangle] +pub extern "C" fn askar_key_derive_ecdh_1pu( + alg: FfiStr<'_>, + ephem_key: LocalKeyHandle, + sender_key: LocalKeyHandle, + recip_key: LocalKeyHandle, + apu: ByteBuffer, + apv: ByteBuffer, + out: *mut LocalKeyHandle, +) -> ErrorCode { + catch_err! { + trace!("ECDH-1PU: {}", alg.as_str()); + check_useful_c_ptr!(out); + let key_alg = KeyAlg::from_str(alg.as_str())?; + let ephem_key = ephem_key.load()?; + let sender_key = sender_key.load()?; + let recip_key = recip_key.load()?; + let derive = Ecdh1PU::new( + &*ephem_key, + &*sender_key, + &*recip_key, + alg.as_str().as_bytes(), + apu.as_slice(), + apv.as_slice() + ); + let key = LocalKey::from_key_derivation(key_alg, derive)?; + unsafe { *out = LocalKeyHandle::create(key) }; + Ok(ErrorCode::Success) + } +} diff --git a/src/key.rs b/src/key.rs index 58d24335..f7689196 100644 --- a/src/key.rs +++ b/src/key.rs @@ -1,12 +1,16 @@ -use std::str::FromStr; - +pub use crate::crypto::{ + alg::KeyAlg, + buffer::{SecretBytes, WriteBuffer}, + kdf::{ecdh_1pu::Ecdh1PU, ecdh_es::EcdhEs, KeyDerivation, KeyExchange}, +}; use crate::{ crypto::{ - alg::{AnyKey, AnyKeyCreate, KeyAlg}, - buffer::SecretBytes, + alg::{AnyKey, AnyKeyCreate}, encrypt::KeyAeadInPlace, jwk::{FromJwk, ToJwk}, random::fill_random, + sign::{KeySigVerify, KeySign}, + Error as CryptoError, }, error::Error, storage::entry::{Entry, EntryTag}, @@ -51,8 +55,7 @@ pub struct LocalKey { impl LocalKey { /// Create a new random key or keypair - pub fn generate(alg: &str, ephemeral: bool) -> Result { - let alg = KeyAlg::from_str(alg)?; + pub fn generate(alg: KeyAlg, ephemeral: bool) -> Result { let inner = Box::::generate(alg)?; Ok(Self { inner, ephemeral }) } @@ -65,8 +68,7 @@ impl LocalKey { }) } - pub fn from_public_bytes(alg: &str, public: &[u8]) -> Result { - let alg = KeyAlg::from_str(alg)?; + pub fn from_public_bytes(alg: KeyAlg, public: &[u8]) -> Result { let inner = Box::::from_public_bytes(alg, public)?; Ok(Self { inner, @@ -74,8 +76,7 @@ impl LocalKey { }) } - pub fn from_secret_bytes(alg: &str, secret: &[u8]) -> Result { - let alg = KeyAlg::from_str(alg)?; + pub fn from_secret_bytes(alg: KeyAlg, secret: &[u8]) -> Result { let inner = Box::::from_secret_bytes(alg, secret)?; Ok(Self { inner, @@ -83,6 +84,14 @@ impl LocalKey { }) } + pub fn from_key_derivation(alg: KeyAlg, derive: impl KeyDerivation) -> Result { + let inner = Box::::from_key_derivation(alg, derive)?; + Ok(Self { + inner, + ephemeral: false, + }) + } + pub(crate) fn encode(&self) -> Result { Ok(self.inner.to_jwk_secret()?) } @@ -127,14 +136,39 @@ impl LocalKey { pub fn aead_decrypt( &self, - message: &[u8], + ciphertext: &[u8], nonce: &[u8], aad: &[u8], ) -> Result { - let mut buf = SecretBytes::from_slice(message); + let mut buf = SecretBytes::from_slice(ciphertext); self.inner.decrypt_in_place(&mut buf, nonce, aad)?; Ok(buf) } + + pub fn sign_message(&self, message: &[u8], sig_type: Option<&str>) -> Result, Error> { + let mut sig = Vec::new(); + self.inner.write_signature(message, None, &mut sig)?; + Ok(sig) + } + + pub fn verify_signature( + &self, + message: &[u8], + signature: &[u8], + sig_type: Option<&str>, + ) -> Result { + Ok(self.inner.verify_signature(message, signature, None)?) + } +} + +impl KeyExchange for LocalKey { + fn key_exchange_buffer( + &self, + other: &LocalKey, + out: &mut dyn WriteBuffer, + ) -> Result<(), CryptoError> { + self.inner.key_exchange_buffer(&other.inner, out) + } } /// A stored key entry diff --git a/wrappers/python/aries_askar/__init__.py b/wrappers/python/aries_askar/__init__.py index bddb50a7..c2ec37be 100644 --- a/wrappers/python/aries_askar/__init__.py +++ b/wrappers/python/aries_askar/__init__.py @@ -2,13 +2,17 @@ from .bindings import generate_raw_key, version from .error import StoreError, StoreErrorCode +from .key import Key, derive_key_ecdh_1pu, derive_key_ecdh_es from .store import Session, Store from .types import Entry, KeyAlg __all__ = ( + "derive_key_ecdh_1pu", + "derive_key_ecdh_es", "generate_raw_key", "version", "Entry", + "Key", "KeyAlg", "Session", "Store", diff --git a/wrappers/python/aries_askar/bindings.py b/wrappers/python/aries_askar/bindings.py index 1e14faee..ad49c927 100644 --- a/wrappers/python/aries_askar/bindings.py +++ b/wrappers/python/aries_askar/bindings.py @@ -24,7 +24,7 @@ from typing import Optional, Union from .error import StoreError, StoreErrorCode -from .types import Entry, EntryOperation +from .types import Entry, EntryOperation, KeyAlg CALLBACKS = {} @@ -724,8 +724,10 @@ def __del__(self): get_library().askar_key_free(self) -def key_generate(alg: str, ephemeral: bool = False) -> LocalKeyHandle: +def key_generate(alg: Union[str, KeyAlg], ephemeral: bool = False) -> LocalKeyHandle: handle = LocalKeyHandle() + if isinstance(alg, KeyAlg): + alg = alg.value do_call("askar_key_generate", encode_str(alg), c_int8(ephemeral), byref(handle)) return handle @@ -742,6 +744,12 @@ def key_get_algorithm(handle: LocalKeyHandle) -> str: return str(alg) +def key_get_ephemeral(handle: LocalKeyHandle) -> bool: + eph = c_int8() + do_call("askar_key_get_ephemeral", handle, byref(eph)) + return bool(eph) + + def key_get_jwk_public(handle: LocalKeyHandle) -> str: jwk = StrBuffer() do_call("askar_key_get_jwk_public", handle, byref(jwk)) @@ -754,6 +762,12 @@ def key_get_jwk_secret(handle: LocalKeyHandle) -> ByteBuffer: return sec +def key_get_jwk_thumbprint(handle: LocalKeyHandle) -> str: + thumb = StrBuffer() + do_call("askar_key_get_jwk_thumbprint", handle, byref(thumb)) + return str(thumb) + + def key_aead_random_nonce(handle: LocalKeyHandle) -> ByteBuffer: nonce = ByteBuffer() do_call("askar_key_aead_random_nonce", handle, byref(nonce)) @@ -794,3 +808,83 @@ def key_aead_decrypt( byref(dec), ) return dec + + +def key_sign_message( + handle: LocalKeyHandle, + message: Union[bytes, str, ByteBuffer], + sig_type: str = None, +) -> ByteBuffer: + sig = ByteBuffer() + do_call( + "askar_key_sign_message", + handle, + encode_bytes(message), + encode_str(sig_type), + byref(sig), + ) + return sig + + +def key_verify_signature( + handle: LocalKeyHandle, + message: Union[bytes, str, ByteBuffer], + signature: Union[bytes, ByteBuffer], + sig_type: str = None, +) -> bool: + verify = c_int8() + do_call( + "askar_key_verify_signature", + handle, + encode_bytes(message), + encode_bytes(signature), + encode_str(sig_type), + byref(verify), + ) + return bool(verify) + + +def key_derive_ecdh_es( + alg: Union[str, KeyAlg], + ephem_key: LocalKeyHandle, + recip_key: LocalKeyHandle, + apu: Union[bytes, str, ByteBuffer], + apv: Union[bytes, str, ByteBuffer], +) -> LocalKeyHandle: + key = LocalKeyHandle() + if isinstance(alg, KeyAlg): + alg = alg.value + do_call( + "askar_key_derive_ecdh_es", + encode_str(alg), + ephem_key, + recip_key, + encode_bytes(apu), + encode_bytes(apv), + byref(key), + ) + return key + + +def key_derive_ecdh_1pu( + alg: Union[str, KeyAlg], + ephem_key: LocalKeyHandle, + sender_key: LocalKeyHandle, + recip_key: LocalKeyHandle, + apu: Union[bytes, str, ByteBuffer], + apv: Union[bytes, str, ByteBuffer], +) -> LocalKeyHandle: + key = LocalKeyHandle() + if isinstance(alg, KeyAlg): + alg = alg.value + do_call( + "askar_key_derive_ecdh_1pu", + encode_str(alg), + ephem_key, + sender_key, + recip_key, + encode_bytes(apu), + encode_bytes(apv), + byref(key), + ) + return key diff --git a/wrappers/python/aries_askar/key.py b/wrappers/python/aries_askar/key.py new file mode 100644 index 00000000..79fcca8a --- /dev/null +++ b/wrappers/python/aries_askar/key.py @@ -0,0 +1,90 @@ +"""Handling of Key instances.""" + +from typing import Union + +from . import bindings + +from .types import KeyAlg + + +class Key: + """An active key or keypair instance.""" + + def __init__(self, handle: bindings.LocalKeyHandle): + """Initialize the Key instance.""" + self._handle = handle + + @property + def handle(self) -> bindings.LocalKeyHandle: + """Accessor for the key handle.""" + return self._handle + + @property + def algorithm(self) -> str: + return bindings.key_get_algorithm(self._handle) + + @property + def ephemeral(self) -> "Key": + return bindings.key_get_ephemeral(self._handle) + + @classmethod + def generate(cls, alg: Union[str, KeyAlg], *, ephemeral: bool = False) -> "Key": + return Key(bindings.key_generate(alg, ephemeral)) + + def get_jwk_public(self) -> str: + return bindings.key_get_jwk_public(self._handle) + + def get_jwk_secret(self) -> str: + return bindings.key_get_jwk_secret(self._handle) + + def get_jwk_thumbprint(self) -> str: + return bindings.key_get_jwk_thumbprint(self._handle) + + def aead_random_nonce(self) -> bytes: + return bytes(bindings.key_aead_random_nonce(self._handle)) + + def aead_encrypt( + self, message: Union[str, bytes], nonce: bytes, aad: bytes = None + ) -> bytes: + return bytes(bindings.key_aead_encrypt(self._handle, message, nonce, aad)) + + def aead_decrypt(self, message: bytes, nonce: bytes, aad: bytes = None) -> bytes: + return bytes(bindings.key_aead_decrypt(self._handle, message, nonce, aad)) + + def sign_message(self, message: Union[str, bytes], sig_type: str = None) -> bytes: + return bytes(bindings.key_sign_message(self._handle, message, sig_type)) + + def verify_signature( + self, message: Union[str, bytes], signature: bytes, sig_type: str = None + ) -> bool: + return bindings.key_verify_signature(self._handle, message, signature, sig_type) + + def __repr__(self) -> str: + return f"" + + +def derive_key_ecdh_1pu( + alg: str, + ephem_key: Key, + sender_key: Key, + recip_key: Key, + apu: Union[bytes, str], + apv: Union[bytes, str], +) -> Key: + return Key( + bindings.key_derive_ecdh_1pu( + alg, ephem_key._handle, sender_key._handle, recip_key._handle, apu, apv + ) + ) + + +def derive_key_ecdh_es( + alg: str, + ephem_key: Key, + recip_key: Key, + apu: Union[bytes, str], + apv: Union[bytes, str], +) -> Key: + return Key( + bindings.key_derive_ecdh_es(alg, ephem_key._handle, recip_key._handle, apu, apv) + ) diff --git a/wrappers/python/aries_askar/types.py b/wrappers/python/aries_askar/types.py index 47aed0b3..0f6ea7e7 100644 --- a/wrappers/python/aries_askar/types.py +++ b/wrappers/python/aries_askar/types.py @@ -1,10 +1,10 @@ import json as _json from enum import Enum -from typing import Mapping +from typing import Mapping, Union -def _make_binary(value: [str, bytes]) -> bytes: +def _make_binary(value: Union[str, bytes]) -> bytes: if isinstance(value, str): return value.encode("utf-8") else: @@ -16,7 +16,7 @@ def __init__( self, category: str, name: str, - value: [str, bytes], + value: Union[str, bytes], tags: Mapping[str, str] = None, ) -> "Entry": self.category = category @@ -77,7 +77,14 @@ def __repr__(self) -> str: class KeyAlg(Enum): + AES128GCM = "a128gcm" + AES256GCM = "a256gcm" + C20P = "c20p" + XC20P = "xc20p" ED25519 = "ed25519" + X25519 = "x25519" + K256 = "k256" + P256 = "p256" class EntryOperation(Enum): diff --git a/wrappers/python/demo/test.py b/wrappers/python/demo/test.py index ce506f9b..b932c4c3 100644 --- a/wrappers/python/demo/test.py +++ b/wrappers/python/demo/test.py @@ -2,13 +2,12 @@ import logging import os import sys -from aries_askar import bindings from aries_askar.bindings import ( generate_raw_key, version, ) -from aries_askar import Store +from aries_askar import KeyAlg, Key, Store, derive_key_ecdh_es, derive_key_ecdh_1pu logging.basicConfig(level=os.getenv("LOG_LEVEL", "").upper() or None) @@ -26,7 +25,44 @@ def log(*args): print(*args, "\n") -async def basic_test(): +def keys_test(): + + key = Key.generate(KeyAlg.ED25519) + log("Created key:", key) + message = b"test message" + sig = key.sign_message(message) + log("Signature:", sig) + verify = key.verify_signature(message, sig) + log("Verify:", verify) + + log("Key algorithm:", key.algorithm) + + jwk = key.get_jwk_public() + log("JWK:", jwk) + + key = Key.generate(KeyAlg.AES128GCM) + log("Key algorithm:", key.algorithm) + + data = b"test message" + nonce = key.aead_random_nonce() + enc = key.aead_encrypt(data, nonce, b"aad") + dec = key.aead_decrypt(enc, nonce, b"aad") + assert data == bytes(dec) + + ephem = Key.generate(KeyAlg.P256, ephemeral=True) + alice = Key.generate(KeyAlg.P256) + bob = Key.generate(KeyAlg.P256) + derived = derive_key_ecdh_1pu("A256GCM", ephem, alice, bob, "Alice", "Bob") + log("Derived:", derived.get_jwk_thumbprint()) + # derived = bindings.key_derive_ecdh_1pu("a256gcm", ephem, alice, bob, "Alice", "Bob") + # log("Derived:", bindings.key_get_jwk_thumbprint(derived)) + # derived = bindings.key_derive_ecdh_es("a256gcm", ephem, bob, "Alice", "Bob") + # log("Derived:", bindings.key_get_jwk_thumbprint(derived)) + derived = derive_key_ecdh_es("A256GCM", ephem, bob, "Alice", "Bob") + log("Derived:", derived.get_jwk_thumbprint()) + + +async def store_test(): if ENCRYPT: key = generate_raw_key(b"00000000000000000000000000000My1") key_method = "raw" @@ -70,23 +106,6 @@ async def basic_test(): async for row in store.scan("category", {"~plaintag": "a", "enctag": "b"}): log("Scan result:", row) - key = bindings.key_generate("ed25519") - log("Created key:", key) - - log("Key algorithm:", bindings.key_get_algorithm(key)) - - jwk = bindings.key_get_jwk_public(key) - log("JWK:", jwk) - - key = bindings.key_generate("aes256gcm") - log("Key algorithm:", bindings.key_get_algorithm(key)) - - data = b"test message" - nonce = bindings.key_aead_random_nonce(key) - enc = bindings.key_aead_encrypt(key, data, nonce, b"aad") - dec = bindings.key_aead_decrypt(key, enc, nonce, b"aad") - assert data == bytes(dec) - # test key operations in a new session async with store as session: # # Create a new keypair @@ -132,6 +151,7 @@ async def basic_test(): if __name__ == "__main__": log("aries-askar version:", version()) - asyncio.get_event_loop().run_until_complete(basic_test()) + keys_test() + asyncio.get_event_loop().run_until_complete(store_test()) log("done") From 81903d0fa9f52f14c9428045c77bc6b1e711a828 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Wed, 21 Apr 2021 09:54:09 -0700 Subject: [PATCH 051/116] expose ed25519->x25519 key conversion Signed-off-by: Andrew Whitehead --- askar-crypto/src/alg/any.rs | 28 +++++++++++++++++++++++++ askar-crypto/src/alg/x25519.rs | 12 +++++++++-- src/ffi/key.rs | 17 +++++++++++++++ src/key.rs | 8 +++++++ wrappers/python/aries_askar/bindings.py | 8 +++++++ wrappers/python/aries_askar/key.py | 3 +++ wrappers/python/demo/test.py | 2 ++ 7 files changed, 76 insertions(+), 2 deletions(-) diff --git a/askar-crypto/src/alg/any.rs b/askar-crypto/src/alg/any.rs index b3a3789a..3df703ad 100644 --- a/askar-crypto/src/alg/any.rs +++ b/askar-crypto/src/alg/any.rs @@ -1,6 +1,7 @@ use alloc::{boxed::Box, sync::Arc}; use core::{ any::{Any, TypeId}, + convert::TryFrom, fmt::Debug, }; @@ -67,6 +68,8 @@ pub trait AnyKeyCreate: Sized { Pk: ?Sized; fn from_key_derivation(alg: KeyAlg, derive: impl KeyDerivation) -> Result; + + fn convert_key(&self, alg: KeyAlg) -> Result; } impl AnyKeyCreate for Box { @@ -98,6 +101,10 @@ impl AnyKeyCreate for Box { fn from_key_derivation(alg: KeyAlg, derive: impl KeyDerivation) -> Result { from_key_derivation_any(alg, derive) } + + fn convert_key(&self, alg: KeyAlg) -> Result { + convert_key_any(self, alg) + } } impl AnyKeyCreate for Arc { @@ -129,6 +136,10 @@ impl AnyKeyCreate for Arc { fn from_key_derivation(alg: KeyAlg, derive: impl KeyDerivation) -> Result { from_key_derivation_any(alg, derive) } + + fn convert_key(&self, alg: KeyAlg) -> Result { + convert_key_any(self, alg) + } } #[inline] @@ -266,6 +277,23 @@ fn from_key_derivation_any( } } +#[inline] +fn convert_key_any(key: &AnyKey, alg: KeyAlg) -> Result { + match (key.algorithm(), alg) { + (KeyAlg::Ed25519, KeyAlg::X25519) => Ok(>::try_from( + key.assume::(), + ) + .map(R::alloc_key)?), + #[allow(unreachable_patterns)] + _ => { + return Err(err_msg!( + Unsupported, + "Unsupported key conversion operation" + )) + } + } +} + impl KeyExchange for AnyKey { fn key_exchange_buffer(&self, other: &AnyKey, out: &mut dyn WriteBuffer) -> Result<(), Error> { match self.key_type_id() { diff --git a/askar-crypto/src/alg/x25519.rs b/askar-crypto/src/alg/x25519.rs index 2995a159..d9cf0669 100644 --- a/askar-crypto/src/alg/x25519.rs +++ b/askar-crypto/src/alg/x25519.rs @@ -1,12 +1,12 @@ use core::{ - convert::TryInto, + convert::{TryFrom, TryInto}, fmt::{self, Debug, Formatter}, }; use x25519_dalek::{PublicKey, StaticSecret as SecretKey}; use zeroize::Zeroizing; -use super::{HasKeyAlg, KeyAlg}; +use super::{ed25519::Ed25519KeyPair, HasKeyAlg, KeyAlg}; use crate::{ buffer::{ArrayKey, WriteBuffer}, error::Error, @@ -233,6 +233,14 @@ impl KeyExchange for X25519KeyPair { } } +impl TryFrom<&Ed25519KeyPair> for X25519KeyPair { + type Error = Error; + + fn try_from(value: &Ed25519KeyPair) -> Result { + Ok(value.to_x25519_keypair()) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/ffi/key.rs b/src/ffi/key.rs index 45986d15..4febdc41 100644 --- a/src/ffi/key.rs +++ b/src/ffi/key.rs @@ -70,6 +70,23 @@ pub extern "C" fn askar_key_from_secret_bytes( } } +#[no_mangle] +pub extern "C" fn askar_key_convert( + handle: LocalKeyHandle, + alg: FfiStr<'_>, + out: *mut LocalKeyHandle, +) -> ErrorCode { + catch_err! { + trace!("Convert key: {} to {}", handle, alg.as_str()); + check_useful_c_ptr!(out); + let alg = KeyAlg::from_str(alg.as_str())?; + let key = handle.load()?.convert_key(alg)?; + let handle = LocalKeyHandle::create(key); + unsafe { *out = handle }; + Ok(ErrorCode::Success) + } +} + #[no_mangle] pub extern "C" fn askar_key_free(handle: LocalKeyHandle) { handle.remove(); diff --git a/src/key.rs b/src/key.rs index f7689196..ae3f2025 100644 --- a/src/key.rs +++ b/src/key.rs @@ -113,6 +113,14 @@ impl LocalKey { Ok(self.inner.to_jwk_thumbprint()?) } + pub fn convert_key(&self, alg: KeyAlg) -> Result { + let inner = self.inner.convert_key(alg)?; + Ok(Self { + inner, + ephemeral: self.ephemeral, + }) + } + pub fn aead_random_nonce(&self) -> Result, Error> { let nonce_len = self.inner.aead_params().nonce_length; if nonce_len == 0 { diff --git a/wrappers/python/aries_askar/bindings.py b/wrappers/python/aries_askar/bindings.py index ad49c927..b2cae583 100644 --- a/wrappers/python/aries_askar/bindings.py +++ b/wrappers/python/aries_askar/bindings.py @@ -738,6 +738,14 @@ def key_from_jwk(jwk: str) -> LocalKeyHandle: return handle +def key_convert(handle: LocalKeyHandle, alg: Union[str, KeyAlg]) -> LocalKeyHandle: + key = LocalKeyHandle() + if isinstance(alg, KeyAlg): + alg = alg.value + do_call("askar_key_convert", handle, encode_str(alg), byref(key)) + return key + + def key_get_algorithm(handle: LocalKeyHandle) -> str: alg = StrBuffer() do_call("askar_key_get_algorithm", handle, byref(alg)) diff --git a/wrappers/python/aries_askar/key.py b/wrappers/python/aries_askar/key.py index 79fcca8a..d8146324 100644 --- a/wrappers/python/aries_askar/key.py +++ b/wrappers/python/aries_askar/key.py @@ -31,6 +31,9 @@ def ephemeral(self) -> "Key": def generate(cls, alg: Union[str, KeyAlg], *, ephemeral: bool = False) -> "Key": return Key(bindings.key_generate(alg, ephemeral)) + def convert_key(self, alg: Union[str, KeyAlg]) -> "Key": + return Key(bindings.key_convert(self._handle, alg)) + def get_jwk_public(self) -> str: return bindings.key_get_jwk_public(self._handle) diff --git a/wrappers/python/demo/test.py b/wrappers/python/demo/test.py index b932c4c3..1ecb17d4 100644 --- a/wrappers/python/demo/test.py +++ b/wrappers/python/demo/test.py @@ -34,6 +34,8 @@ def keys_test(): log("Signature:", sig) verify = key.verify_signature(message, sig) log("Verify:", verify) + x25519_key = key.convert_key(KeyAlg.X25519) + log("Converted key:", x25519_key) log("Key algorithm:", key.algorithm) From 3327eaf0f83d1549c8eadffa6754f942f5665918 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Wed, 21 Apr 2021 10:14:58 -0700 Subject: [PATCH 052/116] encapsulate key derivation Signed-off-by: Andrew Whitehead --- src/ffi/key.rs | 23 +++-------------------- src/key.rs | 38 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 39 insertions(+), 22 deletions(-) diff --git a/src/ffi/key.rs b/src/ffi/key.rs index 4febdc41..bfffc921 100644 --- a/src/ffi/key.rs +++ b/src/ffi/key.rs @@ -3,7 +3,7 @@ use std::{os::raw::c_char, str::FromStr}; use ffi_support::{rust_string_to_c, ByteBuffer, FfiStr}; use super::{handle::ArcHandle, secret::SecretBuffer, ErrorCode}; -use crate::key::{Ecdh1PU, EcdhEs, KeyAlg, LocalKey}; +use crate::key::{derive_key_ecdh_1pu, derive_key_ecdh_es, KeyAlg, LocalKey}; pub type LocalKeyHandle = ArcHandle; @@ -262,17 +262,9 @@ pub extern "C" fn askar_key_derive_ecdh_es( catch_err! { trace!("ECDH-ES: {}", alg.as_str()); check_useful_c_ptr!(out); - let key_alg = KeyAlg::from_str(alg.as_str())?; let ephem_key = ephem_key.load()?; let recip_key = recip_key.load()?; - let derive = EcdhEs::new( - &*ephem_key, - &*recip_key, - alg.as_str().as_bytes(), - apu.as_slice(), - apv.as_slice() - ); - let key = LocalKey::from_key_derivation(key_alg, derive)?; + let key = derive_key_ecdh_es(&ephem_key, &recip_key, alg.as_str(), apu.as_slice(), apv.as_slice())?; unsafe { *out = LocalKeyHandle::create(key) }; Ok(ErrorCode::Success) } @@ -291,19 +283,10 @@ pub extern "C" fn askar_key_derive_ecdh_1pu( catch_err! { trace!("ECDH-1PU: {}", alg.as_str()); check_useful_c_ptr!(out); - let key_alg = KeyAlg::from_str(alg.as_str())?; let ephem_key = ephem_key.load()?; let sender_key = sender_key.load()?; let recip_key = recip_key.load()?; - let derive = Ecdh1PU::new( - &*ephem_key, - &*sender_key, - &*recip_key, - alg.as_str().as_bytes(), - apu.as_slice(), - apv.as_slice() - ); - let key = LocalKey::from_key_derivation(key_alg, derive)?; + let key = derive_key_ecdh_1pu(&ephem_key, &sender_key, &recip_key, alg.as_str(), apu.as_slice(), apv.as_slice())?; unsafe { *out = LocalKeyHandle::create(key) }; Ok(ErrorCode::Success) } diff --git a/src/key.rs b/src/key.rs index ae3f2025..6ba056f6 100644 --- a/src/key.rs +++ b/src/key.rs @@ -1,13 +1,15 @@ +use std::str::FromStr; + pub use crate::crypto::{ alg::KeyAlg, buffer::{SecretBytes, WriteBuffer}, - kdf::{ecdh_1pu::Ecdh1PU, ecdh_es::EcdhEs, KeyDerivation, KeyExchange}, }; use crate::{ crypto::{ alg::{AnyKey, AnyKeyCreate}, encrypt::KeyAeadInPlace, jwk::{FromJwk, ToJwk}, + kdf::{ecdh_1pu::Ecdh1PU, ecdh_es::EcdhEs, KeyDerivation, KeyExchange}, random::fill_random, sign::{KeySigVerify, KeySign}, Error as CryptoError, @@ -84,7 +86,7 @@ impl LocalKey { }) } - pub fn from_key_derivation(alg: KeyAlg, derive: impl KeyDerivation) -> Result { + fn from_key_derivation(alg: KeyAlg, derive: impl KeyDerivation) -> Result { let inner = Box::::from_key_derivation(alg, derive)?; Ok(Self { inner, @@ -179,6 +181,38 @@ impl KeyExchange for LocalKey { } } +pub fn derive_key_ecdh_1pu( + ephem_key: &LocalKey, + sender_key: &LocalKey, + recip_key: &LocalKey, + alg: &str, + apu: &[u8], + apv: &[u8], +) -> Result { + let key_alg = KeyAlg::from_str(alg)?; + let derive = Ecdh1PU::new( + &*ephem_key, + &*sender_key, + &*recip_key, + alg.as_bytes(), + apu, + apv, + ); + LocalKey::from_key_derivation(key_alg, derive) +} + +pub fn derive_key_ecdh_es( + ephem_key: &LocalKey, + recip_key: &LocalKey, + alg: &str, + apu: &[u8], + apv: &[u8], +) -> Result { + let key_alg = KeyAlg::from_str(alg)?; + let derive = EcdhEs::new(&*ephem_key, &*recip_key, alg.as_bytes(), apu, apv); + LocalKey::from_key_derivation(key_alg, derive) +} + /// A stored key entry #[derive(Clone, Debug, PartialEq, Eq)] pub struct KeyEntry { From 90ed0fa53cbeb2cb966a19dfd7eb21bc140ce24a Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Wed, 21 Apr 2021 11:50:11 -0700 Subject: [PATCH 053/116] expose key exchange, crypto box operations Signed-off-by: Andrew Whitehead --- src/ffi/key.rs | 71 +++++++++++++++++++++---- src/{key.rs => keys/mod.rs} | 37 ++++++++++++- src/lib.rs | 3 +- src/storage/types.rs | 5 +- wrappers/python/aries_askar/__init__.py | 10 +++- wrappers/python/aries_askar/bindings.py | 40 ++++++++++++++ wrappers/python/aries_askar/key.py | 17 ++++++ wrappers/python/demo/test.py | 19 ++++++- 8 files changed, 183 insertions(+), 19 deletions(-) rename src/{key.rs => keys/mod.rs} (88%) diff --git a/src/ffi/key.rs b/src/ffi/key.rs index bfffc921..853bf470 100644 --- a/src/ffi/key.rs +++ b/src/ffi/key.rs @@ -3,7 +3,10 @@ use std::{os::raw::c_char, str::FromStr}; use ffi_support::{rust_string_to_c, ByteBuffer, FfiStr}; use super::{handle::ArcHandle, secret::SecretBuffer, ErrorCode}; -use crate::key::{derive_key_ecdh_1pu, derive_key_ecdh_es, KeyAlg, LocalKey}; +use crate::keys::{ + crypto_box_seal, crypto_box_seal_open, derive_key_ecdh_1pu, derive_key_ecdh_es, KeyAlg, + LocalKey, +}; pub type LocalKeyHandle = ArcHandle; @@ -18,8 +21,7 @@ pub extern "C" fn askar_key_generate( check_useful_c_ptr!(out); let alg = KeyAlg::from_str(alg.as_str())?; let key = LocalKey::generate(alg, ephemeral != 0)?; - let handle = LocalKeyHandle::create(key); - unsafe { *out = handle }; + unsafe { *out = LocalKeyHandle::create(key) }; Ok(ErrorCode::Success) } } @@ -30,8 +32,7 @@ pub extern "C" fn askar_key_from_jwk(jwk: FfiStr<'_>, out: *mut LocalKeyHandle) trace!("Load key from JWK"); check_useful_c_ptr!(out); let key = LocalKey::from_jwk(jwk.as_str())?; - let handle = LocalKeyHandle::create(key); - unsafe { *out = handle }; + unsafe { *out = LocalKeyHandle::create(key) }; Ok(ErrorCode::Success) } } @@ -47,8 +48,7 @@ pub extern "C" fn askar_key_from_public_bytes( check_useful_c_ptr!(out); let alg = KeyAlg::from_str(alg.as_str())?; let key = LocalKey::from_public_bytes(alg, public.as_slice())?; - let handle = LocalKeyHandle::create(key); - unsafe { *out = handle }; + unsafe { *out = LocalKeyHandle::create(key) }; Ok(ErrorCode::Success) } } @@ -64,8 +64,7 @@ pub extern "C" fn askar_key_from_secret_bytes( check_useful_c_ptr!(out); let alg = KeyAlg::from_str(alg.as_str())?; let key = LocalKey::from_secret_bytes(alg, secret.as_slice())?; - let handle = LocalKeyHandle::create(key); - unsafe { *out = handle }; + unsafe { *out = LocalKeyHandle::create(key) }; Ok(ErrorCode::Success) } } @@ -81,8 +80,26 @@ pub extern "C" fn askar_key_convert( check_useful_c_ptr!(out); let alg = KeyAlg::from_str(alg.as_str())?; let key = handle.load()?.convert_key(alg)?; - let handle = LocalKeyHandle::create(key); - unsafe { *out = handle }; + unsafe { *out = LocalKeyHandle::create(key) }; + Ok(ErrorCode::Success) + } +} + +#[no_mangle] +pub extern "C" fn askar_key_from_key_exchange( + alg: FfiStr<'_>, + sk_handle: LocalKeyHandle, + pk_handle: LocalKeyHandle, + out: *mut LocalKeyHandle, +) -> ErrorCode { + catch_err! { + trace!("Key exchange: {}, {}", sk_handle, pk_handle); + check_useful_c_ptr!(out); + let alg = KeyAlg::from_str(alg.as_str())?; + let sk = sk_handle.load()?; + let pk = pk_handle.load()?; + let key = sk.to_key_exchange(alg, &pk)?; + unsafe { *out = LocalKeyHandle::create(key) }; Ok(ErrorCode::Success) } } @@ -250,6 +267,38 @@ pub extern "C" fn askar_key_verify_signature( } } +#[no_mangle] +pub extern "C" fn askar_key_crypto_box_seal( + handle: LocalKeyHandle, + message: ByteBuffer, + out: *mut SecretBuffer, +) -> ErrorCode { + catch_err! { + trace!("crypto box seal: {}", handle); + check_useful_c_ptr!(out); + let key = handle.load()?; + let enc = crypto_box_seal(&key, message.as_slice())?; + unsafe { *out = SecretBuffer::from_secret(enc) }; + Ok(ErrorCode::Success) + } +} + +#[no_mangle] +pub extern "C" fn askar_key_crypto_box_seal_open( + handle: LocalKeyHandle, + ciphertext: ByteBuffer, + out: *mut SecretBuffer, +) -> ErrorCode { + catch_err! { + trace!("crypto box seal open: {}", handle); + check_useful_c_ptr!(out); + let key = handle.load()?; + let enc = crypto_box_seal_open(&key, ciphertext.as_slice())?; + unsafe { *out = SecretBuffer::from_secret(enc) }; + Ok(ErrorCode::Success) + } +} + #[no_mangle] pub extern "C" fn askar_key_derive_ecdh_es( alg: FfiStr<'_>, diff --git a/src/key.rs b/src/keys/mod.rs similarity index 88% rename from src/key.rs rename to src/keys/mod.rs index 6ba056f6..67776485 100644 --- a/src/key.rs +++ b/src/keys/mod.rs @@ -6,8 +6,13 @@ pub use crate::crypto::{ }; use crate::{ crypto::{ - alg::{AnyKey, AnyKeyCreate}, - encrypt::KeyAeadInPlace, + alg::{x25519::X25519KeyPair, AnyKey, AnyKeyCreate}, + encrypt::{ + nacl_box::{ + crypto_box_seal as nacl_box_seal, crypto_box_seal_open as nacl_box_seal_open, + }, + KeyAeadInPlace, + }, jwk::{FromJwk, ToJwk}, kdf::{ecdh_1pu::Ecdh1PU, ecdh_es::EcdhEs, KeyDerivation, KeyExchange}, random::fill_random, @@ -86,6 +91,14 @@ impl LocalKey { }) } + pub fn to_key_exchange(&self, alg: KeyAlg, pk: &LocalKey) -> Result { + let inner = Box::::from_key_exchange(alg, &*self.inner, &*pk.inner)?; + Ok(Self { + inner, + ephemeral: self.ephemeral || pk.ephemeral, + }) + } + fn from_key_derivation(alg: KeyAlg, derive: impl KeyDerivation) -> Result { let inner = Box::::from_key_derivation(alg, derive)?; Ok(Self { @@ -181,6 +194,26 @@ impl KeyExchange for LocalKey { } } +pub fn crypto_box_seal(x25519_key: &LocalKey, message: &[u8]) -> Result, Error> { + if let Some(kp) = x25519_key.inner.downcast_ref::() { + let sealed = nacl_box_seal(kp, message)?; + Ok(sealed.into_vec()) + } else { + Err(err_msg!(Input, "x25519 keypair required")) + } +} + +pub fn crypto_box_seal_open( + x25519_key: &LocalKey, + ciphertext: &[u8], +) -> Result { + if let Some(kp) = x25519_key.inner.downcast_ref::() { + Ok(nacl_box_seal_open(kp, ciphertext)?) + } else { + Err(err_msg!(Input, "x25519 keypair required")) + } +} + pub fn derive_key_ecdh_1pu( ephem_key: &LocalKey, sender_key: &LocalKey, diff --git a/src/lib.rs b/src/lib.rs index 1274b9f1..87d971a1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,7 +40,8 @@ extern crate serde_json; #[cfg(feature = "ffi")] mod ffi; -mod key; +mod keys; +pub use keys::{derive_key_ecdh_1pu, derive_key_ecdh_es, KeyAlg, KeyEntry, LocalKey}; mod protect; pub use protect::{generate_raw_store_key, PassKey, StoreKeyMethod}; diff --git a/src/storage/types.rs b/src/storage/types.rs index d725d802..aaacfc81 100644 --- a/src/storage/types.rs +++ b/src/storage/types.rs @@ -12,7 +12,7 @@ use super::entry::{Entry, EntryKind, EntryOperation, EntryTag, TagFilter}; use crate::{ error::Error, future::BoxFuture, - key::{KeyEntry, KeyParams, LocalKey}, + keys::{KeyEntry, KeyParams, LocalKey}, protect::{PassKey, StoreKeyMethod}, }; @@ -567,9 +567,8 @@ impl Debug for Scan<'_, S> { /// Supported categories of KMS entries #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Zeroize)] pub(crate) enum KmsCategory { - /// A user managed key + /// A stored key or keypair CryptoKey, - // may want to manage certificates, passwords, etc } impl KmsCategory { diff --git a/wrappers/python/aries_askar/__init__.py b/wrappers/python/aries_askar/__init__.py index c2ec37be..976c2d0b 100644 --- a/wrappers/python/aries_askar/__init__.py +++ b/wrappers/python/aries_askar/__init__.py @@ -2,11 +2,19 @@ from .bindings import generate_raw_key, version from .error import StoreError, StoreErrorCode -from .key import Key, derive_key_ecdh_1pu, derive_key_ecdh_es +from .key import ( + Key, + crypto_box_seal, + crypto_box_seal_open, + derive_key_ecdh_1pu, + derive_key_ecdh_es, +) from .store import Session, Store from .types import Entry, KeyAlg __all__ = ( + "crypto_box_seal", + "crypto_box_seal_open", "derive_key_ecdh_1pu", "derive_key_ecdh_es", "generate_raw_key", diff --git a/wrappers/python/aries_askar/bindings.py b/wrappers/python/aries_askar/bindings.py index b2cae583..94a169ea 100644 --- a/wrappers/python/aries_askar/bindings.py +++ b/wrappers/python/aries_askar/bindings.py @@ -746,6 +746,18 @@ def key_convert(handle: LocalKeyHandle, alg: Union[str, KeyAlg]) -> LocalKeyHand return key +def key_exchange( + alg: Union[str, KeyAlg], sk_handle: LocalKeyHandle, pk_handle: LocalKeyHandle +) -> LocalKeyHandle: + key = LocalKeyHandle() + if isinstance(alg, KeyAlg): + alg = alg.value + do_call( + "askar_key_from_key_exchange", encode_str(alg), sk_handle, pk_handle, byref(key) + ) + return key + + def key_get_algorithm(handle: LocalKeyHandle) -> str: alg = StrBuffer() do_call("askar_key_get_algorithm", handle, byref(alg)) @@ -852,6 +864,34 @@ def key_verify_signature( return bool(verify) +def key_crypto_box_seal( + handle: LocalKeyHandle, + message: Union[bytes, str, ByteBuffer], +) -> ByteBuffer: + buf = ByteBuffer() + do_call( + "askar_key_crypto_box_seal", + handle, + encode_bytes(message), + byref(buf), + ) + return buf + + +def key_crypto_box_seal_open( + handle: LocalKeyHandle, + ciphertext: Union[bytes, ByteBuffer], +) -> ByteBuffer: + buf = ByteBuffer() + do_call( + "askar_key_crypto_box_seal_open", + handle, + encode_bytes(ciphertext), + byref(buf), + ) + return buf + + def key_derive_ecdh_es( alg: Union[str, KeyAlg], ephem_key: LocalKeyHandle, diff --git a/wrappers/python/aries_askar/key.py b/wrappers/python/aries_askar/key.py index d8146324..64063780 100644 --- a/wrappers/python/aries_askar/key.py +++ b/wrappers/python/aries_askar/key.py @@ -34,6 +34,9 @@ def generate(cls, alg: Union[str, KeyAlg], *, ephemeral: bool = False) -> "Key": def convert_key(self, alg: Union[str, KeyAlg]) -> "Key": return Key(bindings.key_convert(self._handle, alg)) + def key_exchange(self, alg: Union[str, KeyAlg], pk: "Key") -> "Key": + return Key(bindings.key_exchange(alg, self._handle, pk._handle)) + def get_jwk_public(self) -> str: return bindings.key_get_jwk_public(self._handle) @@ -66,6 +69,20 @@ def __repr__(self) -> str: return f"" +def crypto_box_seal( + key: Key, + message: Union[bytes, str], +) -> bytes: + return bytes(bindings.key_crypto_box_seal(key._handle, message)) + + +def crypto_box_seal_open( + key: Key, + ciphertext: bytes, +) -> bytes: + return bytes(bindings.key_crypto_box_seal_open(key._handle, ciphertext)) + + def derive_key_ecdh_1pu( alg: str, ephem_key: Key, diff --git a/wrappers/python/demo/test.py b/wrappers/python/demo/test.py index 1ecb17d4..09c07a6c 100644 --- a/wrappers/python/demo/test.py +++ b/wrappers/python/demo/test.py @@ -7,7 +7,15 @@ generate_raw_key, version, ) -from aries_askar import KeyAlg, Key, Store, derive_key_ecdh_es, derive_key_ecdh_1pu +from aries_askar import ( + KeyAlg, + Key, + Store, + crypto_box_seal, + crypto_box_seal_open, + derive_key_ecdh_es, + derive_key_ecdh_1pu, +) logging.basicConfig(level=os.getenv("LOG_LEVEL", "").upper() or None) @@ -37,6 +45,15 @@ def keys_test(): x25519_key = key.convert_key(KeyAlg.X25519) log("Converted key:", x25519_key) + x25519_key_2 = Key.generate(KeyAlg.X25519) + kex = x25519_key.key_exchange(KeyAlg.XC20P, x25519_key_2) + log("Key exchange:", kex) + + msg = b"test message" + sealed = crypto_box_seal(x25519_key, msg) + opened = crypto_box_seal_open(x25519_key, sealed) + assert msg == opened + log("Key algorithm:", key.algorithm) jwk = key.get_jwk_public() From 9e271b9eec5f773ef996670daec5c557b6b9bab8 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Wed, 21 Apr 2021 12:38:51 -0700 Subject: [PATCH 054/116] reorganizing code Signed-off-by: Andrew Whitehead --- src/backend/any.rs | 18 ++-- src/backend/mod.rs | 3 + src/backend/postgres/mod.rs | 18 ++-- src/backend/postgres/provision.rs | 10 +- src/backend/postgres/test_db.rs | 2 +- src/backend/sqlite/mod.rs | 15 +-- src/backend/sqlite/provision.rs | 7 +- src/backend/types.rs | 124 +++++++++++++++++++++ src/ffi/store.rs | 11 +- src/lib.rs | 12 ++- src/storage/entry.rs | 51 ++++++++- src/storage/mod.rs | 2 +- src/storage/{types.rs => store.rs} | 168 +---------------------------- 13 files changed, 235 insertions(+), 206 deletions(-) create mode 100644 src/backend/types.rs rename src/storage/{types.rs => store.rs} (71%) diff --git a/src/backend/any.rs b/src/backend/any.rs index 9e9d3d76..a470e617 100644 --- a/src/backend/any.rs +++ b/src/backend/any.rs @@ -1,11 +1,13 @@ -use crate::error::Error; -use crate::future::BoxFuture; -use crate::protect::{PassKey, StoreKeyMethod}; - -use crate::storage::{ - entry::{Entry, EntryKind, EntryOperation, EntryTag, TagFilter}, - options::IntoOptions, - types::{Backend, ManageBackend, QueryBackend, Scan, Session, Store}, +use super::{Backend, ManageBackend, QueryBackend}; +use crate::{ + error::Error, + future::BoxFuture, + protect::{PassKey, StoreKeyMethod}, + storage::{ + entry::{Entry, EntryKind, EntryOperation, EntryTag, Scan, TagFilter}, + options::IntoOptions, + store::{Session, Store}, + }, }; #[cfg(feature = "postgres")] diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 36b85f47..537e24ea 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -17,3 +17,6 @@ pub mod postgres; #[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))] /// Sqlite database support pub mod sqlite; + +mod types; +pub use self::types::{Backend, ManageBackend, QueryBackend}; diff --git a/src/backend/postgres/mod.rs b/src/backend/postgres/mod.rs index 9d0e62c6..ddbeda09 100644 --- a/src/backend/postgres/mod.rs +++ b/src/backend/postgres/mod.rs @@ -16,19 +16,19 @@ use sqlx::{ }; use crate::{ - backend::db_utils::{ - decode_tags, decrypt_scan_batch, encode_profile_key, encode_tag_filter, expiry_timestamp, - extend_query, prepare_tags, random_profile_name, replace_arg_placeholders, DbSession, - DbSessionActive, DbSessionRef, EncScanEntry, ExtDatabase, QueryParams, QueryPrepare, - PAGE_SIZE, + backend::{ + db_utils::{ + decode_tags, decrypt_scan_batch, encode_profile_key, encode_tag_filter, + expiry_timestamp, extend_query, prepare_tags, random_profile_name, + replace_arg_placeholders, DbSession, DbSessionActive, DbSessionRef, EncScanEntry, + ExtDatabase, QueryParams, QueryPrepare, PAGE_SIZE, + }, + types::{Backend, QueryBackend}, }, error::Error, future::{unblock, BoxFuture}, protect::{EntryEncryptor, KeyCache, PassKey, ProfileId, ProfileKey, StoreKeyMethod}, - storage::{ - entry::{EncEntryTag, Entry, EntryKind, EntryOperation, EntryTag, TagFilter}, - types::{Backend, QueryBackend, Scan}, - }, + storage::entry::{EncEntryTag, Entry, EntryKind, EntryOperation, EntryTag, Scan, TagFilter}, }; const COUNT_QUERY: &'static str = "SELECT COUNT(*) FROM items i diff --git a/src/backend/postgres/provision.rs b/src/backend/postgres/provision.rs index d5dd43e0..fc7934a8 100644 --- a/src/backend/postgres/provision.rs +++ b/src/backend/postgres/provision.rs @@ -8,14 +8,14 @@ use sqlx::{ }; use crate::{ - backend::db_utils::{init_keys, random_profile_name}, + backend::{ + db_utils::{init_keys, random_profile_name}, + types::ManageBackend, + }, error::Error, future::{unblock, BoxFuture}, protect::{KeyCache, PassKey, ProfileId, StoreKeyMethod, StoreKeyReference}, - storage::{ - options::IntoOptions, - types::{ManageBackend, Store}, - }, + storage::{options::IntoOptions, store::Store}, }; use super::PostgresStore; diff --git a/src/backend/postgres/test_db.rs b/src/backend/postgres/test_db.rs index 038c2ed1..b7a4c85a 100644 --- a/src/backend/postgres/test_db.rs +++ b/src/backend/postgres/test_db.rs @@ -13,7 +13,7 @@ use crate::{ error::Error, future::{block_on, unblock}, protect::{generate_raw_store_key, KeyCache, StoreKeyMethod}, - storage::types::Store, + storage::store::Store, }; #[derive(Debug)] diff --git a/src/backend/sqlite/mod.rs b/src/backend/sqlite/mod.rs index 46217b73..5015863e 100644 --- a/src/backend/sqlite/mod.rs +++ b/src/backend/sqlite/mod.rs @@ -15,16 +15,19 @@ use sqlx::{ }; use crate::{ - backend::db_utils::{ - decode_tags, decrypt_scan_batch, encode_profile_key, encode_tag_filter, expiry_timestamp, - extend_query, prepare_tags, random_profile_name, DbSession, DbSessionActive, DbSessionRef, - EncScanEntry, ExtDatabase, QueryParams, QueryPrepare, PAGE_SIZE, + backend::{ + db_utils::{ + decode_tags, decrypt_scan_batch, encode_profile_key, encode_tag_filter, + expiry_timestamp, extend_query, prepare_tags, random_profile_name, DbSession, + DbSessionActive, DbSessionRef, EncScanEntry, ExtDatabase, QueryParams, QueryPrepare, + PAGE_SIZE, + }, + types::{Backend, QueryBackend}, }, error::Error, future::{unblock, BoxFuture}, protect::{EntryEncryptor, KeyCache, PassKey, ProfileId, ProfileKey, StoreKeyMethod}, - storage::entry::{EncEntryTag, Entry, EntryKind, EntryOperation, EntryTag, TagFilter}, - storage::types::{Backend, QueryBackend, Scan}, + storage::entry::{EncEntryTag, Entry, EntryKind, EntryOperation, EntryTag, Scan, TagFilter}, }; mod provision; diff --git a/src/backend/sqlite/provision.rs b/src/backend/sqlite/provision.rs index b1e1da14..cdffe46d 100644 --- a/src/backend/sqlite/provision.rs +++ b/src/backend/sqlite/provision.rs @@ -10,12 +10,15 @@ use sqlx::{ use super::SqliteStore; use crate::{ - backend::db_utils::{init_keys, random_profile_name}, + backend::{ + db_utils::{init_keys, random_profile_name}, + types::ManageBackend, + }, error::Error, future::{unblock, BoxFuture}, protect::{KeyCache, PassKey, StoreKeyMethod, StoreKeyReference}, storage::options::{IntoOptions, Options}, - storage::types::{ManageBackend, Store}, + storage::store::Store, }; /// Configuration options for Sqlite stores diff --git a/src/backend/types.rs b/src/backend/types.rs new file mode 100644 index 00000000..f8a1df0c --- /dev/null +++ b/src/backend/types.rs @@ -0,0 +1,124 @@ +use crate::{ + error::Error, + future::BoxFuture, + protect::{PassKey, StoreKeyMethod}, + storage::entry::{Entry, EntryKind, EntryOperation, EntryTag, Scan, TagFilter}, +}; + +/// Represents a generic backend implementation +pub trait Backend: Send + Sync { + /// The type of session managed by this backend + type Session: QueryBackend; + + /// Create a new profile + fn create_profile(&self, name: Option) -> BoxFuture<'_, Result>; + + /// Get the name of the active profile + fn get_profile_name(&self) -> &str; + + /// Remove an existing profile + fn remove_profile(&self, name: String) -> BoxFuture<'_, Result>; + + /// Create a [`Scan`] against the store + fn scan( + &self, + profile: Option, + kind: EntryKind, + category: String, + tag_filter: Option, + offset: Option, + limit: Option, + ) -> BoxFuture<'_, Result, Error>>; + + /// Create a new session against the store + fn session(&self, profile: Option, transaction: bool) -> Result; + + /// Replace the wrapping key of the store + fn rekey_backend( + &mut self, + method: StoreKeyMethod, + key: PassKey<'_>, + ) -> BoxFuture<'_, Result<(), Error>>; + + /// Close the store instance + fn close(&self) -> BoxFuture<'_, Result<(), Error>>; +} + +/// Create, open, or remove a generic backend implementation +pub trait ManageBackend<'a> { + /// The type of store being managed + type Store; + + /// Open an existing store + fn open_backend( + self, + method: Option, + pass_key: PassKey<'a>, + profile: Option<&'a str>, + ) -> BoxFuture<'a, Result>; + + /// Provision a new store + fn provision_backend( + self, + method: StoreKeyMethod, + pass_key: PassKey<'a>, + profile: Option<&'a str>, + recreate: bool, + ) -> BoxFuture<'a, Result>; + + /// Remove an existing store + fn remove_backend(self) -> BoxFuture<'a, Result>; +} + +/// Query from a generic backend implementation +pub trait QueryBackend: Send { + /// Count the number of matching records in the store + fn count<'q>( + &'q mut self, + kind: EntryKind, + category: &'q str, + tag_filter: Option, + ) -> BoxFuture<'q, Result>; + + /// Fetch a single record from the store by category and name + fn fetch<'q>( + &'q mut self, + kind: EntryKind, + category: &'q str, + name: &'q str, + for_update: bool, + ) -> BoxFuture<'q, Result, Error>>; + + /// Fetch all matching records from the store + fn fetch_all<'q>( + &'q mut self, + kind: EntryKind, + category: &'q str, + tag_filter: Option, + limit: Option, + for_update: bool, + ) -> BoxFuture<'q, Result, Error>>; + + /// Remove all matching records from the store + fn remove_all<'q>( + &'q mut self, + kind: EntryKind, + category: &'q str, + tag_filter: Option, + ) -> BoxFuture<'q, Result>; + + /// Insert or replace a record in the store + fn update<'q>( + &'q mut self, + kind: EntryKind, + operation: EntryOperation, + category: &'q str, + name: &'q str, + value: Option<&'q [u8]>, + tags: Option<&'q [EntryTag]>, + expiry_ms: Option, + ) -> BoxFuture<'q, Result<(), Error>>; + + /// Close the current store session + fn close(self, commit: bool) -> BoxFuture<'static, Result<(), Error>>; +} diff --git a/src/ffi/store.rs b/src/ffi/store.rs index b98e5859..6b4b05b1 100644 --- a/src/ffi/store.rs +++ b/src/ffi/store.rs @@ -17,15 +17,14 @@ use zeroize::Zeroize; use super::{error::set_last_error, handle::ArcHandle, CallbackId, EnsureCallback, ErrorCode}; use crate::{ - backend::any::{AnySession, AnyStore}, + backend::{ + any::{AnySession, AnyStore}, + ManageBackend, + }, error::Error, future::spawn_ok, protect::{generate_raw_store_key, PassKey, StoreKeyMethod}, - storage::{ - entry::{Entry, EntryOperation, EntryTagSet, TagFilter}, - // key::{KeyCategory, KeyEntry}, - types::{ManageBackend, Scan}, - }, + storage::entry::{Entry, EntryOperation, EntryTagSet, Scan, TagFilter}, }; new_sequence_handle!(StoreHandle, FFI_STORE_COUNTER); diff --git a/src/lib.rs b/src/lib.rs index 87d971a1..4a430427 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,6 +22,16 @@ extern crate log; extern crate serde; pub mod backend; +pub use self::backend::{Backend, ManageBackend}; + +#[cfg(feature = "any")] +pub use self::backend::any; + +#[cfg(feature = "postgres")] +pub use self::backend::postgres; + +#[cfg(feature = "sqlite")] +pub use self::backend::sqlite; pub use askar_crypto as crypto; @@ -49,5 +59,5 @@ pub use protect::{generate_raw_store_key, PassKey, StoreKeyMethod}; mod storage; pub use storage::{ entry::{Entry, EntryTag, TagFilter}, - types::{Backend, ManageBackend, Store}, + store::Store, }; diff --git a/src/storage/entry.rs b/src/storage/entry.rs index 03ab3ebf..97eb7967 100644 --- a/src/storage/entry.rs +++ b/src/storage/entry.rs @@ -1,6 +1,10 @@ -use std::fmt::{self, Debug, Formatter}; -use std::str::FromStr; +use std::{ + fmt::{self, Debug, Formatter}, + pin::Pin, + str::FromStr, +}; +use futures_lite::stream::{Stream, StreamExt}; use serde::{ de::{Error as SerdeError, MapAccess, SeqAccess, Visitor}, ser::SerializeMap, @@ -450,6 +454,49 @@ impl FromStr for TagFilter { } } +/// An active record scan of a store backend +pub struct Scan<'s, T> { + stream: Option, Error>> + Send + 's>>>, + page_size: usize, +} + +impl<'s, T> Scan<'s, T> { + pub(crate) fn new(stream: S, page_size: usize) -> Self + where + S: Stream, Error>> + Send + 's, + { + Self { + stream: Some(stream.boxed()), + page_size, + } + } + + /// Fetch the next set of result rows + pub async fn fetch_next(&mut self) -> Result>, Error> { + if let Some(mut s) = self.stream.take() { + match s.try_next().await? { + Some(val) => { + if val.len() == self.page_size { + self.stream.replace(s); + } + Ok(Some(val)) + } + None => Ok(None), + } + } else { + Ok(None) + } + } +} + +impl Debug for Scan<'_, S> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("Scan") + .field("page_size", &self.page_size) + .finish() + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/storage/mod.rs b/src/storage/mod.rs index e19115ff..1d9f739f 100644 --- a/src/storage/mod.rs +++ b/src/storage/mod.rs @@ -2,6 +2,6 @@ pub mod entry; pub(crate) mod options; -pub mod types; +pub mod store; pub(crate) mod wql; diff --git a/src/storage/types.rs b/src/storage/store.rs similarity index 71% rename from src/storage/types.rs rename to src/storage/store.rs index aaacfc81..05abcd1d 100644 --- a/src/storage/types.rs +++ b/src/storage/store.rs @@ -1,139 +1,20 @@ use std::{ fmt::{self, Debug, Display, Formatter}, - pin::Pin, str::FromStr, sync::Arc, }; -use futures_lite::stream::{Stream, StreamExt}; +use futures_lite::stream::StreamExt; use zeroize::Zeroize; -use super::entry::{Entry, EntryKind, EntryOperation, EntryTag, TagFilter}; +use super::entry::{Entry, EntryKind, EntryOperation, EntryTag, Scan, TagFilter}; use crate::{ + backend::{Backend, QueryBackend}, error::Error, - future::BoxFuture, keys::{KeyEntry, KeyParams, LocalKey}, protect::{PassKey, StoreKeyMethod}, }; -/// Represents a generic backend implementation -pub trait Backend: Send + Sync { - /// The type of session managed by this backend - type Session: QueryBackend; - - /// Create a new profile - fn create_profile(&self, name: Option) -> BoxFuture<'_, Result>; - - /// Get the name of the active profile - fn get_profile_name(&self) -> &str; - - /// Remove an existing profile - fn remove_profile(&self, name: String) -> BoxFuture<'_, Result>; - - /// Create a [`Scan`] against the store - fn scan( - &self, - profile: Option, - kind: EntryKind, - category: String, - tag_filter: Option, - offset: Option, - limit: Option, - ) -> BoxFuture<'_, Result, Error>>; - - /// Create a new session against the store - fn session(&self, profile: Option, transaction: bool) -> Result; - - /// Replace the wrapping key of the store - fn rekey_backend( - &mut self, - method: StoreKeyMethod, - key: PassKey<'_>, - ) -> BoxFuture<'_, Result<(), Error>>; - - /// Close the store instance - fn close(&self) -> BoxFuture<'_, Result<(), Error>>; -} - -/// Create, open, or remove a generic backend implementation -pub trait ManageBackend<'a> { - /// The type of store being managed - type Store; - - /// Open an existing store - fn open_backend( - self, - method: Option, - pass_key: PassKey<'a>, - profile: Option<&'a str>, - ) -> BoxFuture<'a, Result>; - - /// Provision a new store - fn provision_backend( - self, - method: StoreKeyMethod, - pass_key: PassKey<'a>, - profile: Option<&'a str>, - recreate: bool, - ) -> BoxFuture<'a, Result>; - - /// Remove an existing store - fn remove_backend(self) -> BoxFuture<'a, Result>; -} - -/// Query from a generic backend implementation -pub trait QueryBackend: Send { - /// Count the number of matching records in the store - fn count<'q>( - &'q mut self, - kind: EntryKind, - category: &'q str, - tag_filter: Option, - ) -> BoxFuture<'q, Result>; - - /// Fetch a single record from the store by category and name - fn fetch<'q>( - &'q mut self, - kind: EntryKind, - category: &'q str, - name: &'q str, - for_update: bool, - ) -> BoxFuture<'q, Result, Error>>; - - /// Fetch all matching records from the store - fn fetch_all<'q>( - &'q mut self, - kind: EntryKind, - category: &'q str, - tag_filter: Option, - limit: Option, - for_update: bool, - ) -> BoxFuture<'q, Result, Error>>; - - /// Remove all matching records from the store - fn remove_all<'q>( - &'q mut self, - kind: EntryKind, - category: &'q str, - tag_filter: Option, - ) -> BoxFuture<'q, Result>; - - /// Insert or replace a record in the store - fn update<'q>( - &'q mut self, - kind: EntryKind, - operation: EntryOperation, - category: &'q str, - name: &'q str, - value: Option<&'q [u8]>, - tags: Option<&'q [EntryTag]>, - expiry_ms: Option, - ) -> BoxFuture<'q, Result<(), Error>>; - - /// Close the current store session - fn close(self, commit: bool) -> BoxFuture<'static, Result<(), Error>>; -} - #[derive(Debug)] /// An instance of an opened store pub struct Store(B); @@ -521,49 +402,6 @@ impl Session { } } -/// An active record scan of a store backend -pub struct Scan<'s, T> { - stream: Option, Error>> + Send + 's>>>, - page_size: usize, -} - -impl<'s, T> Scan<'s, T> { - pub(crate) fn new(stream: S, page_size: usize) -> Self - where - S: Stream, Error>> + Send + 's, - { - Self { - stream: Some(stream.boxed()), - page_size, - } - } - - /// Fetch the next set of result rows - pub async fn fetch_next(&mut self) -> Result>, Error> { - if let Some(mut s) = self.stream.take() { - match s.try_next().await? { - Some(val) => { - if val.len() == self.page_size { - self.stream.replace(s); - } - Ok(Some(val)) - } - None => Ok(None), - } - } else { - Ok(None) - } - } -} - -impl Debug for Scan<'_, S> { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_struct("Scan") - .field("page_size", &self.page_size) - .finish() - } -} - /// Supported categories of KMS entries #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Zeroize)] pub(crate) enum KmsCategory { From e9d8585937551b523968c0cedb09efc689897b74 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Wed, 21 Apr 2021 14:39:24 -0700 Subject: [PATCH 055/116] more refactoring Signed-off-by: Andrew Whitehead --- src/ffi/key.rs | 4 +- src/keys/mod.rs | 341 ------------------------------------------- src/kms/entry.rs | 130 +++++++++++++++++ src/kms/envelope.rs | 73 +++++++++ src/kms/key.rs | 174 ++++++++++++++++++++++ src/kms/mod.rs | 65 +++++++++ src/lib.rs | 3 +- src/storage/store.rs | 57 +------- 8 files changed, 448 insertions(+), 399 deletions(-) delete mode 100644 src/keys/mod.rs create mode 100644 src/kms/entry.rs create mode 100644 src/kms/envelope.rs create mode 100644 src/kms/key.rs create mode 100644 src/kms/mod.rs diff --git a/src/ffi/key.rs b/src/ffi/key.rs index 853bf470..2a059213 100644 --- a/src/ffi/key.rs +++ b/src/ffi/key.rs @@ -3,7 +3,7 @@ use std::{os::raw::c_char, str::FromStr}; use ffi_support::{rust_string_to_c, ByteBuffer, FfiStr}; use super::{handle::ArcHandle, secret::SecretBuffer, ErrorCode}; -use crate::keys::{ +use crate::kms::{ crypto_box_seal, crypto_box_seal_open, derive_key_ecdh_1pu, derive_key_ecdh_es, KeyAlg, LocalKey, }; @@ -118,7 +118,7 @@ pub extern "C" fn askar_key_get_algorithm( trace!("Get key algorithm: {}", handle); check_useful_c_ptr!(out); let key = handle.load()?; - unsafe { *out = rust_string_to_c(key.algorithm()) }; + unsafe { *out = rust_string_to_c(key.algorithm().as_str()) }; Ok(ErrorCode::Success) } } diff --git a/src/keys/mod.rs b/src/keys/mod.rs deleted file mode 100644 index 67776485..00000000 --- a/src/keys/mod.rs +++ /dev/null @@ -1,341 +0,0 @@ -use std::str::FromStr; - -pub use crate::crypto::{ - alg::KeyAlg, - buffer::{SecretBytes, WriteBuffer}, -}; -use crate::{ - crypto::{ - alg::{x25519::X25519KeyPair, AnyKey, AnyKeyCreate}, - encrypt::{ - nacl_box::{ - crypto_box_seal as nacl_box_seal, crypto_box_seal_open as nacl_box_seal_open, - }, - KeyAeadInPlace, - }, - jwk::{FromJwk, ToJwk}, - kdf::{ecdh_1pu::Ecdh1PU, ecdh_es::EcdhEs, KeyDerivation, KeyExchange}, - random::fill_random, - sign::{KeySigVerify, KeySign}, - Error as CryptoError, - }, - error::Error, - storage::entry::{Entry, EntryTag}, -}; - -/// Parameters defining a stored key -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] -pub struct KeyParams { - /// Associated key metadata - #[serde(default, rename = "meta", skip_serializing_if = "Option::is_none")] - pub metadata: Option, - - /// An optional external reference for the key - #[serde(default, rename = "ref", skip_serializing_if = "Option::is_none")] - pub reference: Option, - - /// The associated key data (JWK) - #[serde(default, skip_serializing_if = "Option::is_none")] - pub data: Option, -} - -impl KeyParams { - pub(crate) fn to_bytes(&self) -> Result { - serde_cbor::to_vec(self) - .map(SecretBytes::from) - .map_err(|e| err_msg!(Unexpected, "Error serializing key params: {}", e)) - } - - pub(crate) fn from_slice(params: &[u8]) -> Result { - let result = serde_cbor::from_slice(params) - .map_err(|e| err_msg!(Unexpected, "Error deserializing key params: {}", e)); - result - } -} - -/// A stored key entry -#[derive(Debug)] -pub struct LocalKey { - pub(crate) inner: Box, - pub(crate) ephemeral: bool, -} - -impl LocalKey { - /// Create a new random key or keypair - pub fn generate(alg: KeyAlg, ephemeral: bool) -> Result { - let inner = Box::::generate(alg)?; - Ok(Self { inner, ephemeral }) - } - - pub fn from_jwk(jwk: &str) -> Result { - let inner = Box::::from_jwk(jwk)?; - Ok(Self { - inner, - ephemeral: false, - }) - } - - pub fn from_public_bytes(alg: KeyAlg, public: &[u8]) -> Result { - let inner = Box::::from_public_bytes(alg, public)?; - Ok(Self { - inner, - ephemeral: false, - }) - } - - pub fn from_secret_bytes(alg: KeyAlg, secret: &[u8]) -> Result { - let inner = Box::::from_secret_bytes(alg, secret)?; - Ok(Self { - inner, - ephemeral: false, - }) - } - - pub fn to_key_exchange(&self, alg: KeyAlg, pk: &LocalKey) -> Result { - let inner = Box::::from_key_exchange(alg, &*self.inner, &*pk.inner)?; - Ok(Self { - inner, - ephemeral: self.ephemeral || pk.ephemeral, - }) - } - - fn from_key_derivation(alg: KeyAlg, derive: impl KeyDerivation) -> Result { - let inner = Box::::from_key_derivation(alg, derive)?; - Ok(Self { - inner, - ephemeral: false, - }) - } - - pub(crate) fn encode(&self) -> Result { - Ok(self.inner.to_jwk_secret()?) - } - - pub fn algorithm(&self) -> &str { - self.inner.algorithm().as_str() - } - - pub fn to_jwk_public(&self) -> Result { - Ok(self.inner.to_jwk_public()?) - } - - pub fn to_jwk_secret(&self) -> Result { - Ok(self.inner.to_jwk_secret()?) - } - - pub fn to_jwk_thumbprint(&self) -> Result { - // FIXME add special case for BLS G1+G2 (two prints) - Ok(self.inner.to_jwk_thumbprint()?) - } - - pub fn convert_key(&self, alg: KeyAlg) -> Result { - let inner = self.inner.convert_key(alg)?; - Ok(Self { - inner, - ephemeral: self.ephemeral, - }) - } - - pub fn aead_random_nonce(&self) -> Result, Error> { - let nonce_len = self.inner.aead_params().nonce_length; - if nonce_len == 0 { - return Err(err_msg!( - Unsupported, - "Key type does not support AEAD encryption" - )); - } - let buf = SecretBytes::new_with(nonce_len, fill_random); - Ok(buf.into_vec()) - } - - pub fn aead_encrypt(&self, message: &[u8], nonce: &[u8], aad: &[u8]) -> Result, Error> { - let params = self.inner.aead_params(); - let enc_size = message.len() + params.nonce_length + params.tag_length; - let mut buf = SecretBytes::with_capacity(enc_size); - buf.extend_from_slice(message); - self.inner.encrypt_in_place(&mut buf, nonce, aad)?; - Ok(buf.into_vec()) - } - - pub fn aead_decrypt( - &self, - ciphertext: &[u8], - nonce: &[u8], - aad: &[u8], - ) -> Result { - let mut buf = SecretBytes::from_slice(ciphertext); - self.inner.decrypt_in_place(&mut buf, nonce, aad)?; - Ok(buf) - } - - pub fn sign_message(&self, message: &[u8], sig_type: Option<&str>) -> Result, Error> { - let mut sig = Vec::new(); - self.inner.write_signature(message, None, &mut sig)?; - Ok(sig) - } - - pub fn verify_signature( - &self, - message: &[u8], - signature: &[u8], - sig_type: Option<&str>, - ) -> Result { - Ok(self.inner.verify_signature(message, signature, None)?) - } -} - -impl KeyExchange for LocalKey { - fn key_exchange_buffer( - &self, - other: &LocalKey, - out: &mut dyn WriteBuffer, - ) -> Result<(), CryptoError> { - self.inner.key_exchange_buffer(&other.inner, out) - } -} - -pub fn crypto_box_seal(x25519_key: &LocalKey, message: &[u8]) -> Result, Error> { - if let Some(kp) = x25519_key.inner.downcast_ref::() { - let sealed = nacl_box_seal(kp, message)?; - Ok(sealed.into_vec()) - } else { - Err(err_msg!(Input, "x25519 keypair required")) - } -} - -pub fn crypto_box_seal_open( - x25519_key: &LocalKey, - ciphertext: &[u8], -) -> Result { - if let Some(kp) = x25519_key.inner.downcast_ref::() { - Ok(nacl_box_seal_open(kp, ciphertext)?) - } else { - Err(err_msg!(Input, "x25519 keypair required")) - } -} - -pub fn derive_key_ecdh_1pu( - ephem_key: &LocalKey, - sender_key: &LocalKey, - recip_key: &LocalKey, - alg: &str, - apu: &[u8], - apv: &[u8], -) -> Result { - let key_alg = KeyAlg::from_str(alg)?; - let derive = Ecdh1PU::new( - &*ephem_key, - &*sender_key, - &*recip_key, - alg.as_bytes(), - apu, - apv, - ); - LocalKey::from_key_derivation(key_alg, derive) -} - -pub fn derive_key_ecdh_es( - ephem_key: &LocalKey, - recip_key: &LocalKey, - alg: &str, - apu: &[u8], - apv: &[u8], -) -> Result { - let key_alg = KeyAlg::from_str(alg)?; - let derive = EcdhEs::new(&*ephem_key, &*recip_key, alg.as_bytes(), apu, apv); - LocalKey::from_key_derivation(key_alg, derive) -} - -/// A stored key entry -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct KeyEntry { - /// The key entry identifier - pub(crate) name: String, - /// The parameters defining the key - pub(crate) params: KeyParams, - /// Key algorithm - pub(crate) alg: Option, - /// Thumbprints for the key - pub(crate) thumbprints: Vec, - /// Thumbprints for the key - pub(crate) tags: Vec, -} - -impl KeyEntry { - /// Accessor for the key identity - pub fn algorithm(&self) -> Option<&str> { - self.alg.as_ref().map(String::as_ref) - } - - /// Accessor for the key identity - pub fn name(&self) -> &str { - self.name.as_str() - } - - /// Determine if a key entry refers to a local or external key - pub fn is_local(&self) -> bool { - self.params.reference.is_none() - } - - pub(crate) fn from_entry(entry: Entry) -> Result { - let params = KeyParams::from_slice(&entry.value)?; - let mut alg = None; - let mut thumbprints = Vec::new(); - let mut tags = entry.tags; - let mut idx = 0; - while idx < tags.len() { - let tag = &mut tags[idx]; - let name = tag.name(); - if name.starts_with("user:") { - tag.update_name(|tag| tag.replace_range(0..5, "")); - idx += 1; - } else if name == "alg" { - alg.replace(tags.remove(idx).into_value()); - } else if name == "thumb" { - thumbprints.push(tags.remove(idx).into_value()); - } else { - // unrecognized tag - tags.remove(idx).into_value(); - } - } - // keep sorted for checking equality - thumbprints.sort(); - tags.sort(); - Ok(Self { - name: entry.name, - params, - alg, - thumbprints, - tags, - }) - } - - fn load_key(&mut self) -> Result { - if let Some(key_data) = self.params.data.as_ref() { - let inner = Box::::from_jwk_slice(key_data.as_ref())?; - Ok(LocalKey { - inner, - ephemeral: false, - }) - } else { - Err(err_msg!("Missing key data")) - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn key_params_roundtrip() { - let params = KeyParams { - metadata: Some("meta".to_string()), - reference: None, - data: Some(SecretBytes::from(vec![0, 0, 0, 0])), - }; - let enc_params = params.to_bytes().unwrap(); - let p2 = KeyParams::from_slice(&enc_params).unwrap(); - assert_eq!(p2, params); - } -} diff --git a/src/kms/entry.rs b/src/kms/entry.rs new file mode 100644 index 00000000..05e17522 --- /dev/null +++ b/src/kms/entry.rs @@ -0,0 +1,130 @@ +use super::key::LocalKey; +use crate::{ + crypto::{alg::AnyKey, buffer::SecretBytes, jwk::FromJwk}, + error::Error, + storage::entry::{Entry, EntryTag}, +}; + +/// Parameters defining a stored key +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub struct KeyParams { + /// Associated key metadata + #[serde(default, rename = "meta", skip_serializing_if = "Option::is_none")] + pub metadata: Option, + + /// An optional external reference for the key + #[serde(default, rename = "ref", skip_serializing_if = "Option::is_none")] + pub reference: Option, + + /// The associated key data (JWK) + #[serde(default, skip_serializing_if = "Option::is_none")] + pub data: Option, +} + +impl KeyParams { + pub(crate) fn to_bytes(&self) -> Result { + serde_cbor::to_vec(self) + .map(SecretBytes::from) + .map_err(|e| err_msg!(Unexpected, "Error serializing key params: {}", e)) + } + + pub(crate) fn from_slice(params: &[u8]) -> Result { + let result = serde_cbor::from_slice(params) + .map_err(|e| err_msg!(Unexpected, "Error deserializing key params: {}", e)); + result + } +} + +/// A stored key entry +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct KeyEntry { + /// The key entry identifier + pub(crate) name: String, + /// The parameters defining the key + pub(crate) params: KeyParams, + /// Key algorithm + pub(crate) alg: Option, + /// Thumbprints for the key + pub(crate) thumbprints: Vec, + /// Thumbprints for the key + pub(crate) tags: Vec, +} + +impl KeyEntry { + /// Accessor for the key identity + pub fn algorithm(&self) -> Option<&str> { + self.alg.as_ref().map(String::as_ref) + } + + /// Accessor for the key identity + pub fn name(&self) -> &str { + self.name.as_str() + } + + /// Determine if a key entry refers to a local or external key + pub fn is_local(&self) -> bool { + self.params.reference.is_none() + } + + pub(crate) fn from_entry(entry: Entry) -> Result { + let params = KeyParams::from_slice(&entry.value)?; + let mut alg = None; + let mut thumbprints = Vec::new(); + let mut tags = entry.tags; + let mut idx = 0; + while idx < tags.len() { + let tag = &mut tags[idx]; + let name = tag.name(); + if name.starts_with("user:") { + tag.update_name(|tag| tag.replace_range(0..5, "")); + idx += 1; + } else if name == "alg" { + alg.replace(tags.remove(idx).into_value()); + } else if name == "thumb" { + thumbprints.push(tags.remove(idx).into_value()); + } else { + // unrecognized tag + tags.remove(idx).into_value(); + } + } + // keep sorted for checking equality + thumbprints.sort(); + tags.sort(); + Ok(Self { + name: entry.name, + params, + alg, + thumbprints, + tags, + }) + } + + fn load_key(&mut self) -> Result { + if let Some(key_data) = self.params.data.as_ref() { + let inner = Box::::from_jwk_slice(key_data.as_ref())?; + Ok(LocalKey { + inner, + ephemeral: false, + }) + } else { + Err(err_msg!("Missing key data")) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn key_params_roundtrip() { + let params = KeyParams { + metadata: Some("meta".to_string()), + reference: None, + data: Some(SecretBytes::from(vec![0, 0, 0, 0])), + }; + let enc_params = params.to_bytes().unwrap(); + let p2 = KeyParams::from_slice(&enc_params).unwrap(); + assert_eq!(p2, params); + } +} diff --git a/src/kms/envelope.rs b/src/kms/envelope.rs new file mode 100644 index 00000000..84ea885b --- /dev/null +++ b/src/kms/envelope.rs @@ -0,0 +1,73 @@ +use std::str::FromStr; + +use super::key::LocalKey; +pub use crate::crypto::{ + alg::KeyAlg, + buffer::{SecretBytes, WriteBuffer}, +}; +use crate::{ + crypto::{ + alg::x25519::X25519KeyPair, + encrypt::nacl_box::{ + crypto_box_seal as nacl_box_seal, crypto_box_seal_open as nacl_box_seal_open, + }, + kdf::{ecdh_1pu::Ecdh1PU, ecdh_es::EcdhEs}, + }, + error::Error, +}; + +/// Perform message encryption equivalent to libsodium's `crypto_box_seal` +pub fn crypto_box_seal(x25519_key: &LocalKey, message: &[u8]) -> Result, Error> { + if let Some(kp) = x25519_key.inner.downcast_ref::() { + let sealed = nacl_box_seal(kp, message)?; + Ok(sealed.into_vec()) + } else { + Err(err_msg!(Input, "x25519 keypair required")) + } +} + +/// Perform message decryption equivalent to libsodium's `crypto_box_seal_open` +pub fn crypto_box_seal_open( + x25519_key: &LocalKey, + ciphertext: &[u8], +) -> Result { + if let Some(kp) = x25519_key.inner.downcast_ref::() { + Ok(nacl_box_seal_open(kp, ciphertext)?) + } else { + Err(err_msg!(Input, "x25519 keypair required")) + } +} + +/// Derive an ECDH-1PU shared key for authenticated encryption +pub fn derive_key_ecdh_1pu( + ephem_key: &LocalKey, + sender_key: &LocalKey, + recip_key: &LocalKey, + alg: &str, + apu: &[u8], + apv: &[u8], +) -> Result { + let key_alg = KeyAlg::from_str(alg)?; + let derive = Ecdh1PU::new( + &*ephem_key, + &*sender_key, + &*recip_key, + alg.as_bytes(), + apu, + apv, + ); + LocalKey::from_key_derivation(key_alg, derive) +} + +/// Derive an ECDH-ES shared key for anonymous encryption +pub fn derive_key_ecdh_es( + ephem_key: &LocalKey, + recip_key: &LocalKey, + alg: &str, + apu: &[u8], + apv: &[u8], +) -> Result { + let key_alg = KeyAlg::from_str(alg)?; + let derive = EcdhEs::new(&*ephem_key, &*recip_key, alg.as_bytes(), apu, apv); + LocalKey::from_key_derivation(key_alg, derive) +} diff --git a/src/kms/key.rs b/src/kms/key.rs new file mode 100644 index 00000000..3a03657a --- /dev/null +++ b/src/kms/key.rs @@ -0,0 +1,174 @@ +pub use crate::crypto::{ + alg::KeyAlg, + buffer::{SecretBytes, WriteBuffer}, +}; +use crate::{ + crypto::{ + alg::{AnyKey, AnyKeyCreate}, + encrypt::KeyAeadInPlace, + jwk::{FromJwk, ToJwk}, + kdf::{KeyDerivation, KeyExchange}, + random::fill_random, + sign::{KeySigVerify, KeySign}, + Error as CryptoError, + }, + error::Error, +}; + +/// A stored key entry +#[derive(Debug)] +pub struct LocalKey { + pub(crate) inner: Box, + pub(crate) ephemeral: bool, +} + +impl LocalKey { + /// Create a new random key or keypair + pub fn generate(alg: KeyAlg, ephemeral: bool) -> Result { + let inner = Box::::generate(alg)?; + Ok(Self { inner, ephemeral }) + } + + /// Import a key or keypair from a JWK + pub fn from_jwk(jwk: &str) -> Result { + let inner = Box::::from_jwk(jwk)?; + Ok(Self { + inner, + ephemeral: false, + }) + } + + /// Import a public key from its compact representation + pub fn from_public_bytes(alg: KeyAlg, public: &[u8]) -> Result { + let inner = Box::::from_public_bytes(alg, public)?; + Ok(Self { + inner, + ephemeral: false, + }) + } + + /// Import a symmetric key or public-private keypair from its compact representation + pub fn from_secret_bytes(alg: KeyAlg, secret: &[u8]) -> Result { + let inner = Box::::from_secret_bytes(alg, secret)?; + Ok(Self { + inner, + ephemeral: false, + }) + } + + /// Derive a new key from a Diffie-Hellman exchange between this keypair and a public key + pub fn to_key_exchange(&self, alg: KeyAlg, pk: &LocalKey) -> Result { + let inner = Box::::from_key_exchange(alg, &*self.inner, &*pk.inner)?; + Ok(Self { + inner, + ephemeral: self.ephemeral || pk.ephemeral, + }) + } + + pub(crate) fn from_key_derivation( + alg: KeyAlg, + derive: impl KeyDerivation, + ) -> Result { + let inner = Box::::from_key_derivation(alg, derive)?; + Ok(Self { + inner, + ephemeral: false, + }) + } + + pub(crate) fn encode(&self) -> Result { + Ok(self.inner.to_jwk_secret()?) + } + + /// Accessor for the key algorithm + pub fn algorithm(&self) -> KeyAlg { + self.inner.algorithm() + } + + /// Get the public JWK representation for this key or keypair + pub fn to_jwk_public(&self) -> Result { + Ok(self.inner.to_jwk_public()?) + } + + /// Get the JWK representation for this private key or keypair + pub fn to_jwk_secret(&self) -> Result { + Ok(self.inner.to_jwk_secret()?) + } + + /// Get the JWK thumbprint for this key or keypair + pub fn to_jwk_thumbprint(&self) -> Result { + // FIXME add special case for BLS G1+G2 (two prints) + Ok(self.inner.to_jwk_thumbprint()?) + } + + /// Map this key or keypair to its equivalent for another key algorithm + pub fn convert_key(&self, alg: KeyAlg) -> Result { + let inner = self.inner.convert_key(alg)?; + Ok(Self { + inner, + ephemeral: self.ephemeral, + }) + } + + /// Create a new random nonce for AEAD message encryption + pub fn aead_random_nonce(&self) -> Result, Error> { + let nonce_len = self.inner.aead_params().nonce_length; + if nonce_len == 0 { + return Err(err_msg!( + Unsupported, + "Key type does not support AEAD encryption" + )); + } + let buf = SecretBytes::new_with(nonce_len, fill_random); + Ok(buf.into_vec()) + } + + /// Perform AEAD message encryption with this encryption key + pub fn aead_encrypt(&self, message: &[u8], nonce: &[u8], aad: &[u8]) -> Result, Error> { + let params = self.inner.aead_params(); + let enc_size = message.len() + params.nonce_length + params.tag_length; + let mut buf = SecretBytes::with_capacity(enc_size); + buf.extend_from_slice(message); + self.inner.encrypt_in_place(&mut buf, nonce, aad)?; + Ok(buf.into_vec()) + } + + /// Perform AEAD message decryption with this encryption key + pub fn aead_decrypt( + &self, + ciphertext: &[u8], + nonce: &[u8], + aad: &[u8], + ) -> Result { + let mut buf = SecretBytes::from_slice(ciphertext); + self.inner.decrypt_in_place(&mut buf, nonce, aad)?; + Ok(buf) + } + + /// Sign a message with this private signing key + pub fn sign_message(&self, message: &[u8], sig_type: Option<&str>) -> Result, Error> { + let mut sig = Vec::new(); + self.inner.write_signature(message, None, &mut sig)?; + Ok(sig) + } + + /// Verify a message signature with this private signing key or public verification key + pub fn verify_signature( + &self, + message: &[u8], + signature: &[u8], + sig_type: Option<&str>, + ) -> Result { + Ok(self.inner.verify_signature(message, signature, None)?) + } +} + +impl KeyExchange for LocalKey { + fn key_exchange_buffer( + &self, + other: &LocalKey, + out: &mut dyn WriteBuffer, + ) -> Result<(), CryptoError> { + self.inner.key_exchange_buffer(&other.inner, out) + } +} diff --git a/src/kms/mod.rs b/src/kms/mod.rs new file mode 100644 index 00000000..0eebd85b --- /dev/null +++ b/src/kms/mod.rs @@ -0,0 +1,65 @@ +//! Support for cryptographic key management and operations + +use std::{ + fmt::{self, Debug, Display, Formatter}, + str::FromStr, +}; + +use zeroize::Zeroize; + +use crate::error::Error; + +mod envelope; +pub use self::envelope::{ + crypto_box_seal, crypto_box_seal_open, derive_key_ecdh_1pu, derive_key_ecdh_es, +}; + +mod entry; +pub use self::entry::{KeyEntry, KeyParams}; + +mod key; +pub use self::key::{KeyAlg, LocalKey}; + +/// Supported categories of KMS entries +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Zeroize)] +pub(crate) enum KmsCategory { + /// A stored key or keypair + CryptoKey, +} + +impl KmsCategory { + /// Get a reference to a string representing the `KmsCategory` + pub fn as_str(&self) -> &str { + match self { + Self::CryptoKey => "cryptokey", + } + } + + /// Convert the `KmsCategory` into an owned string + pub fn to_string(&self) -> String { + self.as_str().to_string() + } +} + +impl AsRef for KmsCategory { + fn as_ref(&self) -> &str { + self.as_str() + } +} + +impl FromStr for KmsCategory { + type Err = Error; + + fn from_str(s: &str) -> Result { + Ok(match s { + "cryptokey" => Self::CryptoKey, + _ => return Err(err_msg!("Unknown KMS category: {}", s)), + }) + } +} + +impl Display for KmsCategory { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 4a430427..d6552385 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,8 +50,7 @@ extern crate serde_json; #[cfg(feature = "ffi")] mod ffi; -mod keys; -pub use keys::{derive_key_ecdh_1pu, derive_key_ecdh_es, KeyAlg, KeyEntry, LocalKey}; +pub mod kms; mod protect; pub use protect::{generate_raw_store_key, PassKey, StoreKeyMethod}; diff --git a/src/storage/store.rs b/src/storage/store.rs index 05abcd1d..ba037a2b 100644 --- a/src/storage/store.rs +++ b/src/storage/store.rs @@ -1,17 +1,10 @@ -use std::{ - fmt::{self, Debug, Display, Formatter}, - str::FromStr, - sync::Arc, -}; - -use futures_lite::stream::StreamExt; -use zeroize::Zeroize; +use std::sync::Arc; use super::entry::{Entry, EntryKind, EntryOperation, EntryTag, Scan, TagFilter}; use crate::{ backend::{Backend, QueryBackend}, error::Error, - keys::{KeyEntry, KeyParams, LocalKey}, + kms::{KeyEntry, KeyParams, KmsCategory, LocalKey}, protect::{PassKey, StoreKeyMethod}, }; @@ -277,7 +270,7 @@ impl Session { }; let value = params.to_bytes()?; let mut ins_tags = Vec::with_capacity(10); - let alg = key.algorithm(); + let alg = key.algorithm().as_str(); if !alg.is_empty() { ins_tags.push(EntryTag::Encrypted("alg".to_string(), alg.to_string())); } @@ -401,47 +394,3 @@ impl Session { Ok(self.0.close(false).await?) } } - -/// Supported categories of KMS entries -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Zeroize)] -pub(crate) enum KmsCategory { - /// A stored key or keypair - CryptoKey, -} - -impl KmsCategory { - /// Get a reference to a string representing the `KmsCategory` - pub fn as_str(&self) -> &str { - match self { - Self::CryptoKey => "cryptokey", - } - } - - /// Convert the `KmsCategory` into an owned string - pub fn to_string(&self) -> String { - self.as_str().to_string() - } -} - -impl AsRef for KmsCategory { - fn as_ref(&self) -> &str { - self.as_str() - } -} - -impl FromStr for KmsCategory { - type Err = Error; - - fn from_str(s: &str) -> Result { - Ok(match s { - "cryptokey" => Self::CryptoKey, - _ => return Err(err_msg!("Unknown KMS category: {}", s)), - }) - } -} - -impl Display for KmsCategory { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.write_str(self.as_str()) - } -} From 18ec9c48f3a2294572fc6506cb6d8da7ee2bd615 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Wed, 21 Apr 2021 14:47:15 -0700 Subject: [PATCH 056/116] adjust exports Signed-off-by: Andrew Whitehead --- src/backend/any.rs | 4 +--- src/backend/db_utils.rs | 2 +- src/backend/postgres/mod.rs | 2 +- src/backend/postgres/provision.rs | 2 +- src/backend/postgres/test_db.rs | 2 +- src/backend/sqlite/mod.rs | 2 +- src/backend/sqlite/provision.rs | 3 +-- src/backend/types.rs | 2 +- src/ffi/store.rs | 2 +- src/kms/entry.rs | 2 +- src/lib.rs | 5 +---- src/protect/kdf/mod.rs | 4 +--- src/protect/mod.rs | 2 +- src/protect/profile_key.rs | 4 ++-- src/storage/mod.rs | 10 +++++++--- 15 files changed, 22 insertions(+), 26 deletions(-) diff --git a/src/backend/any.rs b/src/backend/any.rs index a470e617..bcd540be 100644 --- a/src/backend/any.rs +++ b/src/backend/any.rs @@ -4,9 +4,7 @@ use crate::{ future::BoxFuture, protect::{PassKey, StoreKeyMethod}, storage::{ - entry::{Entry, EntryKind, EntryOperation, EntryTag, Scan, TagFilter}, - options::IntoOptions, - store::{Session, Store}, + Entry, EntryKind, EntryOperation, EntryTag, IntoOptions, Scan, Session, Store, TagFilter, }, }; diff --git a/src/backend/db_utils.rs b/src/backend/db_utils.rs index b1325c55..fc7bba50 100644 --- a/src/backend/db_utils.rs +++ b/src/backend/db_utils.rs @@ -12,11 +12,11 @@ use crate::{ future::BoxFuture, protect::{EntryEncryptor, KeyCache, PassKey, ProfileId, ProfileKey, StoreKey, StoreKeyMethod}, storage::{ - entry::{EncEntryTag, Entry, EntryTag, TagFilter}, wql::{ sql::TagSqlEncoder, tags::{tag_query, TagQueryEncoder}, }, + {EncEntryTag, Entry, EntryTag, TagFilter}, }, }; diff --git a/src/backend/postgres/mod.rs b/src/backend/postgres/mod.rs index ddbeda09..8e945af0 100644 --- a/src/backend/postgres/mod.rs +++ b/src/backend/postgres/mod.rs @@ -28,7 +28,7 @@ use crate::{ error::Error, future::{unblock, BoxFuture}, protect::{EntryEncryptor, KeyCache, PassKey, ProfileId, ProfileKey, StoreKeyMethod}, - storage::entry::{EncEntryTag, Entry, EntryKind, EntryOperation, EntryTag, Scan, TagFilter}, + storage::{EncEntryTag, Entry, EntryKind, EntryOperation, EntryTag, Scan, TagFilter}, }; const COUNT_QUERY: &'static str = "SELECT COUNT(*) FROM items i diff --git a/src/backend/postgres/provision.rs b/src/backend/postgres/provision.rs index fc7934a8..bb5cd544 100644 --- a/src/backend/postgres/provision.rs +++ b/src/backend/postgres/provision.rs @@ -15,7 +15,7 @@ use crate::{ error::Error, future::{unblock, BoxFuture}, protect::{KeyCache, PassKey, ProfileId, StoreKeyMethod, StoreKeyReference}, - storage::{options::IntoOptions, store::Store}, + storage::{IntoOptions, Store}, }; use super::PostgresStore; diff --git a/src/backend/postgres/test_db.rs b/src/backend/postgres/test_db.rs index b7a4c85a..ecefd69b 100644 --- a/src/backend/postgres/test_db.rs +++ b/src/backend/postgres/test_db.rs @@ -13,7 +13,7 @@ use crate::{ error::Error, future::{block_on, unblock}, protect::{generate_raw_store_key, KeyCache, StoreKeyMethod}, - storage::store::Store, + storage::Store, }; #[derive(Debug)] diff --git a/src/backend/sqlite/mod.rs b/src/backend/sqlite/mod.rs index 5015863e..5df97183 100644 --- a/src/backend/sqlite/mod.rs +++ b/src/backend/sqlite/mod.rs @@ -27,7 +27,7 @@ use crate::{ error::Error, future::{unblock, BoxFuture}, protect::{EntryEncryptor, KeyCache, PassKey, ProfileId, ProfileKey, StoreKeyMethod}, - storage::entry::{EncEntryTag, Entry, EntryKind, EntryOperation, EntryTag, Scan, TagFilter}, + storage::{EncEntryTag, Entry, EntryKind, EntryOperation, EntryTag, Scan, TagFilter}, }; mod provision; diff --git a/src/backend/sqlite/provision.rs b/src/backend/sqlite/provision.rs index cdffe46d..987ca9ff 100644 --- a/src/backend/sqlite/provision.rs +++ b/src/backend/sqlite/provision.rs @@ -17,8 +17,7 @@ use crate::{ error::Error, future::{unblock, BoxFuture}, protect::{KeyCache, PassKey, StoreKeyMethod, StoreKeyReference}, - storage::options::{IntoOptions, Options}, - storage::store::Store, + storage::{IntoOptions, Options, Store}, }; /// Configuration options for Sqlite stores diff --git a/src/backend/types.rs b/src/backend/types.rs index f8a1df0c..d397a529 100644 --- a/src/backend/types.rs +++ b/src/backend/types.rs @@ -2,7 +2,7 @@ use crate::{ error::Error, future::BoxFuture, protect::{PassKey, StoreKeyMethod}, - storage::entry::{Entry, EntryKind, EntryOperation, EntryTag, Scan, TagFilter}, + storage::{Entry, EntryKind, EntryOperation, EntryTag, Scan, TagFilter}, }; /// Represents a generic backend implementation diff --git a/src/ffi/store.rs b/src/ffi/store.rs index 6b4b05b1..a1facc60 100644 --- a/src/ffi/store.rs +++ b/src/ffi/store.rs @@ -24,7 +24,7 @@ use crate::{ error::Error, future::spawn_ok, protect::{generate_raw_store_key, PassKey, StoreKeyMethod}, - storage::entry::{Entry, EntryOperation, EntryTagSet, Scan, TagFilter}, + storage::{Entry, EntryOperation, EntryTagSet, Scan, TagFilter}, }; new_sequence_handle!(StoreHandle, FFI_STORE_COUNTER); diff --git a/src/kms/entry.rs b/src/kms/entry.rs index 05e17522..7472ed6c 100644 --- a/src/kms/entry.rs +++ b/src/kms/entry.rs @@ -2,7 +2,7 @@ use super::key::LocalKey; use crate::{ crypto::{alg::AnyKey, buffer::SecretBytes, jwk::FromJwk}, error::Error, - storage::entry::{Entry, EntryTag}, + storage::{Entry, EntryTag}, }; /// Parameters defining a stored key diff --git a/src/lib.rs b/src/lib.rs index d6552385..95685750 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,7 +56,4 @@ mod protect; pub use protect::{generate_raw_store_key, PassKey, StoreKeyMethod}; mod storage; -pub use storage::{ - entry::{Entry, EntryTag, TagFilter}, - store::Store, -}; +pub use storage::{Entry, EntryTag, Scan, Store, TagFilter}; diff --git a/src/protect/kdf/mod.rs b/src/protect/kdf/mod.rs index 3a9c396f..347e14e0 100644 --- a/src/protect/kdf/mod.rs +++ b/src/protect/kdf/mod.rs @@ -5,10 +5,8 @@ use crate::{ generic_array::ArrayLength, }, error::Error, - storage::options::Options, + storage::Options, }; -// use crate::keys::wrap::WrapKey; -// use crate::options::Options; mod argon2; use self::argon2::{Level as Argon2Level, SaltSize as Argon2Salt}; diff --git a/src/protect/mod.rs b/src/protect/mod.rs index 2d969159..71f3f216 100644 --- a/src/protect/mod.rs +++ b/src/protect/mod.rs @@ -20,7 +20,7 @@ use crate::{ crypto::buffer::SecretBytes, error::Error, future::unblock, - storage::entry::{EncEntryTag, EntryTag}, + storage::{EncEntryTag, EntryTag}, }; pub type ProfileId = i64; diff --git a/src/protect/profile_key.rs b/src/protect/profile_key.rs index f2cc7eb8..199caa38 100644 --- a/src/protect/profile_key.rs +++ b/src/protect/profile_key.rs @@ -12,7 +12,7 @@ use crate::{ repr::{KeyGen, KeyMeta, KeySecretBytes}, }, error::Error, - storage::entry::{EncEntryTag, EntryTag}, + storage::{EncEntryTag, EntryTag}, }; pub type ProfileKey = ProfileKeyImpl, super::hmac_key::HmacKey>; @@ -256,7 +256,7 @@ fn decode_utf8(value: Vec) -> Result { #[cfg(test)] mod tests { use super::*; - use crate::storage::entry::Entry; + use crate::storage::Entry; #[test] fn encrypt_entry_round_trip() { diff --git a/src/storage/mod.rs b/src/storage/mod.rs index 1d9f739f..f7d00491 100644 --- a/src/storage/mod.rs +++ b/src/storage/mod.rs @@ -1,7 +1,11 @@ -pub mod entry; +mod entry; +pub(crate) use self::entry::{EncEntryTag, EntryTagSet}; +pub use self::entry::{Entry, EntryKind, EntryOperation, EntryTag, Scan, TagFilter}; -pub(crate) mod options; +mod options; +pub(crate) use self::options::{IntoOptions, Options}; -pub mod store; +mod store; +pub use self::store::{Session, Store}; pub(crate) mod wql; From 15b8108e8dec446f04f29709e8b9c3e57959da8d Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Wed, 21 Apr 2021 19:44:44 -0700 Subject: [PATCH 057/116] updating FFI for list results, key results Signed-off-by: Andrew Whitehead --- src/ffi/mod.rs | 2 + src/ffi/result_list.rs | 229 ++++++++++++ src/ffi/store.rs | 468 +++++++++++------------- src/kms/entry.rs | 8 +- src/kms/mod.rs | 5 - src/storage/entry.rs | 33 +- src/storage/store.rs | 2 +- wrappers/python/aries_askar/bindings.py | 179 ++++++--- wrappers/python/aries_askar/key.py | 5 +- wrappers/python/aries_askar/store.py | 257 +++++++++---- wrappers/python/aries_askar/types.py | 20 - wrappers/python/setup.py | 1 + 12 files changed, 772 insertions(+), 437 deletions(-) create mode 100644 src/ffi/result_list.rs diff --git a/src/ffi/mod.rs b/src/ffi/mod.rs index f120a856..5637b76a 100644 --- a/src/ffi/mod.rs +++ b/src/ffi/mod.rs @@ -21,6 +21,8 @@ mod key; mod log; +mod result_list; + mod secret; mod store; diff --git a/src/ffi/result_list.rs b/src/ffi/result_list.rs new file mode 100644 index 00000000..ad8f7143 --- /dev/null +++ b/src/ffi/result_list.rs @@ -0,0 +1,229 @@ +use std::{ffi::CString, os::raw::c_char, ptr}; + +use super::{handle::ArcHandle, key::LocalKeyHandle, secret::SecretBuffer, ErrorCode}; +use crate::{ + error::Error, + kms::KeyEntry, + storage::{Entry, EntryTagSet}, +}; + +pub enum FfiResultList { + Single(R), + Rows(Vec), +} + +impl FfiResultList { + pub fn get_row(&self, idx: i32) -> Result<&R, Error> { + if idx >= 0 { + match self { + Self::Single(e) => { + if idx == 0 { + return Ok(e); + } + } + Self::Rows(r) => { + if let Some(e) = r.get(idx as usize) { + return Ok(e); + } + } + } + } + return Err(err_msg!(Input, "Invalid index for result set")); + } + + pub fn len(&self) -> i32 { + match self { + Self::Single(..) => 0, + Self::Rows(r) => r.len() as i32, + } + } +} + +impl From for FfiResultList { + fn from(row: R) -> Self { + Self::Single(row) + } +} + +impl From> for FfiResultList { + fn from(rows: Vec) -> Self { + Self::Rows(rows) + } +} + +pub type EntryListHandle = ArcHandle; + +pub type FfiEntryList = FfiResultList; + +#[no_mangle] +pub extern "C" fn askar_entry_list_length(handle: EntryListHandle, length: *mut i32) -> ErrorCode { + catch_err! { + check_useful_c_ptr!(length); + let results = handle.load()?; + unsafe { *length = results.len() }; + Ok(ErrorCode::Success) + } +} + +#[no_mangle] +pub extern "C" fn askar_entry_list_get_category( + handle: EntryListHandle, + index: i32, + category: *mut *const c_char, +) -> ErrorCode { + catch_err! { + check_useful_c_ptr!(category); + let results = handle.load()?; + let entry = results.get_row(index)?; + unsafe { *category = CString::new(entry.category.as_str()).unwrap().into_raw() }; + Ok(ErrorCode::Success) + } +} + +#[no_mangle] +pub extern "C" fn askar_entry_list_get_name( + handle: EntryListHandle, + index: i32, + name: *mut *const c_char, +) -> ErrorCode { + catch_err! { + check_useful_c_ptr!(name); + let results = handle.load()?; + let entry = results.get_row(index)?; + unsafe { *name = CString::new(entry.name.as_str()).unwrap().into_raw() }; + Ok(ErrorCode::Success) + } +} + +#[no_mangle] +pub extern "C" fn askar_entry_list_get_value( + handle: EntryListHandle, + index: i32, + value: *mut SecretBuffer, +) -> ErrorCode { + catch_err! { + check_useful_c_ptr!(value); + let results = handle.load()?; + let entry = results.get_row(index)?; + unsafe { *value = SecretBuffer::from_secret(entry.value.as_ref()); } + Ok(ErrorCode::Success) + } +} + +#[no_mangle] +pub extern "C" fn askar_entry_list_get_tags( + handle: EntryListHandle, + index: i32, + tags: *mut *const c_char, +) -> ErrorCode { + catch_err! { + check_useful_c_ptr!(tags); + let results = handle.load()?; + let entry = results.get_row(index)?; + if entry.tags.is_empty() { + unsafe { *tags = ptr::null() }; + } else { + let tag_json = serde_json::to_vec(&EntryTagSet::from(entry.tags.as_slice())).unwrap(); + unsafe { *tags = CString::new(tag_json).unwrap().into_raw() }; + } + Ok(ErrorCode::Success) + } +} + +#[no_mangle] +pub extern "C" fn askar_entry_list_free(handle: EntryListHandle) { + handle.remove(); +} + +pub type KeyEntryListHandle = ArcHandle; + +pub type FfiKeyEntryList = FfiResultList; + +#[no_mangle] +pub extern "C" fn askar_key_entry_list_length( + handle: KeyEntryListHandle, + length: *mut i32, +) -> ErrorCode { + catch_err! { + check_useful_c_ptr!(length); + let results = handle.load()?; + unsafe { *length = results.len() }; + Ok(ErrorCode::Success) + } +} + +#[no_mangle] +pub extern "C" fn askar_key_entry_list_free(handle: KeyEntryListHandle) { + handle.remove(); +} + +#[no_mangle] +pub extern "C" fn askar_key_entry_list_get_name( + handle: KeyEntryListHandle, + index: i32, + name: *mut *const c_char, +) -> ErrorCode { + catch_err! { + check_useful_c_ptr!(name); + let results = handle.load()?; + let entry = results.get_row(index)?; + unsafe { *name = CString::new(entry.name.as_str()).unwrap().into_raw() }; + Ok(ErrorCode::Success) + } +} + +#[no_mangle] +pub extern "C" fn askar_key_entry_list_get_metadata( + handle: KeyEntryListHandle, + index: i32, + metadata: *mut *const c_char, +) -> ErrorCode { + catch_err! { + check_useful_c_ptr!(metadata); + let results = handle.load()?; + let entry = results.get_row(index)?; + if let Some(m) = entry.metadata() { + unsafe { *metadata = CString::new(m).unwrap().into_raw(); } + } else { + unsafe { *metadata = ptr::null(); } + } + Ok(ErrorCode::Success) + } +} + +#[no_mangle] +pub extern "C" fn askar_key_entry_list_get_tags( + handle: KeyEntryListHandle, + index: i32, + tags: *mut *const c_char, +) -> ErrorCode { + catch_err! { + check_useful_c_ptr!(tags); + let results = handle.load()?; + let entry = results.get_row(index)?; + if entry.tags.is_empty() { + unsafe { *tags = ptr::null() }; + } else { + let tag_json = serde_json::to_vec(&EntryTagSet::from(entry.tags.as_slice())).unwrap(); + unsafe { *tags = CString::new(tag_json).unwrap().into_raw() }; + } + Ok(ErrorCode::Success) + } +} + +#[no_mangle] +pub extern "C" fn askar_key_entry_list_load_local( + handle: KeyEntryListHandle, + index: i32, + out: *mut LocalKeyHandle, +) -> ErrorCode { + catch_err! { + trace!("Load key"); + check_useful_c_ptr!(out); + let results = handle.load()?; + let entry = results.get_row(index)?; + let key = entry.load_local_key()?; + unsafe { *out = LocalKeyHandle::create(key) }; + Ok(ErrorCode::Success) + } +} diff --git a/src/ffi/store.rs b/src/ffi/store.rs index a1facc60..b611e48d 100644 --- a/src/ffi/store.rs +++ b/src/ffi/store.rs @@ -1,21 +1,15 @@ -use std::{ - collections::BTreeMap, - ffi::CString, - os::raw::c_char, - ptr, - str::FromStr, - sync::{ - atomic::{AtomicUsize, Ordering}, - Arc, - }, -}; +use std::{collections::BTreeMap, os::raw::c_char, ptr, str::FromStr, sync::Arc}; use async_mutex::{Mutex, MutexGuardArc}; use ffi_support::{rust_string_to_c, ByteBuffer, FfiStr}; use once_cell::sync::Lazy; -use zeroize::Zeroize; -use super::{error::set_last_error, handle::ArcHandle, CallbackId, EnsureCallback, ErrorCode}; +use super::{ + error::set_last_error, + key::LocalKeyHandle, + result_list::{EntryListHandle, FfiEntryList, FfiKeyEntryList, KeyEntryListHandle}, + CallbackId, EnsureCallback, ErrorCode, +}; use crate::{ backend::{ any::{AnySession, AnyStore}, @@ -134,111 +128,6 @@ impl ScanHandle { } } -pub type EntrySetHandle = ArcHandle; - -pub struct FfiEntrySet { - pos: AtomicUsize, - rows: Vec, -} - -impl FfiEntrySet { - pub fn next(&self) -> Option { - let pos = self.pos.fetch_add(1, Ordering::Release); - if pos < self.rows.len() { - Some(self.rows[pos].clone()) - } else { - None - } - } -} - -impl From for FfiEntrySet { - fn from(entry: Entry) -> Self { - Self { - pos: AtomicUsize::default(), - rows: vec![FfiEntry::new(entry)], - } - } -} - -impl From> for FfiEntrySet { - fn from(entries: Vec) -> Self { - Self { - pos: AtomicUsize::default(), - rows: { - let mut acc = Vec::with_capacity(entries.len()); - acc.extend(entries.into_iter().map(FfiEntry::new)); - acc - }, - } - } -} - -impl Drop for FfiEntrySet { - fn drop(&mut self) { - self.rows.drain(..).for_each(FfiEntry::destroy); - } -} - -#[repr(C)] -pub struct FfiEntry { - category: *const c_char, - name: *const c_char, - value: ByteBuffer, - tags: *const c_char, -} - -unsafe impl Send for FfiEntry {} -unsafe impl Sync for FfiEntry {} - -impl Clone for FfiEntry { - fn clone(&self) -> Self { - Self { - category: self.category, - name: self.name, - value: unsafe { ptr::read(&self.value) }, - tags: self.tags, - } - } -} - -impl FfiEntry { - pub fn new(entry: Entry) -> Self { - let Entry { - category, - name, - value, - tags, - } = entry; - let category = CString::new(category).unwrap().into_raw(); - let name = CString::new(name).unwrap().into_raw(); - let value = ByteBuffer::from_vec(value.into_vec()); - let tags = if tags.is_empty() { - ptr::null() - } else { - let tags = serde_json::to_vec(&EntryTagSet::new(tags)).unwrap(); - CString::new(tags).unwrap().into_raw() - }; - Self { - category, - name, - value, - tags, - } - } - - pub fn destroy(self) { - unsafe { - CString::from_raw(self.category as *mut c_char); - CString::from_raw(self.name as *mut c_char); - self.value.destroy_into_vec().zeroize(); - if !self.tags.is_null() { - CString::from_raw(self.tags as *mut c_char); - } - } - } -} - #[no_mangle] pub extern "C" fn askar_store_generate_raw_key( seed: ByteBuffer, @@ -572,7 +461,7 @@ pub extern "C" fn askar_scan_start( #[no_mangle] pub extern "C" fn askar_scan_next( handle: ScanHandle, - cb: Option, + cb: Option, cb_id: CallbackId, ) -> ErrorCode { catch_err! { @@ -581,11 +470,11 @@ pub extern "C" fn askar_scan_next( let cb = EnsureCallback::new(move |result: Result>,Error>| match result { Ok(Some(entries)) => { - let results = EntrySetHandle::create(FfiEntrySet::from(entries)); + let results = EntryListHandle::create(FfiEntryList::from(entries)); cb(cb_id, ErrorCode::Success, results) }, - Ok(None) => cb(cb_id, ErrorCode::Success, EntrySetHandle::invalid()), - Err(err) => cb(cb_id, set_last_error(Some(err)), EntrySetHandle::invalid()), + Ok(None) => cb(cb_id, ErrorCode::Success, EntryListHandle::invalid()), + Err(err) => cb(cb_id, set_last_error(Some(err)), EntryListHandle::invalid()), } ); spawn_ok(async move { @@ -613,31 +502,6 @@ pub extern "C" fn askar_scan_free(handle: ScanHandle) -> ErrorCode { } } -#[no_mangle] -pub extern "C" fn askar_entry_set_next( - handle: EntrySetHandle, - entry: *mut FfiEntry, - found: *mut i8, -) -> ErrorCode { - catch_err! { - check_useful_c_ptr!(entry); - check_useful_c_ptr!(found); - let results = handle.load()?; - if let Some(next) = results.next() { - unsafe { *entry = next }; - unsafe { *found = 1 }; - } else { - unsafe { *found = 0 }; - } - Ok(ErrorCode::Success) - } -} - -#[no_mangle] -pub extern "C" fn askar_entry_set_free(handle: EntrySetHandle) { - handle.remove(); -} - #[no_mangle] pub extern "C" fn askar_session_start( handle: StoreHandle, @@ -711,7 +575,7 @@ pub extern "C" fn askar_session_fetch( category: FfiStr<'_>, name: FfiStr<'_>, for_update: i8, - cb: Option, + cb: Option, cb_id: CallbackId, ) -> ErrorCode { catch_err! { @@ -722,11 +586,11 @@ pub extern "C" fn askar_session_fetch( let cb = EnsureCallback::new(move |result: Result,Error>| match result { Ok(Some(entry)) => { - let results = EntrySetHandle::create(FfiEntrySet::from(entry)); + let results = EntryListHandle::create(FfiEntryList::from(entry)); cb(cb_id, ErrorCode::Success, results) }, - Ok(None) => cb(cb_id, ErrorCode::Success, EntrySetHandle::invalid()), - Err(err) => cb(cb_id, set_last_error(Some(err)), EntrySetHandle::invalid()), + Ok(None) => cb(cb_id, ErrorCode::Success, EntryListHandle::invalid()), + Err(err) => cb(cb_id, set_last_error(Some(err)), EntryListHandle::invalid()), } ); spawn_ok(async move { @@ -747,7 +611,7 @@ pub extern "C" fn askar_session_fetch_all( tag_filter: FfiStr<'_>, limit: i64, for_update: i8, - cb: Option, + cb: Option, cb_id: CallbackId, ) -> ErrorCode { catch_err! { @@ -759,10 +623,10 @@ pub extern "C" fn askar_session_fetch_all( let cb = EnsureCallback::new(move |result| match result { Ok(rows) => { - let results = EntrySetHandle::create(FfiEntrySet::from(rows)); + let results = EntryListHandle::create(FfiEntryList::from(rows)); cb(cb_id, ErrorCode::Success, results) } - Err(err) => cb(cb_id, set_last_error(Some(err)), EntrySetHandle::invalid()), + Err(err) => cb(cb_id, set_last_error(Some(err)), EntryListHandle::invalid()), } ); spawn_ok(async move { @@ -834,9 +698,9 @@ pub extern "C" fn askar_session_update( let value = value.as_slice().to_vec(); let tags = if let Some(tags) = tags.as_opt_str() { Some( - serde_json::from_str::(tags) + serde_json::from_str::>(tags) .map_err(err_map!("Error decoding tags"))? - .into_inner(), + .into_vec(), ) } else { None @@ -864,97 +728,194 @@ pub extern "C" fn askar_session_update( } } -// #[no_mangle] -// pub extern "C" fn askar_session_fetch_keypair( -// handle: SessionHandle, -// ident: FfiStr<'_>, -// for_update: i8, -// cb: Option, -// cb_id: CallbackId, -// ) -> ErrorCode { -// catch_err! { -// trace!("Fetch keypair"); -// let cb = cb.ok_or_else(|| err_msg!("No callback provided"))?; -// let ident = ident.into_opt_string().ok_or_else(|| err_msg!("No key ident provided"))?; - -// let cb = EnsureCallback::new(move |result| -// match result { -// Ok(Some(entry)) => { -// let results = Box::into_raw(Box::new(FfiEntrySet::from(entry))); -// cb(cb_id, ErrorCode::Success, results) -// } -// Ok(None) => { -// cb(cb_id, ErrorCode::Success, ptr::null()) -// } -// Err(err) => cb(cb_id, set_last_error(Some(err)), ptr::null()), -// } -// ); - -// spawn_ok(async move { -// let result = async { -// let mut session = handle.load().await?; -// let key_entry = session.fetch_key( -// KeyCategory::PrivateKey, -// &ident, -// for_update != 0 -// ).await?; -// Ok(key_entry.map(export_key_entry).transpose()?) -// }.await; -// cb.resolve(result); -// }); -// Ok(ErrorCode::Success) -// } -// } - -// #[no_mangle] -// pub extern "C" fn askar_session_update_keypair( -// handle: SessionHandle, -// ident: FfiStr<'_>, -// metadata: FfiStr<'_>, -// tags: FfiStr<'_>, -// cb: Option, -// cb_id: CallbackId, -// ) -> ErrorCode { -// catch_err! { -// trace!("Update keypair"); -// let cb = cb.ok_or_else(|| err_msg!("No callback provided"))?; -// let ident = ident.into_opt_string().ok_or_else(|| err_msg!("No key ident provided"))?; -// let metadata = metadata.into_opt_string(); -// let tags = if let Some(tags) = tags.as_opt_str() { -// Some( -// serde_json::from_str::(tags) -// .map_err(err_map!("Error decoding tags"))? -// .into_inner(), -// ) -// } else { -// None -// }; - -// let cb = EnsureCallback::new(move |result| -// match result { -// Ok(_) => { -// cb(cb_id, ErrorCode::Success) -// } -// Err(err) => cb(cb_id, set_last_error(Some(err))), -// } -// ); - -// spawn_ok(async move { -// let result = async { -// let mut session = handle.load().await?; -// session.update_key( -// KeyCategory::PrivateKey, -// &ident, -// metadata.as_ref().map(String::as_str), -// tags.as_ref().map(Vec::as_slice) -// ).await?; -// Ok(()) -// }.await; -// cb.resolve(result); -// }); -// Ok(ErrorCode::Success) -// } -// } +#[no_mangle] +pub extern "C" fn askar_session_insert_key( + handle: SessionHandle, + key_handle: LocalKeyHandle, + name: FfiStr<'_>, + metadata: FfiStr<'_>, + tags: FfiStr<'_>, + expiry_ms: i64, + cb: Option, + cb_id: CallbackId, +) -> ErrorCode { + catch_err! { + trace!("Insert key"); + let cb = cb.ok_or_else(|| err_msg!("No callback provided"))?; + let key = key_handle.load()?; + let name = name.into_opt_string().ok_or_else(|| err_msg!("No key name provided"))?; + let metadata = metadata.into_opt_string(); + let tags = if let Some(tags) = tags.as_opt_str() { + Some( + serde_json::from_str::>(tags) + .map_err(err_map!("Error decoding tags"))? + .into_vec(), + ) + } else { + None + }; + let expiry_ms = if expiry_ms < 0 { + None + } else { + Some(expiry_ms) + }; + let cb = EnsureCallback::new(move |result| + match result { + Ok(_) => { + cb(cb_id, ErrorCode::Success) + } + Err(err) => cb(cb_id, set_last_error(Some(err))), + } + ); + + spawn_ok(async move { + let result = async { + let mut session = handle.load().await?; + session.insert_key( + name.as_str(), + &key, + metadata.as_ref().map(String::as_str), + tags.as_ref().map(Vec::as_slice), + expiry_ms, + ).await?; + Ok(()) + }.await; + cb.resolve(result); + }); + Ok(ErrorCode::Success) + } +} + +#[no_mangle] +pub extern "C" fn askar_session_fetch_key( + handle: SessionHandle, + name: FfiStr<'_>, + for_update: i8, + cb: Option, + cb_id: CallbackId, +) -> ErrorCode { + catch_err! { + trace!("Fetch key"); + let cb = cb.ok_or_else(|| err_msg!("No callback provided"))?; + let name = name.into_opt_string().ok_or_else(|| err_msg!("No key name provided"))?; + + let cb = EnsureCallback::new(move |result| + match result { + Ok(Some(entry)) => { + let results = KeyEntryListHandle::create(FfiKeyEntryList::from(entry)); + cb(cb_id, ErrorCode::Success, results) + } + Ok(None) => { + cb(cb_id, ErrorCode::Success, KeyEntryListHandle::invalid()) + } + Err(err) => cb(cb_id, set_last_error(Some(err)), KeyEntryListHandle::invalid()), + } + ); + + spawn_ok(async move { + let result = async { + let mut session = handle.load().await?; + let key_entry = session.fetch_key( + name.as_str(), + for_update != 0 + ).await?; + Ok(key_entry) + }.await; + cb.resolve(result); + }); + Ok(ErrorCode::Success) + } +} + +#[no_mangle] +pub extern "C" fn askar_session_update_key( + handle: SessionHandle, + name: FfiStr<'_>, + metadata: FfiStr<'_>, + tags: FfiStr<'_>, + expiry_ms: i64, + cb: Option, + cb_id: CallbackId, +) -> ErrorCode { + catch_err! { + trace!("Update key"); + let cb = cb.ok_or_else(|| err_msg!("No callback provided"))?; + let name = name.into_opt_string().ok_or_else(|| err_msg!("No key name provided"))?; + let metadata = metadata.into_opt_string(); + let tags = if let Some(tags) = tags.as_opt_str() { + Some( + serde_json::from_str::>(tags) + .map_err(err_map!("Error decoding tags"))? + .into_vec(), + ) + } else { + None + }; + let expiry_ms = if expiry_ms < 0 { + None + } else { + Some(expiry_ms) + }; + let cb = EnsureCallback::new(move |result| + match result { + Ok(_) => { + cb(cb_id, ErrorCode::Success) + } + Err(err) => cb(cb_id, set_last_error(Some(err))), + } + ); + + spawn_ok(async move { + let result = async { + let mut session = handle.load().await?; + session.update_key( + &name, + metadata.as_ref().map(String::as_str), + tags.as_ref().map(Vec::as_slice), + expiry_ms, + + ).await?; + Ok(()) + }.await; + cb.resolve(result); + }); + Ok(ErrorCode::Success) + } +} + +#[no_mangle] +pub extern "C" fn askar_session_remove_key( + handle: SessionHandle, + name: FfiStr<'_>, + cb: Option, + cb_id: CallbackId, +) -> ErrorCode { + catch_err! { + trace!("Remove key"); + let cb = cb.ok_or_else(|| err_msg!("No callback provided"))?; + let name = name.into_opt_string().ok_or_else(|| err_msg!("No key name provided"))?; + let cb = EnsureCallback::new(move |result| + match result { + Ok(_) => { + cb(cb_id, ErrorCode::Success) + } + Err(err) => cb(cb_id, set_last_error(Some(err))), + } + ); + + spawn_ok(async move { + let result = async { + let mut session = handle.load().await?; + session.remove_key( + &name, + ).await?; + Ok(()) + }.await; + cb.resolve(result); + }); + Ok(ErrorCode::Success) + } +} #[no_mangle] pub extern "C" fn askar_session_close( @@ -1002,16 +963,3 @@ pub extern "C" fn askar_session_close( Ok(ErrorCode::Success) } } - -// fn export_key_entry(key_entry: KeyEntry) -> Result { -// let KeyEntry { -// category, -// ident, -// params, -// tags, -// } = key_entry; -// let value = serde_json::to_string(¶ms) -// .map_err(err_map!("Error converting key entry to JSON"))? -// .into_bytes(); -// Ok(Entry::new(category.to_string(), ident, value, tags)) -// } diff --git a/src/kms/entry.rs b/src/kms/entry.rs index 7472ed6c..4e1e93d9 100644 --- a/src/kms/entry.rs +++ b/src/kms/entry.rs @@ -56,6 +56,11 @@ impl KeyEntry { self.alg.as_ref().map(String::as_ref) } + /// Accessor for the stored key metadata + pub fn metadata(&self) -> Option<&str> { + self.params.metadata.as_ref().map(String::as_ref) + } + /// Accessor for the key identity pub fn name(&self) -> &str { self.name.as_str() @@ -99,7 +104,8 @@ impl KeyEntry { }) } - fn load_key(&mut self) -> Result { + /// Create a local key instance from this key storage entry + pub fn load_local_key(&self) -> Result { if let Some(key_data) = self.params.data.as_ref() { let inner = Box::::from_jwk_slice(key_data.as_ref())?; Ok(LocalKey { diff --git a/src/kms/mod.rs b/src/kms/mod.rs index 0eebd85b..4a038c7e 100644 --- a/src/kms/mod.rs +++ b/src/kms/mod.rs @@ -34,11 +34,6 @@ impl KmsCategory { Self::CryptoKey => "cryptokey", } } - - /// Convert the `KmsCategory` into an owned string - pub fn to_string(&self) -> String { - self.as_str().to_string() - } } impl AsRef for KmsCategory { diff --git a/src/storage/entry.rs b/src/storage/entry.rs index 97eb7967..85300feb 100644 --- a/src/storage/entry.rs +++ b/src/storage/entry.rs @@ -1,4 +1,5 @@ use std::{ + borrow::Cow, fmt::{self, Debug, Formatter}, pin::Pin, str::FromStr, @@ -158,22 +159,30 @@ impl Debug for EntryTag { } } +/// A wrapper type used for managing (de)serialization of tags #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub(crate) struct EntryTagSet(Vec); +pub(crate) struct EntryTagSet<'e>(Cow<'e, [EntryTag]>); -impl EntryTagSet { +impl EntryTagSet<'_> { #[inline] - pub fn new(tags: Vec) -> Self { - Self(tags) + pub fn into_vec(self) -> Vec { + self.0.into_owned() } +} - #[inline] - pub fn into_inner(self) -> Vec { - self.0 +impl<'e> From<&'e [EntryTag]> for EntryTagSet<'e> { + fn from(tags: &'e [EntryTag]) -> Self { + Self(Cow::Borrowed(tags)) + } +} + +impl From> for EntryTagSet<'static> { + fn from(tags: Vec) -> Self { + Self(Cow::Owned(tags)) } } -impl<'de> Deserialize<'de> for EntryTagSet { +impl<'de> Deserialize<'de> for EntryTagSet<'static> { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, @@ -181,7 +190,7 @@ impl<'de> Deserialize<'de> for EntryTagSet { struct TagSetVisitor; impl<'d> Visitor<'d> for TagSetVisitor { - type Value = EntryTagSet; + type Value = EntryTagSet<'static>; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("an object containing zero or more entry tags") @@ -219,7 +228,7 @@ impl<'de> Deserialize<'de> for EntryTagSet { } } - Ok(EntryTagSet(v)) + Ok(EntryTagSet(Cow::Owned(v))) } } @@ -276,7 +285,7 @@ impl<'de> Deserialize<'de> for EntryTagValues { } } -impl Serialize for EntryTagSet { +impl Serialize for EntryTagSet<'_> { fn serialize(&self, serializer: S) -> Result where S: Serializer, @@ -503,7 +512,7 @@ mod tests { #[test] fn serialize_tags() { - let tags = EntryTagSet(vec![ + let tags = EntryTagSet::from(vec![ EntryTag::Encrypted("a".to_owned(), "aval".to_owned()), EntryTag::Plaintext("b".to_owned(), "bval".to_owned()), EntryTag::Plaintext("b".to_owned(), "bval-2".to_owned()), diff --git a/src/storage/store.rs b/src/storage/store.rs index ba037a2b..b426f40e 100644 --- a/src/storage/store.rs +++ b/src/storage/store.rs @@ -340,7 +340,7 @@ impl Session { } /// Replace the metadata and tags on an existing key in the store - pub async fn replace_key( + pub async fn update_key( &mut self, name: &str, metadata: Option<&str>, diff --git a/wrappers/python/aries_askar/bindings.py b/wrappers/python/aries_askar/bindings.py index 94a169ea..3434d865 100644 --- a/wrappers/python/aries_askar/bindings.py +++ b/wrappers/python/aries_askar/bindings.py @@ -24,7 +24,7 @@ from typing import Optional, Union from .error import StoreError, StoreErrorCode -from .types import Entry, EntryOperation, KeyAlg +from .types import EntryOperation, KeyAlg CALLBACKS = {} @@ -94,38 +94,115 @@ def __del__(self): get_library().askar_scan_free(self) -class EntrySetHandle(c_size_t): - """Pointer to an active EntrySet instance.""" +class EntryListHandle(c_size_t): + """Pointer to an active EntryList instance.""" + + def get_category(self, index: int) -> str: + """Get the entry category.""" + cat = StrBuffer() + do_call( + "askar_entry_list_get_category", + self, + c_int32(index), + byref(cat), + ) + return str(cat) + + def get_name(self, index: int) -> str: + """Get the entry name.""" + name = StrBuffer() + do_call( + "askar_entry_list_get_name", + self, + c_int32(index), + byref(name), + ) + return str(name) + + def get_value(self, index: int) -> bytes: + """Get the entry value.""" + val = ByteBuffer() + do_call("askar_entry_list_get_value", self, c_int32(index), byref(val)) + return bytes(val) def __repr__(self) -> str: - """Format entry set handle as a string.""" + """Format entry list handle as a string.""" return f"{self.__class__.__name__}({self.value})" def __del__(self): """Free the entry set when there are no more references.""" if self: - get_library().askar_entry_set_free(self) + get_library().askar_entry_list_free(self) -class FfiEntry(Structure): - _fields_ = [ - ("category", c_char_p), - ("name", c_char_p), - ("value_len", c_int64), - ("value", c_void_p), - ("tags", c_char_p), - ] +class KeyEntryListHandle(c_size_t): + """Pointer to an active KeyEntryList instance.""" - def decode(self, handle: EntrySetHandle) -> Entry: - value = (c_ubyte * self.value_len).from_address(self.value) - setattr(value, "_ref_", handle) # ensure buffer is not dropped - tags = json.loads(decode_str(self.tags)) if self.tags is not None else None - return Entry( - decode_str(self.category), - decode_str(self.name), - memoryview(value), - tags, + def get_name(self, index: int) -> str: + """Get the key name.""" + name = StrBuffer() + do_call( + "askar_key_entry_list_get_name", + self, + c_int32(index), + byref(name), + ) + return str(name) + + def get_metadata(self, index: int) -> str: + """Get for the key metadata.""" + metadata = StrBuffer() + do_call( + "askar_key_entry_list_get_metadata", + self, + c_int32(index), + byref(metadata), + ) + return str(metadata) + + def get_tags(self, index: int) -> dict: + """Get the key tags.""" + tags = StrBuffer() + do_call( + "askar_key_entry_list_get_tags", + self, + c_int32(index), + byref(tags), ) + return json.loads(decode_str(tags)) if tags else None + + def load_key(self, index: int) -> "LocalKeyHandle": + """Load the key instance.""" + handle = LocalKeyHandle() + do_call( + "askar_key_entry_list_load_local", + self, + c_int32(index), + byref(handle), + ) + return handle + + def __repr__(self) -> str: + """Format key entry list handle as a string.""" + return f"{self.__class__.__name__}({self.value})" + + def __del__(self): + """Free the key entry set when there are no more references.""" + if self: + get_library().askar_key_entry_list_free(self) + + +class LocalKeyHandle(c_size_t): + """Pointer to an active LocalKey instance.""" + + def __repr__(self) -> str: + """Format key handle as a string.""" + return f"{self.__class__.__name__}({self.value})" + + def __del__(self): + """Free the key when there are no more references.""" + if self: + get_library().askar_key_free(self) class FfiByteBuffer(Structure): @@ -566,7 +643,7 @@ async def session_count( async def session_fetch( handle: SessionHandle, category: str, name: str, for_update: bool = False -) -> EntrySetHandle: +) -> EntryListHandle: """Fetch a row from the Store.""" category = encode_str(category) name = encode_str(name) @@ -576,7 +653,7 @@ async def session_fetch( category, name, c_int8(for_update), - return_type=EntrySetHandle, + return_type=EntryListHandle, ) @@ -586,7 +663,7 @@ async def session_fetch_all( tag_filter: Union[str, dict] = None, limit: int = None, for_update: bool = False, -) -> EntrySetHandle: +) -> EntryListHandle: """Fetch all matching rows in the Store.""" category = encode_str(category) if isinstance(tag_filter, dict): @@ -599,7 +676,7 @@ async def session_fetch_all( tag_filter, c_int64(limit if limit is not None else -1), c_int8(for_update), - return_type=EntrySetHandle, + return_type=EntryListHandle, ) @@ -647,27 +724,27 @@ async def session_update( ) -async def session_fetch_keypair( +async def session_fetch_key( handle: SessionHandle, ident: str, for_update: bool = False -) -> Optional[EntrySetHandle]: +) -> Optional[KeyEntryListHandle]: ptr = await do_call_async( - "askar_session_fetch_keypair", + "askar_session_fetch_key", handle, encode_str(ident), c_int8(for_update), return_type=c_void_p, ) if ptr: - return EntrySetHandle(ptr) + return KeyEntryListHandle(ptr) -async def session_update_keypair( - handle: SessionHandle, ident: str, metadata: str = None, tags: dict = None +async def session_update_key( + handle: SessionHandle, name: str, metadata: str = None, tags: dict = None ): await do_call_async( - "askar_session_update_keypair", + "askar_session_update_key", handle, - encode_str(ident), + encode_str(name), encode_str(metadata), encode_str(None if tags is None else json.dumps(tags)), ) @@ -697,31 +774,21 @@ async def scan_start( ) -async def scan_next(handle: StoreHandle) -> Optional[EntrySetHandle]: - handle = await do_call_async("askar_scan_next", handle, return_type=EntrySetHandle) +async def scan_next(handle: StoreHandle) -> Optional[EntryListHandle]: + handle = await do_call_async("askar_scan_next", handle, return_type=EntryListHandle) return handle or None -def entry_set_next(handle: EntrySetHandle) -> Optional[Entry]: - ffi_entry = FfiEntry() - found = c_int8(0) - do_call("askar_entry_set_next", handle, byref(ffi_entry), byref(found)) - if found: - return ffi_entry.decode(handle) - return None +def entry_list_length(handle: EntryListHandle) -> int: + len = c_int32() + do_call("askar_entry_list_length", handle, byref(len)) + return len.value -class LocalKeyHandle(c_size_t): - """Pointer to an active LocalKey instance.""" - - def __repr__(self) -> str: - """Format key handle as a string.""" - return f"{self.__class__.__name__}({self.value})" - - def __del__(self): - """Free the key when there are no more references.""" - if self: - get_library().askar_key_free(self) +def key_entry_list_length(handle: EntryListHandle) -> int: + len = c_int32() + do_call("askar_key_entry_list_length", handle, byref(len)) + return len.value def key_generate(alg: Union[str, KeyAlg], ephemeral: bool = False) -> LocalKeyHandle: @@ -767,7 +834,7 @@ def key_get_algorithm(handle: LocalKeyHandle) -> str: def key_get_ephemeral(handle: LocalKeyHandle) -> bool: eph = c_int8() do_call("askar_key_get_ephemeral", handle, byref(eph)) - return bool(eph) + return eph.value != 0 def key_get_jwk_public(handle: LocalKeyHandle) -> str: @@ -861,7 +928,7 @@ def key_verify_signature( encode_str(sig_type), byref(verify), ) - return bool(verify) + return verify.value != 0 def key_crypto_box_seal( diff --git a/wrappers/python/aries_askar/key.py b/wrappers/python/aries_askar/key.py index 64063780..ee11e819 100644 --- a/wrappers/python/aries_askar/key.py +++ b/wrappers/python/aries_askar/key.py @@ -66,7 +66,10 @@ def verify_signature( return bindings.key_verify_signature(self._handle, message, signature, sig_type) def __repr__(self) -> str: - return f"" + return ( + f"" + ) def crypto_box_seal( diff --git a/wrappers/python/aries_askar/store.py b/wrappers/python/aries_askar/store.py index 61d72c01..9a132028 100644 --- a/wrappers/python/aries_askar/store.py +++ b/wrappers/python/aries_askar/store.py @@ -2,38 +2,164 @@ import json -from typing import Optional, Sequence, Union +from typing import Optional, Union + +from cached_property import cached_property from . import bindings +from .bindings import ( + EntryListHandle, + KeyEntryListHandle, + ScanHandle, + SessionHandle, + StoreHandle, +) from .error import StoreError, StoreErrorCode -from .types import Entry, EntryOperation, KeyAlg, KeyEntry +from .key import Key +from .types import EntryOperation + + +class Entry: + """A single result from a store query.""" + + def __init__(self, lst: EntryListHandle, pos: int): + """Initialize the EntryHandle.""" + self._list = lst + self._pos = pos + self._cache = dict() + + @cached_property + def category(self) -> str: + """Accessor for the entry category.""" + return self._list.get_category(self._pos) + + @cached_property + def name(self) -> str: + """Accessor for the entry name.""" + return self._list.get_name(self._pos) + + @cached_property + def value(self) -> bytes: + """Accessor for the entry value.""" + return self._list.get_value(self._pos) + + @cached_property + def tags(self) -> dict: + """Accessor for the entry tags.""" + return self._list.get_value(self._pos) + def __repr__(self) -> str: + """Format entry handle as a string.""" + return ( + f"" + ) -class EntrySet: - """A set of query results.""" - def __init__(self, handle: bindings.EntrySetHandle): - """Initialize the EntrySet instance.""" +class EntryList: + """A list of query results.""" + + def __init__(self, handle: EntryListHandle, len: int = None): + """Initialize the EntryList instance.""" self._handle = handle + self._pos = 0 + if handle: + self._len = bindings.entry_list_length(self._handle) if len is None else len + else: + self._len = 0 @property - def handle(self) -> bindings.EntrySetHandle: - """Accessor for the entry set handle.""" + def handle(self) -> EntryListHandle: + """Accessor for the entry list handle.""" return self._handle def __iter__(self): return self def __next__(self): - entry = bindings.entry_set_next(self._handle) - if entry: - # keep reference to self so the buffer isn't dropped - entry.entry_set = self + if self._pos < self._len: + entry = Entry(self._handle, self._pos) + self._pos += 1 return entry else: raise StopIteration + def __repr__(self) -> str: + return f"" + + +class KeyEntry: + """Pointer to one result of a KeyEntryList instance.""" + + def __init__(self, lst: KeyEntryListHandle, pos: int): + """Initialize the KeyEntryHandle.""" + self._list = lst + self._pos = pos + self._cache = dict() + + @cached_property + def name(self) -> str: + """Accessor for the entry name.""" + return self._list.get_name(self._pos) + + @cached_property + def metadata(self) -> str: + """Accessor for the entry metadata.""" + return self._list.get_metadata(self._pos) + + @cached_property + def key(self) -> Key: + """Accessor for the entry metadata.""" + return Key(self._list.load_key(self._pos)) + + @cached_property + def tags(self) -> dict: + """Accessor for the entry tags.""" + return self._list.get_value(self._pos) + + def __repr__(self) -> str: + """Format key entry handle as a string.""" + return ( + f"" + ) + + +class KeyEntryList: + """A list of key query results.""" + + def __init__(self, handle: KeyEntryListHandle, len: int = None): + """Initialize the KeyEntryList instance.""" + self._handle = handle + if handle: + self._len = ( + bindings.key_entry_list_length(self._handle) if len is None else len + ) + else: + self._len = 0 + + @property + def handle(self) -> KeyEntryListHandle: + """Accessor for the key entry list handle.""" + return self._handle + + def __iter__(self): + return self + + def __next__(self): + if self._pos < self._len: + entry = KeyEntry(self._handle, self._pos) + self._pos += 1 + return entry + else: + raise StopIteration + + def __repr__(self) -> str: + return ( + f"" + ) + class Scan: """A scan of the Store.""" @@ -49,11 +175,11 @@ def __init__( ): """Initialize the Scan instance.""" self.params = (store, profile, category, tag_filter, offset, limit) - self._handle: bindings.ScanHandle = None - self._buffer: EntrySet = None + self._handle: ScanHandle = None + self._buffer: EntryList = None @property - def handle(self) -> bindings.ScanHandle: + def handle(self) -> ScanHandle: """Accessor for the scan handle.""" return self._handle @@ -70,16 +196,16 @@ async def __anext__(self): self._handle = await bindings.scan_start( store.handle, profile, category, tag_filter, offset, limit ) - scan_handle = await bindings.scan_next(self._handle) - self._buffer = EntrySet(scan_handle) if scan_handle else None + list_handle = await bindings.scan_next(self._handle) + self._buffer = EntryList(list_handle) if list_handle else None while True: if not self._buffer: raise StopAsyncIteration row = next(self._buffer, None) if row: return row - scan_handle = await bindings.scan_next(self._handle) - self._buffer = EntrySet(scan_handle) if scan_handle else None + list_handle = await bindings.scan_next(self._handle) + self._buffer = EntryList(list_handle) if list_handle else None def __repr__(self) -> str: return f"" @@ -88,14 +214,14 @@ def __repr__(self) -> str: class Store: """An opened Store instance.""" - def __init__(self, handle: bindings.StoreHandle, uri: str): + def __init__(self, handle: StoreHandle, uri: str): """Initialize the Store instance.""" self._handle = handle self._opener: OpenSession = None self._uri = uri @property - def handle(self) -> bindings.StoreHandle: + def handle(self) -> StoreHandle: """Accessor for the store handle.""" return self._handle @@ -194,7 +320,7 @@ def __repr__(self) -> str: class Session: """An opened Session instance.""" - def __init__(self, store: Store, handle: bindings.SessionHandle, is_txn: bool): + def __init__(self, store: Store, handle: SessionHandle, is_txn: bool): """Initialize the Session instance.""" self._store = store self._handle = handle @@ -206,7 +332,7 @@ def is_transaction(self) -> bool: return self._is_txn @property - def handle(self) -> bindings.SessionHandle: + def handle(self) -> SessionHandle: """Accessor for the SessionHandle instance.""" return self._handle @@ -228,7 +354,7 @@ async def fetch( result_handle = await bindings.session_fetch( self._handle, category, name, for_update ) - return next(EntrySet(result_handle), None) if result_handle else None + return next(EntryList(result_handle, 1), None) if result_handle else None async def fetch_all( self, @@ -237,14 +363,12 @@ async def fetch_all( limit: int = None, *, for_update: bool = False, - ) -> Sequence[Entry]: + ) -> EntryList: if not self._handle: raise StoreError(StoreErrorCode.WRAPPER, "Cannot fetch from closed session") - return list( - EntrySet( - await bindings.session_fetch_all( - self._handle, category, tag_filter, limit, for_update - ) + return EntryList( + await bindings.session_fetch_all( + self._handle, category, tag_filter, limit, for_update ) ) @@ -304,86 +428,57 @@ async def remove_all( ) return await bindings.session_remove_all(self._handle, category, tag_filter) - async def create_keypair( + async def insert_key( self, - key_alg: KeyAlg, + name: str, + key: Key, *, metadata: str = None, tags: dict = None, - seed: Union[str, bytes] = None, ) -> str: if not self._handle: raise StoreError( - StoreErrorCode.WRAPPER, "Cannot create keypair with closed session" + StoreErrorCode.WRAPPER, "Cannot insert key with closed session" ) return str( - await bindings.session_create_keypair( - self._handle, key_alg.value, metadata, tags, seed + await bindings.session_insert_key( + self._handle, + name, + key._handle, + metadata, + tags, ) ) - async def fetch_keypair( - self, ident: str, *, for_update: bool = False + async def fetch_key( + self, name: str, *, for_update: bool = False ) -> Optional[KeyEntry]: if not self._handle: raise StoreError( - StoreErrorCode.WRAPPER, "Cannot fetch keypair from closed session" + StoreErrorCode.WRAPPER, "Cannot fetch key from closed session" ) - handle = await bindings.session_fetch_keypair(self._handle, ident, for_update) - if handle: - entry = next(EntrySet(handle)) - result = KeyEntry(entry.category, entry.name, entry.value_json, entry.tags) - return result + result_handle = await bindings.session_fetch_key(self._handle, name, for_update) + return next(KeyEntryList(result_handle, 1)) if result_handle else None - async def update_keypair( + async def update_key( self, - ident: str, + name: str, *, metadata: str = None, tags: dict = None, ): if not self._handle: raise StoreError( - StoreErrorCode.WRAPPER, "Cannot update keypair with closed session" - ) - await bindings.session_update_keypair(self._handle, ident, metadata, tags) - - async def sign_message(self, key_ident: str, message: Union[str, bytes]) -> bytes: - if not self._handle: - raise StoreError( - StoreErrorCode.WRAPPER, "Cannot sign message with closed session" + StoreErrorCode.WRAPPER, "Cannot update key with closed session" ) - buf = await bindings.session_sign_message(self._handle, key_ident, message) - return bytes(buf) + await bindings.session_update_key(self._handle, name, metadata, tags) - async def pack_message( - self, - recipient_vks: Sequence[str], - from_key_ident: Optional[str], - message: Union[str, bytes], - ) -> bytes: + async def remove_key(self, name: str): if not self._handle: raise StoreError( - StoreErrorCode.WRAPPER, "Cannot pack message with closed session" - ) - return bytes( - await bindings.session_pack_message( - self._handle, recipient_vks, from_key_ident, message + StoreErrorCode.WRAPPER, "Cannot remove key with closed session" ) - ) - - async def unpack_message( - self, - message: Union[str, bytes], - ) -> (bytes, str, Optional[str]): - if not self._handle: - raise StoreError( - StoreErrorCode.WRAPPER, "Cannot unpack message with closed session" - ) - (unpacked, recip, sender) = await bindings.session_unpack_message( - self._handle, message - ) - return (bytes(unpacked), recip, sender) + await bindings.session_remove_key(self._handle, name) async def commit(self): if not self._is_txn: diff --git a/wrappers/python/aries_askar/types.py b/wrappers/python/aries_askar/types.py index 0f6ea7e7..59db182b 100644 --- a/wrappers/python/aries_askar/types.py +++ b/wrappers/python/aries_askar/types.py @@ -56,26 +56,6 @@ def __repr__(self) -> str: ) -class KeyEntry: - def __init__( - self, - category: str, - ident: str, - params: dict, - tags: Mapping[str, str] = None, - ) -> "Entry": - self.category = category - self.ident = ident - self.params = params - self.tags = dict(tags) if tags else {} - - def __repr__(self) -> str: - return ( - f"{self.__class__.__name__}(category={repr(self.category)}, " - f"ident={repr(self.ident)}, params=.., tags={self.tags})" - ) - - class KeyAlg(Enum): AES128GCM = "a128gcm" AES256GCM = "a256gcm" diff --git a/wrappers/python/setup.py b/wrappers/python/setup.py index 7ce7cb5a..92b8b9f3 100644 --- a/wrappers/python/setup.py +++ b/wrappers/python/setup.py @@ -22,6 +22,7 @@ long_description_content_type="text/markdown", url="https://github.com/hyperledger/aries-askar", packages=find_packages(), + install_requires=["cached_property~=1.5"], include_package_data=True, package_data={ "": [ From fea38213e7cd546dfee308aea74d928cc7a820c2 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Wed, 21 Apr 2021 20:32:25 -0700 Subject: [PATCH 058/116] add key bytes export via FFI Signed-off-by: Andrew Whitehead --- askar-crypto/src/alg/any.rs | 66 ++++++++++++++++++++++--- src/ffi/key.rs | 30 +++++++++++ src/kms/key.rs | 10 ++++ wrappers/python/aries_askar/bindings.py | 50 +++++++++++++++++++ wrappers/python/aries_askar/key.py | 16 ++++-- 5 files changed, 161 insertions(+), 11 deletions(-) diff --git a/askar-crypto/src/alg/any.rs b/askar-crypto/src/alg/any.rs index 3df703ad..ba26a007 100644 --- a/askar-crypto/src/alg/any.rs +++ b/askar-crypto/src/alg/any.rs @@ -13,7 +13,7 @@ use super::p256::{self, P256KeyPair}; use super::x25519::{self, X25519KeyPair}; use super::{AesTypes, Chacha20Types, EcCurves, HasKeyAlg, KeyAlg}; use crate::{ - buffer::{ResizeBuffer, WriteBuffer}, + buffer::{ResizeBuffer, SecretBytes, WriteBuffer}, encrypt::{KeyAeadInPlace, KeyAeadParams}, error::Error, jwk::{FromJwk, JwkEncoder, JwkParts, ToJwk}, @@ -78,11 +78,11 @@ impl AnyKeyCreate for Box { } fn from_public_bytes(alg: KeyAlg, public: &[u8]) -> Result { - from_public_any(alg, public) + from_public_bytes_any(alg, public) } fn from_secret_bytes(alg: KeyAlg, secret: &[u8]) -> Result { - from_secret_any(alg, secret) + from_secret_bytes_any(alg, secret) } #[inline(always)] @@ -113,11 +113,11 @@ impl AnyKeyCreate for Arc { } fn from_public_bytes(alg: KeyAlg, public: &[u8]) -> Result { - from_public_any(alg, public) + from_public_bytes_any(alg, public) } fn from_secret_bytes(alg: KeyAlg, secret: &[u8]) -> Result { - from_secret_any(alg, secret) + from_secret_bytes_any(alg, secret) } #[inline(always)] @@ -166,7 +166,7 @@ fn generate_any(alg: KeyAlg) -> Result { } #[inline] -fn from_public_any(alg: KeyAlg, public: &[u8]) -> Result { +fn from_public_bytes_any(alg: KeyAlg, public: &[u8]) -> Result { match alg { KeyAlg::Ed25519 => Ed25519KeyPair::from_public_bytes(public).map(R::alloc_key), KeyAlg::X25519 => X25519KeyPair::from_public_bytes(public).map(R::alloc_key), @@ -187,7 +187,7 @@ fn from_public_any(alg: KeyAlg, public: &[u8]) -> Result } #[inline] -fn from_secret_any(alg: KeyAlg, secret: &[u8]) -> Result { +fn from_secret_bytes_any(alg: KeyAlg, secret: &[u8]) -> Result { match alg { KeyAlg::Aes(AesTypes::A128GCM) => { AesGcmKey::::from_secret_bytes(secret).map(R::alloc_key) @@ -341,6 +341,58 @@ fn from_jwk_any(jwk: JwkParts<'_>) -> Result { } } +impl AnyKey { + pub fn to_public_bytes(&self) -> Result { + match self.key_type_id() { + s if s == TypeId::of::() => { + Ok(self.assume::().to_public_bytes()?) + } + s if s == TypeId::of::() => { + Ok(self.assume::().to_public_bytes()?) + } + s if s == TypeId::of::() => { + Ok(self.assume::().to_public_bytes()?) + } + s if s == TypeId::of::() => { + Ok(self.assume::().to_public_bytes()?) + } + #[allow(unreachable_patterns)] + _ => return Err(err_msg!(Unsupported, "Unsupported key exchange")), + } + } + + pub fn to_secret_bytes(&self) -> Result { + match self.key_type_id() { + s if s == TypeId::of::>() => { + Ok(self.assume::>().to_secret_bytes()?) + } + s if s == TypeId::of::>() => { + Ok(self.assume::>().to_secret_bytes()?) + } + s if s == TypeId::of::>() => { + Ok(self.assume::>().to_secret_bytes()?) + } + s if s == TypeId::of::>() => { + Ok(self.assume::>().to_secret_bytes()?) + } + s if s == TypeId::of::() => { + Ok(self.assume::().to_secret_bytes()?) + } + s if s == TypeId::of::() => { + Ok(self.assume::().to_secret_bytes()?) + } + s if s == TypeId::of::() => { + Ok(self.assume::().to_secret_bytes()?) + } + s if s == TypeId::of::() => { + Ok(self.assume::().to_secret_bytes()?) + } + #[allow(unreachable_patterns)] + _ => return Err(err_msg!(Unsupported, "Unsupported key exchange")), + } + } +} + macro_rules! match_key_types { ($slf:expr, $( $t:ty ),+; $errmsg:expr) => { match $slf.key_type_id() { diff --git a/src/ffi/key.rs b/src/ffi/key.rs index 2a059213..d780dabf 100644 --- a/src/ffi/key.rs +++ b/src/ffi/key.rs @@ -53,6 +53,21 @@ pub extern "C" fn askar_key_from_public_bytes( } } +#[no_mangle] +pub extern "C" fn askar_key_get_public_bytes( + handle: LocalKeyHandle, + out: *mut SecretBuffer, +) -> ErrorCode { + catch_err! { + trace!("Get key public bytes: {}", handle); + check_useful_c_ptr!(out); + let key = handle.load()?; + let public = key.to_public_bytes()?; + unsafe { *out = SecretBuffer::from_secret(public) }; + Ok(ErrorCode::Success) + } +} + #[no_mangle] pub extern "C" fn askar_key_from_secret_bytes( alg: FfiStr<'_>, @@ -69,6 +84,21 @@ pub extern "C" fn askar_key_from_secret_bytes( } } +#[no_mangle] +pub extern "C" fn askar_key_get_secret_bytes( + handle: LocalKeyHandle, + out: *mut SecretBuffer, +) -> ErrorCode { + catch_err! { + trace!("Get key secret bytes: {}", handle); + check_useful_c_ptr!(out); + let key = handle.load()?; + let public = key.to_secret_bytes()?; + unsafe { *out = SecretBuffer::from_secret(public) }; + Ok(ErrorCode::Success) + } +} + #[no_mangle] pub extern "C" fn askar_key_convert( handle: LocalKeyHandle, diff --git a/src/kms/key.rs b/src/kms/key.rs index 3a03657a..fa638de5 100644 --- a/src/kms/key.rs +++ b/src/kms/key.rs @@ -47,6 +47,11 @@ impl LocalKey { }) } + /// Export the raw bytes of the public key + pub fn to_public_bytes(&self) -> Result { + Ok(self.inner.to_public_bytes()?) + } + /// Import a symmetric key or public-private keypair from its compact representation pub fn from_secret_bytes(alg: KeyAlg, secret: &[u8]) -> Result { let inner = Box::::from_secret_bytes(alg, secret)?; @@ -56,6 +61,11 @@ impl LocalKey { }) } + /// Export the raw bytes of the private key + pub fn to_secret_bytes(&self) -> Result { + Ok(self.inner.to_secret_bytes()?) + } + /// Derive a new key from a Diffie-Hellman exchange between this keypair and a public key pub fn to_key_exchange(&self, alg: KeyAlg, pk: &LocalKey) -> Result { let inner = Box::::from_key_exchange(alg, &*self.inner, &*pk.inner)?; diff --git a/wrappers/python/aries_askar/bindings.py b/wrappers/python/aries_askar/bindings.py index 3434d865..c18061c9 100644 --- a/wrappers/python/aries_askar/bindings.py +++ b/wrappers/python/aries_askar/bindings.py @@ -799,6 +799,56 @@ def key_generate(alg: Union[str, KeyAlg], ephemeral: bool = False) -> LocalKeyHa return handle +def key_from_public_bytes( + alg: Union[str, KeyAlg], public: Union[bytes, ByteBuffer] +) -> LocalKeyHandle: + handle = LocalKeyHandle() + if isinstance(alg, KeyAlg): + alg = alg.value + do_call( + "askar_key_from_public_bytes", + encode_str(alg), + encode_bytes(public), + byref(handle), + ) + return handle + + +def key_get_public_bytes(handle: LocalKeyHandle) -> ByteBuffer: + buf = ByteBuffer() + do_call( + "askar_key_get_public_bytes", + handle, + byref(buf), + ) + return buf + + +def key_from_secret_bytes( + alg: Union[str, KeyAlg], secret: Union[bytes, ByteBuffer] +) -> LocalKeyHandle: + handle = LocalKeyHandle() + if isinstance(alg, KeyAlg): + alg = alg.value + do_call( + "askar_key_from_secret_bytes", + encode_str(alg), + encode_bytes(secret), + byref(handle), + ) + return handle + + +def key_get_secret_bytes(handle: LocalKeyHandle) -> ByteBuffer: + buf = ByteBuffer() + do_call( + "askar_key_get_secret_bytes", + handle, + byref(buf), + ) + return buf + + def key_from_jwk(jwk: str) -> LocalKeyHandle: handle = LocalKeyHandle() do_call("askar_key_from_jwk", encode_str(jwk), byref(handle)) diff --git a/wrappers/python/aries_askar/key.py b/wrappers/python/aries_askar/key.py index ee11e819..ec7401e5 100644 --- a/wrappers/python/aries_askar/key.py +++ b/wrappers/python/aries_askar/key.py @@ -14,6 +14,18 @@ def __init__(self, handle: bindings.LocalKeyHandle): """Initialize the Key instance.""" self._handle = handle + @classmethod + def generate(cls, alg: Union[str, KeyAlg], *, ephemeral: bool = False) -> "Key": + return Key(bindings.key_generate(alg, ephemeral)) + + @classmethod + def from_secret_bytes(cls, alg: Union[str, KeyAlg], secret: bytes) -> "Key": + return Key(bindings.key_from_secret_bytes(alg, secret)) + + @classmethod + def from_public_bytes(cls, alg: Union[str, KeyAlg], public: bytes) -> "Key": + return Key(bindings.key_from_public_bytes(alg, public)) + @property def handle(self) -> bindings.LocalKeyHandle: """Accessor for the key handle.""" @@ -27,10 +39,6 @@ def algorithm(self) -> str: def ephemeral(self) -> "Key": return bindings.key_get_ephemeral(self._handle) - @classmethod - def generate(cls, alg: Union[str, KeyAlg], *, ephemeral: bool = False) -> "Key": - return Key(bindings.key_generate(alg, ephemeral)) - def convert_key(self, alg: Union[str, KeyAlg]) -> "Key": return Key(bindings.key_convert(self._handle, alg)) From 96c070904ce5a60fc735edb3d3ea533a706afa33 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Wed, 21 Apr 2021 22:10:10 -0700 Subject: [PATCH 059/116] key management fixes Signed-off-by: Andrew Whitehead --- src/ffi/result_list.rs | 33 ++++++++--- src/storage/store.rs | 2 +- wrappers/python/aries_askar/bindings.py | 75 +++++++++++++++++++++---- wrappers/python/aries_askar/key.py | 6 ++ wrappers/python/aries_askar/store.py | 28 +++++---- wrappers/python/demo/test.py | 34 +++++------ 6 files changed, 126 insertions(+), 52 deletions(-) diff --git a/src/ffi/result_list.rs b/src/ffi/result_list.rs index ad8f7143..38b15a9c 100644 --- a/src/ffi/result_list.rs +++ b/src/ffi/result_list.rs @@ -56,11 +56,11 @@ pub type EntryListHandle = ArcHandle; pub type FfiEntryList = FfiResultList; #[no_mangle] -pub extern "C" fn askar_entry_list_length(handle: EntryListHandle, length: *mut i32) -> ErrorCode { +pub extern "C" fn askar_entry_list_count(handle: EntryListHandle, count: *mut i32) -> ErrorCode { catch_err! { - check_useful_c_ptr!(length); + check_useful_c_ptr!(count); let results = handle.load()?; - unsafe { *length = results.len() }; + unsafe { *count = results.len() }; Ok(ErrorCode::Success) } } @@ -140,14 +140,14 @@ pub type KeyEntryListHandle = ArcHandle; pub type FfiKeyEntryList = FfiResultList; #[no_mangle] -pub extern "C" fn askar_key_entry_list_length( +pub extern "C" fn askar_key_entry_list_count( handle: KeyEntryListHandle, - length: *mut i32, + count: *mut i32, ) -> ErrorCode { catch_err! { - check_useful_c_ptr!(length); + check_useful_c_ptr!(count); let results = handle.load()?; - unsafe { *length = results.len() }; + unsafe { *count = results.len() }; Ok(ErrorCode::Success) } } @@ -157,6 +157,25 @@ pub extern "C" fn askar_key_entry_list_free(handle: KeyEntryListHandle) { handle.remove(); } +#[no_mangle] +pub extern "C" fn askar_key_entry_list_get_algorithm( + handle: KeyEntryListHandle, + index: i32, + alg: *mut *const c_char, +) -> ErrorCode { + catch_err! { + check_useful_c_ptr!(alg); + let results = handle.load()?; + let entry = results.get_row(index)?; + if let Some(alg_name) = entry.algorithm() { + unsafe { *alg = CString::new(alg_name).unwrap().into_raw() }; + } else { + unsafe { *alg = ptr::null() }; + } + Ok(ErrorCode::Success) + } +} + #[no_mangle] pub extern "C" fn askar_key_entry_list_get_name( handle: KeyEntryListHandle, diff --git a/src/storage/store.rs b/src/storage/store.rs index b426f40e..73157788 100644 --- a/src/storage/store.rs +++ b/src/storage/store.rs @@ -376,7 +376,7 @@ impl Session { KmsCategory::CryptoKey.as_str(), name, Some(value.as_ref()), - tags, + Some(upd_tags.as_slice()), expiry_ms, ) .await?; diff --git a/wrappers/python/aries_askar/bindings.py b/wrappers/python/aries_askar/bindings.py index c18061c9..8835a004 100644 --- a/wrappers/python/aries_askar/bindings.py +++ b/wrappers/python/aries_askar/bindings.py @@ -125,6 +125,17 @@ def get_value(self, index: int) -> bytes: do_call("askar_entry_list_get_value", self, c_int32(index), byref(val)) return bytes(val) + def get_tags(self, index: int) -> dict: + """Get the entry tags.""" + tags = StrBuffer() + do_call( + "askar_entry_list_get_tags", + self, + c_int32(index), + byref(tags), + ) + return json.loads(tags.value) if tags else None + def __repr__(self) -> str: """Format entry list handle as a string.""" return f"{self.__class__.__name__}({self.value})" @@ -138,6 +149,17 @@ def __del__(self): class KeyEntryListHandle(c_size_t): """Pointer to an active KeyEntryList instance.""" + def get_algorithm(self, index: int) -> str: + """Get the key algorithm.""" + name = StrBuffer() + do_call( + "askar_key_entry_list_get_algorithm", + self, + c_int32(index), + byref(name), + ) + return str(name) + def get_name(self, index: int) -> str: """Get the key name.""" name = StrBuffer() @@ -169,7 +191,7 @@ def get_tags(self, index: int) -> dict: c_int32(index), byref(tags), ) - return json.loads(decode_str(tags)) if tags else None + return json.loads(tags.value) if tags else None def load_key(self, index: int) -> "LocalKeyHandle": """Load the key instance.""" @@ -441,10 +463,6 @@ def do_call_async( return fut -def decode_str(value: c_char_p) -> str: - return value.decode("utf-8") - - def encode_str(arg: Optional[Union[str, bytes]]) -> c_char_p: """ Encode an optional input argument as a string. @@ -724,13 +742,33 @@ async def session_update( ) +async def session_insert_key( + handle: SessionHandle, + key_handle: LocalKeyHandle, + name: str, + metadata: str = None, + tags: dict = None, + expiry_ms: Optional[int] = None, +): + await do_call_async( + "askar_session_insert_key", + handle, + key_handle, + encode_str(name), + encode_str(metadata), + encode_str(None if tags is None else json.dumps(tags)), + c_int64(-1 if expiry_ms is None else expiry_ms), + return_type=c_void_p, + ) + + async def session_fetch_key( - handle: SessionHandle, ident: str, for_update: bool = False + handle: SessionHandle, name: str, for_update: bool = False ) -> Optional[KeyEntryListHandle]: ptr = await do_call_async( "askar_session_fetch_key", handle, - encode_str(ident), + encode_str(name), c_int8(for_update), return_type=c_void_p, ) @@ -739,7 +777,11 @@ async def session_fetch_key( async def session_update_key( - handle: SessionHandle, name: str, metadata: str = None, tags: dict = None + handle: SessionHandle, + name: str, + metadata: str = None, + tags: dict = None, + expiry_ms: Optional[int] = None, ): await do_call_async( "askar_session_update_key", @@ -747,6 +789,15 @@ async def session_update_key( encode_str(name), encode_str(metadata), encode_str(None if tags is None else json.dumps(tags)), + c_int64(-1 if expiry_ms is None else expiry_ms), + ) + + +async def session_remove_key(handle: SessionHandle, name: str): + await do_call_async( + "askar_session_remove_key", + handle, + encode_str(name), ) @@ -779,15 +830,15 @@ async def scan_next(handle: StoreHandle) -> Optional[EntryListHandle]: return handle or None -def entry_list_length(handle: EntryListHandle) -> int: +def entry_list_count(handle: EntryListHandle) -> int: len = c_int32() - do_call("askar_entry_list_length", handle, byref(len)) + do_call("askar_entry_list_count", handle, byref(len)) return len.value -def key_entry_list_length(handle: EntryListHandle) -> int: +def key_entry_list_count(handle: EntryListHandle) -> int: len = c_int32() - do_call("askar_key_entry_list_length", handle, byref(len)) + do_call("askar_key_entry_list_count", handle, byref(len)) return len.value diff --git a/wrappers/python/aries_askar/key.py b/wrappers/python/aries_askar/key.py index ec7401e5..6c15866a 100644 --- a/wrappers/python/aries_askar/key.py +++ b/wrappers/python/aries_askar/key.py @@ -45,6 +45,12 @@ def convert_key(self, alg: Union[str, KeyAlg]) -> "Key": def key_exchange(self, alg: Union[str, KeyAlg], pk: "Key") -> "Key": return Key(bindings.key_exchange(alg, self._handle, pk._handle)) + def get_public_bytes(self) -> bytes: + return bytes(bindings.key_get_public_bytes(self._handle)) + + def get_secret_bytes(self) -> bytes: + return bytes(bindings.key_get_secret_bytes(self._handle)) + def get_jwk_public(self) -> str: return bindings.key_get_jwk_public(self._handle) diff --git a/wrappers/python/aries_askar/store.py b/wrappers/python/aries_askar/store.py index 9a132028..319efa19 100644 --- a/wrappers/python/aries_askar/store.py +++ b/wrappers/python/aries_askar/store.py @@ -47,7 +47,7 @@ def value(self) -> bytes: @cached_property def tags(self) -> dict: """Accessor for the entry tags.""" - return self._list.get_value(self._pos) + return self._list.get_tags(self._pos) def __repr__(self) -> str: """Format entry handle as a string.""" @@ -65,7 +65,7 @@ def __init__(self, handle: EntryListHandle, len: int = None): self._handle = handle self._pos = 0 if handle: - self._len = bindings.entry_list_length(self._handle) if len is None else len + self._len = bindings.entry_list_count(self._handle) if len is None else len else: self._len = 0 @@ -98,6 +98,11 @@ def __init__(self, lst: KeyEntryListHandle, pos: int): self._pos = pos self._cache = dict() + @cached_property + def algorithm(self) -> str: + """Accessor for the entry algorithm.""" + return self._list.get_algorithm(self._pos) + @cached_property def name(self) -> str: """Accessor for the entry name.""" @@ -116,13 +121,13 @@ def key(self) -> Key: @cached_property def tags(self) -> dict: """Accessor for the entry tags.""" - return self._list.get_value(self._pos) + return self._list.get_tags(self._pos) def __repr__(self) -> str: """Format key entry handle as a string.""" return ( - f"" + f"" ) @@ -132,9 +137,10 @@ class KeyEntryList: def __init__(self, handle: KeyEntryListHandle, len: int = None): """Initialize the KeyEntryList instance.""" self._handle = handle + self._pos = 0 if handle: self._len = ( - bindings.key_entry_list_length(self._handle) if len is None else len + bindings.key_entry_list_count(self._handle) if len is None else len ) else: self._len = 0 @@ -435,6 +441,7 @@ async def insert_key( *, metadata: str = None, tags: dict = None, + expiry_ms: int = None, ) -> str: if not self._handle: raise StoreError( @@ -442,11 +449,7 @@ async def insert_key( ) return str( await bindings.session_insert_key( - self._handle, - name, - key._handle, - metadata, - tags, + self._handle, key._handle, name, metadata, tags, expiry_ms ) ) @@ -466,12 +469,13 @@ async def update_key( *, metadata: str = None, tags: dict = None, + expiry_ms: int = None, ): if not self._handle: raise StoreError( StoreErrorCode.WRAPPER, "Cannot update key with closed session" ) - await bindings.session_update_key(self._handle, name, metadata, tags) + await bindings.session_update_key(self._handle, name, metadata, tags, expiry_ms) async def remove_key(self, name: str): if not self._handle: diff --git a/wrappers/python/demo/test.py b/wrappers/python/demo/test.py index 09c07a6c..0217b299 100644 --- a/wrappers/python/demo/test.py +++ b/wrappers/python/demo/test.py @@ -73,10 +73,6 @@ def keys_test(): bob = Key.generate(KeyAlg.P256) derived = derive_key_ecdh_1pu("A256GCM", ephem, alice, bob, "Alice", "Bob") log("Derived:", derived.get_jwk_thumbprint()) - # derived = bindings.key_derive_ecdh_1pu("a256gcm", ephem, alice, bob, "Alice", "Bob") - # log("Derived:", bindings.key_get_jwk_thumbprint(derived)) - # derived = bindings.key_derive_ecdh_es("a256gcm", ephem, bob, "Alice", "Bob") - # log("Derived:", bindings.key_get_jwk_thumbprint(derived)) derived = derive_key_ecdh_es("A256GCM", ephem, bob, "Alice", "Bob") log("Derived:", derived.get_jwk_thumbprint()) @@ -127,26 +123,24 @@ async def store_test(): # test key operations in a new session async with store as session: - # # Create a new keypair - # key_ident = await session.create_keypair(KeyAlg.ED25519, metadata="metadata") - # log("Created key:", key_ident) + # Create a new keypair + keypair = Key.generate(KeyAlg.ED25519) + log("Created key:", keypair) - # # Update keypair - # await session.update_keypair(key_ident, metadata="updated metadata") - # log("Updated key") + # Store keypair + key_name = "testkey" + await session.insert_key(key_name, keypair, metadata="metadata") + log("Inserted key") - # # Fetch keypair - # key = await session.fetch_keypair(key_ident) - # log("Fetch key:", key, "\nKey params:", key.params) + # Update keypair + await session.update_key(key_name, metadata="updated metadata") + log("Updated key") - # # Sign a message - # signature = await session.sign_message(key_ident, b"my message") - # log("Signature:", signature) - - # # Verify signature - # verify = await verify_signature(key_ident, b"my message", signature) - # log("Verify signature:", verify) + # Fetch keypair + key = await session.fetch_key(key_name) + log("Fetched key:", key) + async with store as session: # Remove rows by category and (optional) tag filter log( "Removed entry count:", From 0d529b0a52184835363bdadeaf637db72b359185 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Tue, 27 Apr 2021 13:34:44 -0700 Subject: [PATCH 060/116] properly handle different output lengths in ECDH-ES/1PU Signed-off-by: Andrew Whitehead --- askar-crypto/src/kdf/ecdh_1pu.rs | 2 +- askar-crypto/src/kdf/ecdh_es.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/askar-crypto/src/kdf/ecdh_1pu.rs b/askar-crypto/src/kdf/ecdh_1pu.rs index b7787817..61b2d4c3 100644 --- a/askar-crypto/src/kdf/ecdh_1pu.rs +++ b/askar-crypto/src/kdf/ecdh_1pu.rs @@ -54,7 +54,7 @@ impl KeyDerivation for Ecdh1PU<'_, Key> { alg: self.alg, apu: self.apu, apv: self.apv, - pub_info: &(256u32).to_be_bytes(), // output length in bits + pub_info: &(output_len * 8).to_be_bytes(), // output length in bits prv_info: &[], }); diff --git a/askar-crypto/src/kdf/ecdh_es.rs b/askar-crypto/src/kdf/ecdh_es.rs index fb61a618..237338f1 100644 --- a/askar-crypto/src/kdf/ecdh_es.rs +++ b/askar-crypto/src/kdf/ecdh_es.rs @@ -52,7 +52,7 @@ impl KeyDerivation for EcdhEs<'_, Key> { alg: self.alg, apu: self.apu, apv: self.apv, - pub_info: &(256u32).to_be_bytes(), // output length in bits + pub_info: &(output_len * 8).to_be_bytes(), // output length in bits prv_info: &[], }); From 53f4c15df1d6ed521eafa55143e3f20f7284d4c4 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Tue, 27 Apr 2021 13:36:26 -0700 Subject: [PATCH 061/116] expose addition crypto_box primitives Signed-off-by: Andrew Whitehead --- askar-crypto/src/buffer/secret.rs | 7 +++ askar-crypto/src/encrypt/nacl_box.rs | 33 ++++++------ src/ffi/key.rs | 63 +++++++++++++++++++++- src/kms/envelope.rs | 70 +++++++++++++++++++------ src/kms/key.rs | 5 +- src/kms/mod.rs | 3 +- wrappers/python/aries_askar/__init__.py | 6 +++ wrappers/python/aries_askar/bindings.py | 45 ++++++++++++++++ wrappers/python/aries_askar/key.py | 46 ++++++++++++---- wrappers/python/aries_askar/store.py | 21 ++++++++ 10 files changed, 252 insertions(+), 47 deletions(-) diff --git a/askar-crypto/src/buffer/secret.rs b/askar-crypto/src/buffer/secret.rs index a6b9cab1..772f221f 100644 --- a/askar-crypto/src/buffer/secret.rs +++ b/askar-crypto/src/buffer/secret.rs @@ -35,6 +35,13 @@ impl SecretBytes { Self(v) } + #[inline] + pub fn from_slice_reserve(data: &[u8], reserve: usize) -> Self { + let mut v = Vec::with_capacity(data.len() + reserve); + v.extend_from_slice(data); + Self(v) + } + #[inline] pub fn len(&self) -> usize { self.0.len() diff --git a/askar-crypto/src/encrypt/nacl_box.rs b/askar-crypto/src/encrypt/nacl_box.rs index 88f29508..4e271bb0 100644 --- a/askar-crypto/src/encrypt/nacl_box.rs +++ b/askar-crypto/src/encrypt/nacl_box.rs @@ -13,8 +13,9 @@ use crate::{ repr::{KeyGen, KeyPublicBytes}, }; -const CBOX_NONCE_SIZE: usize = NonceSize::::USIZE; -const CBOX_KEY_SIZE: usize = crate::alg::x25519::PUBLIC_KEY_LENGTH; +pub const CBOX_NONCE_SIZE: usize = NonceSize::::USIZE; +pub const CBOX_KEY_SIZE: usize = crate::alg::x25519::PUBLIC_KEY_LENGTH; +pub const CBOX_TAG_SIZE: usize = TagSize::::USIZE; type NonceSize = ::NonceSize; @@ -38,18 +39,6 @@ fn nonce_from(nonce: &[u8]) -> Result<&GenericArray>, Er } } -pub fn crypto_box_nonce( - ephemeral_pk: &[u8], - recip_pk: &[u8], -) -> Result<[u8; CBOX_NONCE_SIZE], Error> { - let mut key_hash = VarBlake2b::new(CBOX_NONCE_SIZE).unwrap(); - key_hash.update(ephemeral_pk); - key_hash.update(recip_pk); - let mut nonce = [0u8; CBOX_NONCE_SIZE]; - key_hash.finalize_variable(|hash| nonce.copy_from_slice(hash)); - Ok(nonce) -} - pub fn crypto_box( recip_pk: &X25519KeyPair, sender_sk: &X25519KeyPair, @@ -91,6 +80,18 @@ pub fn crypto_box_open( Ok(()) } +pub fn crypto_box_seal_nonce( + ephemeral_pk: &[u8], + recip_pk: &[u8], +) -> Result<[u8; CBOX_NONCE_SIZE], Error> { + let mut key_hash = VarBlake2b::new(CBOX_NONCE_SIZE).unwrap(); + key_hash.update(ephemeral_pk); + key_hash.update(recip_pk); + let mut nonce = [0u8; CBOX_NONCE_SIZE]; + key_hash.finalize_variable(|hash| nonce.copy_from_slice(hash)); + Ok(nonce) +} + // Could add a non-alloc version, if needed pub fn crypto_box_seal(recip_pk: &X25519KeyPair, message: &[u8]) -> Result { let ephem_kp = X25519KeyPair::generate()?; @@ -100,7 +101,7 @@ pub fn crypto_box_seal(recip_pk: &X25519KeyPair, message: &[u8]) -> Result Result { let ephem_pk = X25519KeyPair::from_public_bytes(&ciphertext[..CBOX_KEY_SIZE])?; let mut buffer = SecretBytes::from_slice(&ciphertext[CBOX_KEY_SIZE..]); - let nonce = crypto_box_nonce(ephem_pk.public.as_bytes(), recip_sk.public.as_bytes())?; + let nonce = crypto_box_seal_nonce(ephem_pk.public.as_bytes(), recip_sk.public.as_bytes())?; crypto_box_open(recip_sk, &ephem_pk, &mut buffer, &nonce)?; Ok(buffer) } diff --git a/src/ffi/key.rs b/src/ffi/key.rs index d780dabf..9b306000 100644 --- a/src/ffi/key.rs +++ b/src/ffi/key.rs @@ -4,8 +4,8 @@ use ffi_support::{rust_string_to_c, ByteBuffer, FfiStr}; use super::{handle::ArcHandle, secret::SecretBuffer, ErrorCode}; use crate::kms::{ - crypto_box_seal, crypto_box_seal_open, derive_key_ecdh_1pu, derive_key_ecdh_es, KeyAlg, - LocalKey, + crypto_box, crypto_box_open, crypto_box_random_nonce, crypto_box_seal, crypto_box_seal_open, + derive_key_ecdh_1pu, derive_key_ecdh_es, KeyAlg, LocalKey, }; pub type LocalKeyHandle = ArcHandle; @@ -297,6 +297,65 @@ pub extern "C" fn askar_key_verify_signature( } } +#[no_mangle] +pub extern "C" fn askar_key_crypto_box_random_nonce(out: *mut SecretBuffer) -> ErrorCode { + catch_err! { + trace!("crypto box random nonce"); + check_useful_c_ptr!(out); + let nonce = crypto_box_random_nonce()?; + unsafe { *out = SecretBuffer::from_secret(&nonce[..]) }; + Ok(ErrorCode::Success) + } +} + +#[no_mangle] +pub extern "C" fn askar_key_crypto_box( + recip_key: LocalKeyHandle, + sender_key: LocalKeyHandle, + message: ByteBuffer, + nonce: ByteBuffer, + out: *mut SecretBuffer, +) -> ErrorCode { + catch_err! { + trace!("crypto box: {}, {}", recip_key, sender_key); + check_useful_c_ptr!(out); + let recip_key = recip_key.load()?; + let sender_key = sender_key.load()?; + let message = crypto_box( + &*recip_key, + &*sender_key, + message.as_slice(), + nonce.as_slice() + )?; + unsafe { *out = SecretBuffer::from_secret(message) }; + Ok(ErrorCode::Success) + } +} + +#[no_mangle] +pub extern "C" fn askar_key_crypto_box_open( + recip_key: LocalKeyHandle, + sender_key: LocalKeyHandle, + message: ByteBuffer, + nonce: ByteBuffer, + out: *mut SecretBuffer, +) -> ErrorCode { + catch_err! { + trace!("crypto box open: {}, {}", recip_key, sender_key); + check_useful_c_ptr!(out); + let recip_key = recip_key.load()?; + let sender_key = sender_key.load()?; + let message = crypto_box_open( + &*recip_key, + &*sender_key, + message.as_slice(), + nonce.as_slice() + )?; + unsafe { *out = SecretBuffer::from_secret(message) }; + Ok(ErrorCode::Success) + } +} + #[no_mangle] pub extern "C" fn askar_key_crypto_box_seal( handle: LocalKeyHandle, diff --git a/src/kms/envelope.rs b/src/kms/envelope.rs index 84ea885b..c3e62b4c 100644 --- a/src/kms/envelope.rs +++ b/src/kms/envelope.rs @@ -1,41 +1,79 @@ use std::str::FromStr; use super::key::LocalKey; -pub use crate::crypto::{ - alg::KeyAlg, - buffer::{SecretBytes, WriteBuffer}, -}; use crate::{ crypto::{ - alg::x25519::X25519KeyPair, + alg::{x25519::X25519KeyPair, KeyAlg}, + buffer::SecretBytes, encrypt::nacl_box::{ + crypto_box as nacl_box, crypto_box_open as nacl_box_open, crypto_box_seal as nacl_box_seal, crypto_box_seal_open as nacl_box_seal_open, + CBOX_NONCE_SIZE, CBOX_TAG_SIZE, }, kdf::{ecdh_1pu::Ecdh1PU, ecdh_es::EcdhEs}, + random::fill_random, }, error::Error, }; -/// Perform message encryption equivalent to libsodium's `crypto_box_seal` -pub fn crypto_box_seal(x25519_key: &LocalKey, message: &[u8]) -> Result, Error> { - if let Some(kp) = x25519_key.inner.downcast_ref::() { - let sealed = nacl_box_seal(kp, message)?; - Ok(sealed.into_vec()) +#[inline] +fn cast_x25519(key: &LocalKey) -> Result<&X25519KeyPair, Error> { + if let Some(kp) = key.inner.downcast_ref::() { + Ok(kp) } else { Err(err_msg!(Input, "x25519 keypair required")) } } +/// Generate a new random nonce for crypto_box +pub fn crypto_box_random_nonce() -> Result<[u8; CBOX_NONCE_SIZE], Error> { + let mut nonce = [0u8; CBOX_NONCE_SIZE]; + fill_random(&mut nonce); + Ok(nonce) +} + +/// Encrypt a message with crypto_box and a detached nonce +pub fn crypto_box( + recip_x25519: &LocalKey, + sender_x25519: &LocalKey, + message: &[u8], + nonce: &[u8], +) -> Result, Error> { + let recip_pk = cast_x25519(recip_x25519)?; + let sender_sk = cast_x25519(sender_x25519)?; + let mut buffer = SecretBytes::from_slice_reserve(message, CBOX_TAG_SIZE); + nacl_box(recip_pk, sender_sk, &mut buffer, nonce)?; + Ok(buffer.into_vec()) +} + +/// Decrypt a message with crypto_box and a detached nonce +pub fn crypto_box_open( + recip_x25519: &LocalKey, + sender_x25519: &LocalKey, + message: &[u8], + nonce: &[u8], +) -> Result { + let recip_pk = cast_x25519(recip_x25519)?; + let sender_sk = cast_x25519(sender_x25519)?; + let mut buffer = SecretBytes::from_slice(message); + nacl_box_open(recip_pk, sender_sk, &mut buffer, nonce)?; + Ok(buffer) +} + +/// Perform message encryption equivalent to libsodium's `crypto_box_seal` +pub fn crypto_box_seal(recip_x25519: &LocalKey, message: &[u8]) -> Result, Error> { + let kp = cast_x25519(recip_x25519)?; + let sealed = nacl_box_seal(kp, message)?; + Ok(sealed.into_vec()) +} + /// Perform message decryption equivalent to libsodium's `crypto_box_seal_open` pub fn crypto_box_seal_open( - x25519_key: &LocalKey, + recip_x25519: &LocalKey, ciphertext: &[u8], ) -> Result { - if let Some(kp) = x25519_key.inner.downcast_ref::() { - Ok(nacl_box_seal_open(kp, ciphertext)?) - } else { - Err(err_msg!(Input, "x25519 keypair required")) - } + let kp = cast_x25519(recip_x25519)?; + Ok(nacl_box_seal_open(kp, ciphertext)?) } /// Derive an ECDH-1PU shared key for authenticated encryption diff --git a/src/kms/key.rs b/src/kms/key.rs index fa638de5..2c303fe8 100644 --- a/src/kms/key.rs +++ b/src/kms/key.rs @@ -136,9 +136,8 @@ impl LocalKey { /// Perform AEAD message encryption with this encryption key pub fn aead_encrypt(&self, message: &[u8], nonce: &[u8], aad: &[u8]) -> Result, Error> { let params = self.inner.aead_params(); - let enc_size = message.len() + params.nonce_length + params.tag_length; - let mut buf = SecretBytes::with_capacity(enc_size); - buf.extend_from_slice(message); + let mut buf = + SecretBytes::from_slice_reserve(message, params.nonce_length + params.tag_length); self.inner.encrypt_in_place(&mut buf, nonce, aad)?; Ok(buf.into_vec()) } diff --git a/src/kms/mod.rs b/src/kms/mod.rs index 4a038c7e..0629bece 100644 --- a/src/kms/mod.rs +++ b/src/kms/mod.rs @@ -11,7 +11,8 @@ use crate::error::Error; mod envelope; pub use self::envelope::{ - crypto_box_seal, crypto_box_seal_open, derive_key_ecdh_1pu, derive_key_ecdh_es, + crypto_box, crypto_box_open, crypto_box_random_nonce, crypto_box_seal, crypto_box_seal_open, + derive_key_ecdh_1pu, derive_key_ecdh_es, }; mod entry; diff --git a/wrappers/python/aries_askar/__init__.py b/wrappers/python/aries_askar/__init__.py index 976c2d0b..6704c64b 100644 --- a/wrappers/python/aries_askar/__init__.py +++ b/wrappers/python/aries_askar/__init__.py @@ -4,6 +4,9 @@ from .error import StoreError, StoreErrorCode from .key import ( Key, + crypto_box, + crypto_box_open, + crypto_box_random_nonce, crypto_box_seal, crypto_box_seal_open, derive_key_ecdh_1pu, @@ -13,6 +16,9 @@ from .types import Entry, KeyAlg __all__ = ( + "crypto_box", + "crypto_box_open", + "crypto_box_random_nonce", "crypto_box_seal", "crypto_box_seal_open", "derive_key_ecdh_1pu", diff --git a/wrappers/python/aries_askar/bindings.py b/wrappers/python/aries_askar/bindings.py index 8835a004..3f12df0e 100644 --- a/wrappers/python/aries_askar/bindings.py +++ b/wrappers/python/aries_askar/bindings.py @@ -1032,6 +1032,51 @@ def key_verify_signature( return verify.value != 0 +def key_crypto_box_random_nonce() -> ByteBuffer: + buf = ByteBuffer() + do_call( + "askar_key_crypto_box_random_nonce", + byref(buf), + ) + return buf + + +def key_crypto_box( + recip_handle: LocalKeyHandle, + sender_handle: LocalKeyHandle, + message: Union[bytes, str, ByteBuffer], + nonce: Union[bytes, ByteBuffer], +) -> ByteBuffer: + buf = ByteBuffer() + do_call( + "askar_key_crypto_box", + recip_handle, + sender_handle, + encode_bytes(message), + encode_bytes(nonce), + byref(buf), + ) + return buf + + +def key_crypto_box_open( + recip_handle: LocalKeyHandle, + sender_handle: LocalKeyHandle, + message: Union[bytes, str, ByteBuffer], + nonce: Union[bytes, ByteBuffer], +) -> ByteBuffer: + buf = ByteBuffer() + do_call( + "askar_key_crypto_box_open", + recip_handle, + sender_handle, + encode_bytes(message), + encode_bytes(nonce), + byref(buf), + ) + return buf + + def key_crypto_box_seal( handle: LocalKeyHandle, message: Union[bytes, str, ByteBuffer], diff --git a/wrappers/python/aries_askar/key.py b/wrappers/python/aries_askar/key.py index 6c15866a..2821bd7d 100644 --- a/wrappers/python/aries_askar/key.py +++ b/wrappers/python/aries_askar/key.py @@ -16,15 +16,15 @@ def __init__(self, handle: bindings.LocalKeyHandle): @classmethod def generate(cls, alg: Union[str, KeyAlg], *, ephemeral: bool = False) -> "Key": - return Key(bindings.key_generate(alg, ephemeral)) + return cls(bindings.key_generate(alg, ephemeral)) @classmethod def from_secret_bytes(cls, alg: Union[str, KeyAlg], secret: bytes) -> "Key": - return Key(bindings.key_from_secret_bytes(alg, secret)) + return cls(bindings.key_from_secret_bytes(alg, secret)) @classmethod def from_public_bytes(cls, alg: Union[str, KeyAlg], public: bytes) -> "Key": - return Key(bindings.key_from_public_bytes(alg, public)) + return cls(bindings.key_from_public_bytes(alg, public)) @property def handle(self) -> bindings.LocalKeyHandle: @@ -40,10 +40,10 @@ def ephemeral(self) -> "Key": return bindings.key_get_ephemeral(self._handle) def convert_key(self, alg: Union[str, KeyAlg]) -> "Key": - return Key(bindings.key_convert(self._handle, alg)) + return self.__class__(bindings.key_convert(self._handle, alg)) def key_exchange(self, alg: Union[str, KeyAlg], pk: "Key") -> "Key": - return Key(bindings.key_exchange(alg, self._handle, pk._handle)) + return self.__class__(bindings.key_exchange(alg, self._handle, pk._handle)) def get_public_bytes(self) -> bytes: return bytes(bindings.key_get_public_bytes(self._handle)) @@ -86,18 +86,46 @@ def __repr__(self) -> str: ) +def crypto_box_random_nonce() -> bytes: + return bytes(bindings.key_crypto_box_random_nonce()) + + +def crypto_box( + recip_key: Key, + sender_key: Key, + message: Union[bytes, str], + nonce: bytes, +) -> bytes: + return bytes( + bindings.key_crypto_box(recip_key._handle, sender_key._handle, message, nonce) + ) + + +def crypto_box_open( + recip_key: Key, + sender_key: Key, + message: Union[bytes, str], + nonce: bytes, +) -> bytes: + return bytes( + bindings.key_crypto_box_open( + recip_key._handle, sender_key._handle, message, nonce + ) + ) + + def crypto_box_seal( - key: Key, + recip_key: Key, message: Union[bytes, str], ) -> bytes: - return bytes(bindings.key_crypto_box_seal(key._handle, message)) + return bytes(bindings.key_crypto_box_seal(recip_key._handle, message)) def crypto_box_seal_open( - key: Key, + recip_key: Key, ciphertext: bytes, ) -> bytes: - return bytes(bindings.key_crypto_box_seal_open(key._handle, ciphertext)) + return bytes(bindings.key_crypto_box_seal_open(recip_key._handle, ciphertext)) def derive_key_ecdh_1pu( diff --git a/wrappers/python/aries_askar/store.py b/wrappers/python/aries_askar/store.py index 319efa19..2c231cfd 100644 --- a/wrappers/python/aries_askar/store.py +++ b/wrappers/python/aries_askar/store.py @@ -44,6 +44,11 @@ def value(self) -> bytes: """Accessor for the entry value.""" return self._list.get_value(self._pos) + @property + def value_json(self) -> dict: + """Accessor for the entry value as JSON.""" + return json.loads(self.value) + @cached_property def tags(self) -> dict: """Accessor for the entry tags.""" @@ -74,6 +79,11 @@ def handle(self) -> EntryListHandle: """Accessor for the entry list handle.""" return self._handle + def __getitem__(self, index) -> Entry: + if not isinstance(index, int) or index < 0 or index >= self._len: + return IndexError() + return Entry(self._handle, index) + def __iter__(self): return self @@ -85,6 +95,9 @@ def __next__(self): else: raise StopIteration + def __len__(self) -> int: + return self._len + def __repr__(self) -> str: return f"" @@ -150,6 +163,11 @@ def handle(self) -> KeyEntryListHandle: """Accessor for the key entry list handle.""" return self._handle + def __getitem__(self, index) -> KeyEntry: + if not isinstance(index, int) or index < 0 or index >= self._len: + return IndexError() + return KeyEntry(self._handle, index) + def __iter__(self): return self @@ -161,6 +179,9 @@ def __next__(self): else: raise StopIteration + def __len__(self) -> int: + return self._len + def __repr__(self) -> str: return ( f"" From 4727d446b3bb50e5065a4227bbd03b5e4f3acd09 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Tue, 27 Apr 2021 13:37:05 -0700 Subject: [PATCH 062/116] restore normal perf test Signed-off-by: Andrew Whitehead --- wrappers/python/demo/perf.py | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/wrappers/python/demo/perf.py b/wrappers/python/demo/perf.py index da31d974..aaa80b4c 100644 --- a/wrappers/python/demo/perf.py +++ b/wrappers/python/demo/perf.py @@ -42,24 +42,23 @@ async def perf_test(): dur = time.perf_counter() - insert_start print(f"insert duration ({PERF_ROWS} rows): {dur:0.2f}s") - # fetch_start = time.perf_counter() - # async with store as session: - # tags = 0 - # for idx in range(PERF_ROWS): - # entry = await session.fetch("category", f"name-{idx}") - # tags += len(entry.tags) - # dur = time.perf_counter() - fetch_start - # print(f"fetch duration ({PERF_ROWS} rows, {tags} tags): {dur:0.2f}s") - - for _ in range(10): - rc = 0 + fetch_start = time.perf_counter() + async with store as session: tags = 0 - scan_start = time.perf_counter() - async for row in store.scan("category", {"~plaintag": "a", "enctag": "b"}): - rc += 1 - tags += len(row.tags) - dur = time.perf_counter() - scan_start - print(f"scan duration ({rc} rows, {tags} tags): {dur:0.2f}s") + for idx in range(PERF_ROWS): + entry = await session.fetch("category", f"name-{idx}") + tags += len(entry.tags) + dur = time.perf_counter() - fetch_start + print(f"fetch duration ({PERF_ROWS} rows, {tags} tags): {dur:0.2f}s") + + rc = 0 + tags = 0 + scan_start = time.perf_counter() + async for row in store.scan("category", {"~plaintag": "a", "enctag": "b"}): + rc += 1 + tags += len(row.tags) + dur = time.perf_counter() - scan_start + print(f"scan duration ({rc} rows, {tags} tags): {dur:0.2f}s") await store.close() From b47198c2debe6c50cdc70f454e9510e66fb36f5f Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Tue, 27 Apr 2021 14:22:14 -0700 Subject: [PATCH 063/116] renames for clarity Signed-off-by: Andrew Whitehead --- askar-crypto/src/alg/aesgcm.rs | 16 +++++------ askar-crypto/src/alg/any.rs | 52 +++++++++++++++++----------------- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/askar-crypto/src/alg/aesgcm.rs b/askar-crypto/src/alg/aesgcm.rs index 7f975279..3687f4ea 100644 --- a/askar-crypto/src/alg/aesgcm.rs +++ b/askar-crypto/src/alg/aesgcm.rs @@ -25,18 +25,18 @@ pub trait AesGcmType: 'static { const JWK_ALG: &'static str; } -pub struct A128; +pub struct A128GCM; -impl AesGcmType for A128 { +impl AesGcmType for A128GCM { type Aead = Aes128Gcm; const ALG_TYPE: AesTypes = AesTypes::A128GCM; const JWK_ALG: &'static str = "A128GCM"; } -pub struct A256; +pub struct A256GCM; -impl AesGcmType for A256 { +impl AesGcmType for A256GCM { type Aead = Aes256Gcm; const ALG_TYPE: AesTypes = AesTypes::A256GCM; @@ -245,8 +245,8 @@ mod tests { key.decrypt_in_place(&mut buffer, &nonce, &[]).unwrap(); assert_eq!(&buffer[..], input); } - test_encrypt::(); - test_encrypt::(); + test_encrypt::(); + test_encrypt::(); } #[test] @@ -258,7 +258,7 @@ mod tests { let deser: &[u8] = serde_cbor::from_slice(bytes.as_ref()).unwrap(); assert_eq!(deser, sk.as_ref()); } - test_serialize::(); - test_serialize::(); + test_serialize::(); + test_serialize::(); } } diff --git a/askar-crypto/src/alg/any.rs b/askar-crypto/src/alg/any.rs index ba26a007..7f4fd0ed 100644 --- a/askar-crypto/src/alg/any.rs +++ b/askar-crypto/src/alg/any.rs @@ -5,7 +5,7 @@ use core::{ fmt::Debug, }; -use super::aesgcm::{AesGcmKey, A128, A256}; +use super::aesgcm::{AesGcmKey, A128GCM, A256GCM}; use super::chacha20::{Chacha20Key, C20P, XC20P}; use super::ed25519::{self, Ed25519KeyPair}; use super::k256::{self, K256KeyPair}; @@ -23,21 +23,21 @@ use crate::{ }; #[derive(Debug)] -pub struct KeyT(T); +pub struct KeyT(T); -pub type AnyKey = KeyT; +pub type AnyKey = KeyT; impl AnyKey { pub fn algorithm(&self) -> KeyAlg { self.0.algorithm() } - fn assume(&self) -> &K { + fn assume(&self) -> &K { self.downcast_ref().expect("Error assuming key type") } #[inline] - pub fn downcast_ref(&self) -> Option<&K> { + pub fn downcast_ref(&self) -> Option<&K> { self.0.as_any().downcast_ref() } @@ -145,8 +145,8 @@ impl AnyKeyCreate for Arc { #[inline] fn generate_any(alg: KeyAlg) -> Result { match alg { - KeyAlg::Aes(AesTypes::A128GCM) => AesGcmKey::::generate().map(R::alloc_key), - KeyAlg::Aes(AesTypes::A256GCM) => AesGcmKey::::generate().map(R::alloc_key), + KeyAlg::Aes(AesTypes::A128GCM) => AesGcmKey::::generate().map(R::alloc_key), + KeyAlg::Aes(AesTypes::A256GCM) => AesGcmKey::::generate().map(R::alloc_key), KeyAlg::Chacha20(Chacha20Types::C20P) => Chacha20Key::::generate().map(R::alloc_key), KeyAlg::Chacha20(Chacha20Types::XC20P) => { Chacha20Key::::generate().map(R::alloc_key) @@ -190,10 +190,10 @@ fn from_public_bytes_any(alg: KeyAlg, public: &[u8]) -> Result(alg: KeyAlg, secret: &[u8]) -> Result { match alg { KeyAlg::Aes(AesTypes::A128GCM) => { - AesGcmKey::::from_secret_bytes(secret).map(R::alloc_key) + AesGcmKey::::from_secret_bytes(secret).map(R::alloc_key) } KeyAlg::Aes(AesTypes::A256GCM) => { - AesGcmKey::::from_secret_bytes(secret).map(R::alloc_key) + AesGcmKey::::from_secret_bytes(secret).map(R::alloc_key) } KeyAlg::Chacha20(Chacha20Types::C20P) => { Chacha20Key::::from_secret_bytes(secret).map(R::alloc_key) @@ -228,10 +228,10 @@ where { match alg { KeyAlg::Aes(AesTypes::A128GCM) => { - AesGcmKey::::from_key_exchange(secret, public).map(R::alloc_key) + AesGcmKey::::from_key_exchange(secret, public).map(R::alloc_key) } KeyAlg::Aes(AesTypes::A256GCM) => { - AesGcmKey::::from_key_exchange(secret, public).map(R::alloc_key) + AesGcmKey::::from_key_exchange(secret, public).map(R::alloc_key) } KeyAlg::Chacha20(Chacha20Types::C20P) => { Chacha20Key::::from_key_exchange(secret, public).map(R::alloc_key) @@ -256,10 +256,10 @@ fn from_key_derivation_any( ) -> Result { match alg { KeyAlg::Aes(AesTypes::A128GCM) => { - AesGcmKey::::from_key_derivation(derive).map(R::alloc_key) + AesGcmKey::::from_key_derivation(derive).map(R::alloc_key) } KeyAlg::Aes(AesTypes::A256GCM) => { - AesGcmKey::::from_key_derivation(derive).map(R::alloc_key) + AesGcmKey::::from_key_derivation(derive).map(R::alloc_key) } KeyAlg::Chacha20(Chacha20Types::C20P) => { Chacha20Key::::from_key_derivation(derive).map(R::alloc_key) @@ -363,11 +363,11 @@ impl AnyKey { pub fn to_secret_bytes(&self) -> Result { match self.key_type_id() { - s if s == TypeId::of::>() => { - Ok(self.assume::>().to_secret_bytes()?) + s if s == TypeId::of::>() => { + Ok(self.assume::>().to_secret_bytes()?) } - s if s == TypeId::of::>() => { - Ok(self.assume::>().to_secret_bytes()?) + s if s == TypeId::of::>() => { + Ok(self.assume::>().to_secret_bytes()?) } s if s == TypeId::of::>() => { Ok(self.assume::>().to_secret_bytes()?) @@ -414,8 +414,8 @@ impl AnyKey { fn key_as_aead(&self) -> Result<&dyn KeyAeadInPlace, Error> { Ok(match_key_types! { self, - AesGcmKey, - AesGcmKey, + AesGcmKey, + AesGcmKey, Chacha20Key, Chacha20Key; "AEAD is not supported for this key type" @@ -455,8 +455,8 @@ impl ToJwk for AnyKey { fn to_jwk_encoder(&self, enc: &mut JwkEncoder<'_>) -> Result<(), Error> { let key: &dyn ToJwk = match_key_types! { self, - AesGcmKey, - AesGcmKey, + AesGcmKey, + AesGcmKey, Chacha20Key, Chacha20Key, Ed25519KeyPair, @@ -507,29 +507,29 @@ impl KeySigVerify for AnyKey { // may want to implement in-place initialization to avoid copies trait AllocKey { - fn alloc_key(key: K) -> Self; + fn alloc_key(key: K) -> Self; } impl AllocKey for Arc { #[inline(always)] - fn alloc_key(key: K) -> Self { + fn alloc_key(key: K) -> Self { Self::from_key(key) } } impl AllocKey for Box { #[inline(always)] - fn alloc_key(key: K) -> Self { + fn alloc_key(key: K) -> Self { Self::from_key(key) } } -pub trait KeyAsAny: HasKeyAlg + 'static { +pub trait AnyKeyAlg: HasKeyAlg + 'static { fn as_any(&self) -> &dyn Any; } // implement for all concrete key types -impl KeyAsAny for K { +impl AnyKeyAlg for K { fn as_any(&self) -> &dyn Any { self } From ca5ab4662377508755070f5b0f2cd832d314176f Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Tue, 27 Apr 2021 14:34:31 -0700 Subject: [PATCH 064/116] require debug impls Signed-off-by: Andrew Whitehead --- askar-crypto/src/alg/aesgcm.rs | 2 ++ askar-crypto/src/alg/chacha20.rs | 2 ++ askar-crypto/src/alg/ed25519.rs | 10 +++++++++- askar-crypto/src/buffer/hash.rs | 5 ++++- askar-crypto/src/buffer/mod.rs | 4 ++-- askar-crypto/src/buffer/writer.rs | 1 + askar-crypto/src/encrypt/mod.rs | 2 +- askar-crypto/src/jwk/encode.rs | 3 ++- askar-crypto/src/jwk/ops.rs | 1 + askar-crypto/src/kdf/argon2.rs | 3 ++- askar-crypto/src/kdf/concat.rs | 8 +++++--- askar-crypto/src/kdf/ecdh_1pu.rs | 1 + askar-crypto/src/kdf/ecdh_es.rs | 1 + askar-crypto/src/lib.rs | 2 +- 14 files changed, 34 insertions(+), 11 deletions(-) diff --git a/askar-crypto/src/alg/aesgcm.rs b/askar-crypto/src/alg/aesgcm.rs index 3687f4ea..f4862091 100644 --- a/askar-crypto/src/alg/aesgcm.rs +++ b/askar-crypto/src/alg/aesgcm.rs @@ -25,6 +25,7 @@ pub trait AesGcmType: 'static { const JWK_ALG: &'static str; } +#[derive(Debug)] pub struct A128GCM; impl AesGcmType for A128GCM { @@ -34,6 +35,7 @@ impl AesGcmType for A128GCM { const JWK_ALG: &'static str = "A128GCM"; } +#[derive(Debug)] pub struct A256GCM; impl AesGcmType for A256GCM { diff --git a/askar-crypto/src/alg/chacha20.rs b/askar-crypto/src/alg/chacha20.rs index cfc7d3de..01c6beed 100644 --- a/askar-crypto/src/alg/chacha20.rs +++ b/askar-crypto/src/alg/chacha20.rs @@ -26,6 +26,7 @@ pub trait Chacha20Type: 'static { const JWK_ALG: &'static str; } +#[derive(Debug)] pub struct C20P; impl Chacha20Type for C20P { @@ -35,6 +36,7 @@ impl Chacha20Type for C20P { const JWK_ALG: &'static str = "C20P"; } +#[derive(Debug)] pub struct XC20P; impl Chacha20Type for XC20P { diff --git a/askar-crypto/src/alg/ed25519.rs b/askar-crypto/src/alg/ed25519.rs index 5ede41bc..29b560da 100644 --- a/askar-crypto/src/alg/ed25519.rs +++ b/askar-crypto/src/alg/ed25519.rs @@ -278,7 +278,6 @@ impl FromJwk for Ed25519KeyPair { } } -/// FIXME implement debug // SECURITY: ExpandedSecretKey zeroizes on drop pub struct Ed25519SigningKey<'p>(ExpandedSecretKey, &'p PublicKey); @@ -288,6 +287,15 @@ impl Ed25519SigningKey<'_> { } } +impl Debug for Ed25519SigningKey<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("Ed25519SigningKey") + .field("secret", &"") + .field("public", &self.1) + .finish() + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/askar-crypto/src/buffer/hash.rs b/askar-crypto/src/buffer/hash.rs index a84df250..22a5181e 100644 --- a/askar-crypto/src/buffer/hash.rs +++ b/askar-crypto/src/buffer/hash.rs @@ -1,9 +1,12 @@ +use core::fmt::Debug; + use digest::Digest; use crate::generic_array::GenericArray; use crate::{buffer::WriteBuffer, error::Error}; +#[derive(Debug)] pub struct HashBuffer(D); impl HashBuffer { @@ -16,7 +19,7 @@ impl HashBuffer { } } -impl WriteBuffer for HashBuffer { +impl WriteBuffer for HashBuffer { fn buffer_write(&mut self, data: &[u8]) -> Result<(), Error> { self.0.update(data); Ok(()) diff --git a/askar-crypto/src/buffer/mod.rs b/askar-crypto/src/buffer/mod.rs index 0ca1842b..1ff52ec7 100644 --- a/askar-crypto/src/buffer/mod.rs +++ b/askar-crypto/src/buffer/mod.rs @@ -1,6 +1,6 @@ #[cfg(feature = "alloc")] use alloc::vec::Vec; -use core::ops::Range; +use core::{fmt::Debug, ops::Range}; use crate::error::Error; @@ -21,7 +21,7 @@ pub use self::string::HexRepr; mod writer; pub use self::writer::Writer; -pub trait WriteBuffer { +pub trait WriteBuffer: Debug { fn buffer_write(&mut self, data: &[u8]) -> Result<(), Error>; } diff --git a/askar-crypto/src/buffer/writer.rs b/askar-crypto/src/buffer/writer.rs index 58f1e7b0..bdbe8663 100644 --- a/askar-crypto/src/buffer/writer.rs +++ b/askar-crypto/src/buffer/writer.rs @@ -5,6 +5,7 @@ use core::{fmt, ops::Range}; use super::{ResizeBuffer, WriteBuffer}; use crate::error::Error; +#[derive(Debug)] pub struct Writer<'w, B: ?Sized> { inner: &'w mut B, pos: usize, diff --git a/askar-crypto/src/encrypt/mod.rs b/askar-crypto/src/encrypt/mod.rs index e78fbb65..f7982d18 100644 --- a/askar-crypto/src/encrypt/mod.rs +++ b/askar-crypto/src/encrypt/mod.rs @@ -42,7 +42,7 @@ pub trait KeyAeadMeta { } } -#[derive(Clone, Copy, Default, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct KeyAeadParams { pub nonce_length: usize, pub tag_length: usize, diff --git a/askar-crypto/src/jwk/encode.rs b/askar-crypto/src/jwk/encode.rs index 8ea88694..75cced3a 100644 --- a/askar-crypto/src/jwk/encode.rs +++ b/askar-crypto/src/jwk/encode.rs @@ -7,13 +7,14 @@ use crate::{ use super::ops::KeyOpsSet; -#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum JwkEncoderMode { PublicKey, SecretKey, Thumbprint, } +#[derive(Debug)] pub struct JwkEncoder<'b> { buffer: &'b mut dyn WriteBuffer, empty: bool, diff --git a/askar-crypto/src/jwk/ops.rs b/askar-crypto/src/jwk/ops.rs index 70e8b3e3..7282792e 100644 --- a/askar-crypto/src/jwk/ops.rs +++ b/askar-crypto/src/jwk/ops.rs @@ -158,6 +158,7 @@ impl IntoIterator for &KeyOpsSet { } } +#[derive(Debug)] pub struct KeyOpsIter { index: usize, value: KeyOpsSet, diff --git a/askar-crypto/src/kdf/argon2.rs b/askar-crypto/src/kdf/argon2.rs index 956d0b64..3036247d 100644 --- a/askar-crypto/src/kdf/argon2.rs +++ b/askar-crypto/src/kdf/argon2.rs @@ -22,7 +22,7 @@ pub const PARAMS_MODERATE: Params = Params { time_cost: 6, }; -#[derive(Copy, Clone, Debug)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Params { alg: Algorithm, version: Version, @@ -30,6 +30,7 @@ pub struct Params { time_cost: u32, } +#[derive(Clone, Copy, Debug)] pub struct Argon2; impl Argon2 { diff --git a/askar-crypto/src/kdf/concat.rs b/askar-crypto/src/kdf/concat.rs index f7b3a1da..3c619d66 100644 --- a/askar-crypto/src/kdf/concat.rs +++ b/askar-crypto/src/kdf/concat.rs @@ -1,6 +1,6 @@ //! ConcatKDF from NIST 800-56ar for ECDH-ES / ECDH-1PU -use core::marker::PhantomData; +use core::{fmt::Debug, marker::PhantomData}; use digest::Digest; @@ -8,9 +8,10 @@ use crate::generic_array::GenericArray; use crate::{buffer::WriteBuffer, error::Error}; +#[derive(Clone, Copy, Debug)] pub struct ConcatKDF(PhantomData); -#[derive(Clone, Copy, Default)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct ConcatKDFParams<'p> { pub alg: &'p [u8], pub apu: &'p [u8], @@ -49,6 +50,7 @@ where } } +#[derive(Debug)] pub struct ConcatKDFHash { hasher: H, counter: u32, @@ -88,7 +90,7 @@ impl ConcatKDFHash { } } -impl WriteBuffer for ConcatKDFHash { +impl WriteBuffer for ConcatKDFHash { fn buffer_write(&mut self, data: &[u8]) -> Result<(), Error> { self.hasher.update(data); Ok(()) diff --git a/askar-crypto/src/kdf/ecdh_1pu.rs b/askar-crypto/src/kdf/ecdh_1pu.rs index 61b2d4c3..618db626 100644 --- a/askar-crypto/src/kdf/ecdh_1pu.rs +++ b/askar-crypto/src/kdf/ecdh_1pu.rs @@ -7,6 +7,7 @@ use super::{ }; use crate::error::Error; +#[derive(Debug)] pub struct Ecdh1PU<'d, Key: KeyExchange + ?Sized> { ephem_key: &'d Key, send_key: &'d Key, diff --git a/askar-crypto/src/kdf/ecdh_es.rs b/askar-crypto/src/kdf/ecdh_es.rs index 237338f1..d177158c 100644 --- a/askar-crypto/src/kdf/ecdh_es.rs +++ b/askar-crypto/src/kdf/ecdh_es.rs @@ -7,6 +7,7 @@ use super::{ }; use crate::error::Error; +#[derive(Debug)] pub struct EcdhEs<'d, Key> where Key: KeyExchange + ?Sized, diff --git a/askar-crypto/src/lib.rs b/askar-crypto/src/lib.rs index ec8265f8..2fae8225 100644 --- a/askar-crypto/src/lib.rs +++ b/askar-crypto/src/lib.rs @@ -1,5 +1,5 @@ #![no_std] -// #![deny(missing_debug_implementations)] +#![deny(missing_debug_implementations)] // #![deny(missing_docs)] #[cfg(feature = "alloc")] From b4d96cbad4eab8a29953bdff71ddfcd17812f30c Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Tue, 27 Apr 2021 15:32:59 -0700 Subject: [PATCH 065/116] adding docstrings Signed-off-by: Andrew Whitehead --- askar-crypto/src/alg/aesgcm.rs | 2 ++ askar-crypto/src/alg/any.rs | 9 +++++++++ askar-crypto/src/alg/chacha20.rs | 2 ++ askar-crypto/src/alg/ed25519.rs | 2 ++ askar-crypto/src/alg/k256.rs | 2 ++ askar-crypto/src/alg/mod.rs | 6 ++++++ askar-crypto/src/alg/p256.rs | 2 ++ askar-crypto/src/alg/x25519.rs | 2 ++ askar-crypto/src/buffer/array.rs | 9 ++++++++- askar-crypto/src/buffer/hash.rs | 3 +++ askar-crypto/src/buffer/mod.rs | 10 ++++++++++ askar-crypto/src/encrypt/mod.rs | 4 +++- askar-crypto/src/encrypt/nacl_box.rs | 2 ++ askar-crypto/src/jwk/mod.rs | 2 ++ askar-crypto/src/kdf/argon2.rs | 9 +++++++++ askar-crypto/src/kdf/concat.rs | 14 ++++++++++++++ askar-crypto/src/kdf/ecdh_1pu.rs | 4 ++++ askar-crypto/src/kdf/ecdh_es.rs | 4 ++++ askar-crypto/src/kdf/mod.rs | 11 +++++++++++ askar-crypto/src/lib.rs | 2 ++ askar-crypto/src/random.rs | 9 +++++++-- askar-crypto/src/repr.rs | 24 +++++++++++++++++++++--- askar-crypto/src/sign.rs | 26 +++++++++++++++++++------- 23 files changed, 146 insertions(+), 14 deletions(-) diff --git a/askar-crypto/src/alg/aesgcm.rs b/askar-crypto/src/alg/aesgcm.rs index f4862091..7bdc224b 100644 --- a/askar-crypto/src/alg/aesgcm.rs +++ b/askar-crypto/src/alg/aesgcm.rs @@ -1,3 +1,5 @@ +//! AES-GCM (Advanced Encryption Standard, Galois Counter Mode) key support + use core::fmt::{self, Debug, Formatter}; use aead::{Aead, AeadInPlace, NewAead}; diff --git a/askar-crypto/src/alg/any.rs b/askar-crypto/src/alg/any.rs index 7f4fd0ed..5bd6c0e8 100644 --- a/askar-crypto/src/alg/any.rs +++ b/askar-crypto/src/alg/any.rs @@ -25,6 +25,7 @@ use crate::{ #[derive(Debug)] pub struct KeyT(T); +/// The type-erased representation for a concrete key instance pub type AnyKey = KeyT; impl AnyKey { @@ -53,22 +54,30 @@ impl std::panic::UnwindSafe for AnyKey {} #[cfg(feature = "std")] impl std::panic::RefUnwindSafe for AnyKey {} +/// Create `AnyKey` instances from various sources pub trait AnyKeyCreate: Sized { + /// Generate a new key for the given key algorithm. fn generate(alg: KeyAlg) -> Result; + /// Load a public key from its byte representation fn from_public_bytes(alg: KeyAlg, public: &[u8]) -> Result; + /// Load a secret key or keypair from its byte representation fn from_secret_bytes(alg: KeyAlg, secret: &[u8]) -> Result; + /// Convert from a concrete key instance fn from_key(key: K) -> Self; + /// Create a new key instance from a key exchange fn from_key_exchange(alg: KeyAlg, secret: &Sk, public: &Pk) -> Result where Sk: KeyExchange + ?Sized, Pk: ?Sized; + /// Create a new key instance from a key derivation fn from_key_derivation(alg: KeyAlg, derive: impl KeyDerivation) -> Result; + /// Derive the corresponding key for the provided key algorithm fn convert_key(&self, alg: KeyAlg) -> Result; } diff --git a/askar-crypto/src/alg/chacha20.rs b/askar-crypto/src/alg/chacha20.rs index 01c6beed..6c696c66 100644 --- a/askar-crypto/src/alg/chacha20.rs +++ b/askar-crypto/src/alg/chacha20.rs @@ -1,3 +1,5 @@ +//! ChaCha20 and XChaCha20 stream ciphers with AEAD + use core::fmt::{self, Debug, Formatter}; use aead::{Aead, AeadInPlace, NewAead}; diff --git a/askar-crypto/src/alg/ed25519.rs b/askar-crypto/src/alg/ed25519.rs index 29b560da..5d271772 100644 --- a/askar-crypto/src/alg/ed25519.rs +++ b/askar-crypto/src/alg/ed25519.rs @@ -1,3 +1,5 @@ +//! Ed25519 signature and verification key support + use core::{ convert::{TryFrom, TryInto}, fmt::{self, Debug, Formatter}, diff --git a/askar-crypto/src/alg/k256.rs b/askar-crypto/src/alg/k256.rs index b7b63aa8..e7bee20e 100644 --- a/askar-crypto/src/alg/k256.rs +++ b/askar-crypto/src/alg/k256.rs @@ -1,3 +1,5 @@ +//! Elliptic curve ECDH and ECDSA support on curve secp256k1 + use core::convert::{TryFrom, TryInto}; use k256::{ diff --git a/askar-crypto/src/alg/mod.rs b/askar-crypto/src/alg/mod.rs index efb544a8..3a8d50bb 100644 --- a/askar-crypto/src/alg/mod.rs +++ b/askar-crypto/src/alg/mod.rs @@ -1,3 +1,5 @@ +//! Supported key algorithms + use core::{ fmt::{self, Debug, Display, Formatter}, str::FromStr, @@ -134,6 +136,7 @@ impl Display for KeyAlg { } } +/// Supported BLS12-381 groups #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Zeroize)] pub enum BlsGroups { /// A key or signature represented by an element from the BLS12-381 G1 group @@ -169,6 +172,9 @@ pub enum EcCurves { Secp256k1, } +/// A common trait for accessing the algorithm of a key, +/// used when converting to generic `AnyKey` instances. pub trait HasKeyAlg: Debug { + /// Get the corresponding key algorithm. fn algorithm(&self) -> KeyAlg; } diff --git a/askar-crypto/src/alg/p256.rs b/askar-crypto/src/alg/p256.rs index ec0242f6..cbe3083a 100644 --- a/askar-crypto/src/alg/p256.rs +++ b/askar-crypto/src/alg/p256.rs @@ -1,3 +1,5 @@ +//! Elliptic curve ECDH and ECDSA support on curve secp256r1 + use core::convert::{TryFrom, TryInto}; use p256::{ diff --git a/askar-crypto/src/alg/x25519.rs b/askar-crypto/src/alg/x25519.rs index d9cf0669..e4feb6d2 100644 --- a/askar-crypto/src/alg/x25519.rs +++ b/askar-crypto/src/alg/x25519.rs @@ -1,3 +1,5 @@ +//! X25519 key exchange support on Curve25519 + use core::{ convert::{TryFrom, TryInto}, fmt::{self, Debug, Formatter}, diff --git a/askar-crypto/src/buffer/array.rs b/askar-crypto/src/buffer/array.rs index 5ffdc0d6..2ac1e2b8 100644 --- a/askar-crypto/src/buffer/array.rs +++ b/askar-crypto/src/buffer/array.rs @@ -15,29 +15,36 @@ use crate::random::fill_random; pub struct ArrayKey>(GenericArray); impl> ArrayKey { + /// The array length in bytes pub const SIZE: usize = L::USIZE; + /// Copy from a slice of the same length as this array #[inline] pub fn copy_from_slice>(&mut self, data: D) { self.0[..].copy_from_slice(data.as_ref()); } + /// Convert this array to a non-zeroing GenericArray instance #[inline] pub fn extract(self) -> GenericArray { self.0.clone() } + /// Create a new array instance from a slice of bytes. + /// Like <&GenericArray>::from_slice, panics if the length of the slice + /// is incorrect. #[inline] pub fn from_slice(data: &[u8]) -> Self { - // like <&GenericArray>::from_slice, panics if the length is incorrect Self(GenericArray::from_slice(data).clone()) } + /// Get the length of the array #[inline] pub fn len(&self) -> usize { self.0.len() } + /// Create a new array of random bytes #[inline] pub fn random() -> Self { let mut slf = GenericArray::default(); diff --git a/askar-crypto/src/buffer/hash.rs b/askar-crypto/src/buffer/hash.rs index 22a5181e..0807ed62 100644 --- a/askar-crypto/src/buffer/hash.rs +++ b/askar-crypto/src/buffer/hash.rs @@ -6,14 +6,17 @@ use crate::generic_array::GenericArray; use crate::{buffer::WriteBuffer, error::Error}; +/// A `WriteBuffer` implementation which hashes its input #[derive(Debug)] pub struct HashBuffer(D); impl HashBuffer { + /// Create a new instance pub fn new() -> Self { Self(D::new()) } + /// Finalize the hasher and extract the result pub fn finalize(self) -> GenericArray { self.0.finalize() } diff --git a/askar-crypto/src/buffer/mod.rs b/askar-crypto/src/buffer/mod.rs index 1ff52ec7..d5208322 100644 --- a/askar-crypto/src/buffer/mod.rs +++ b/askar-crypto/src/buffer/mod.rs @@ -1,3 +1,5 @@ +//! Structures and traits for representing byte ranges in memory + #[cfg(feature = "alloc")] use alloc::vec::Vec; use core::{fmt::Debug, ops::Range}; @@ -21,17 +23,25 @@ pub use self::string::HexRepr; mod writer; pub use self::writer::Writer; +/// Support for writing to a byte buffer pub trait WriteBuffer: Debug { + /// Append a slice to the buffer fn buffer_write(&mut self, data: &[u8]) -> Result<(), Error>; } +/// Support for writing to, accessing, and resizing a byte buffer pub trait ResizeBuffer: WriteBuffer + AsRef<[u8]> + AsMut<[u8]> { + /// Insert a slice at the given position in the buffer fn buffer_insert(&mut self, pos: usize, data: &[u8]) -> Result<(), Error>; + /// Remove an exclusive range from the buffer fn buffer_remove(&mut self, range: Range) -> Result<(), Error>; + /// Resize the buffer, truncating or padding it with zeroes fn buffer_resize(&mut self, len: usize) -> Result<(), Error>; + /// Extend the buffer with `len` bytes of zeroes and return + /// a mutable reference to the slice fn buffer_extend(&mut self, len: usize) -> Result<&mut [u8], Error> { let pos = self.as_ref().len(); let end = pos + len; diff --git a/askar-crypto/src/encrypt/mod.rs b/askar-crypto/src/encrypt/mod.rs index f7982d18..c620f730 100644 --- a/askar-crypto/src/encrypt/mod.rs +++ b/askar-crypto/src/encrypt/mod.rs @@ -1,3 +1,5 @@ +//! AEAD encryption traits and parameters + use crate::{ buffer::ResizeBuffer, error::Error, @@ -5,7 +7,7 @@ use crate::{ random::fill_random, }; -#[cfg(feature = "alloc")] // FIXME - support non-alloc +#[cfg(feature = "alloc")] // FIXME - support non-alloc? pub mod nacl_box; /// Trait for key types which perform AEAD encryption diff --git a/askar-crypto/src/encrypt/nacl_box.rs b/askar-crypto/src/encrypt/nacl_box.rs index 4e271bb0..0faf6af8 100644 --- a/askar-crypto/src/encrypt/nacl_box.rs +++ b/askar-crypto/src/encrypt/nacl_box.rs @@ -1,3 +1,5 @@ +//! Compatibility with libsodium's crypto_box construct + use crate::{ buffer::Writer, generic_array::{typenum::Unsigned, GenericArray}, diff --git a/askar-crypto/src/jwk/mod.rs b/askar-crypto/src/jwk/mod.rs index df263ac9..65822913 100644 --- a/askar-crypto/src/jwk/mod.rs +++ b/askar-crypto/src/jwk/mod.rs @@ -1,3 +1,5 @@ +//! JSON Web Key (JWK) support + #[cfg(feature = "alloc")] use alloc::{string::String, vec::Vec}; diff --git a/askar-crypto/src/kdf/argon2.rs b/askar-crypto/src/kdf/argon2.rs index 3036247d..27f8c14e 100644 --- a/askar-crypto/src/kdf/argon2.rs +++ b/askar-crypto/src/kdf/argon2.rs @@ -1,3 +1,5 @@ +//! Argon2 key derivation from a password + use crate::{ error::Error, generic_array::typenum::{Unsigned, U16}, @@ -5,16 +7,20 @@ use crate::{ pub use argon2::{Algorithm, Version}; +/// The size of the password salt pub type SaltSize = U16; +/// The length of the password salt pub const SALT_LENGTH: usize = SaltSize::USIZE; +/// Standard parameters for 'interactive' level pub const PARAMS_INTERACTIVE: Params = Params { alg: Algorithm::Argon2i, version: Version::V0x13, mem_cost: 32768, time_cost: 4, }; +/// Standard parameters for 'moderate' level pub const PARAMS_MODERATE: Params = Params { alg: Algorithm::Argon2i, version: Version::V0x13, @@ -22,6 +28,7 @@ pub const PARAMS_MODERATE: Params = Params { time_cost: 6, }; +/// Parameters to the argon2 key derivation #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Params { alg: Algorithm, @@ -30,10 +37,12 @@ pub struct Params { time_cost: u32, } +/// Struct wrapping the KDF functionality #[derive(Clone, Copy, Debug)] pub struct Argon2; impl Argon2 { + /// Derive the key and write it to the provided buffer pub fn derive_key( password: &[u8], salt: &[u8], diff --git a/askar-crypto/src/kdf/concat.rs b/askar-crypto/src/kdf/concat.rs index 3c619d66..a16c5f73 100644 --- a/askar-crypto/src/kdf/concat.rs +++ b/askar-crypto/src/kdf/concat.rs @@ -8,15 +8,22 @@ use crate::generic_array::GenericArray; use crate::{buffer::WriteBuffer, error::Error}; +/// A struct providing the key derivation for a particular hash function #[derive(Clone, Copy, Debug)] pub struct ConcatKDF(PhantomData); +/// Parameters for the key derivation #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct ConcatKDFParams<'p> { + /// The algorithm name pub alg: &'p [u8], + /// Sender identifier (PartyUInfo) pub apu: &'p [u8], + /// Recipient identifier (PartyVInfo) pub apv: &'p [u8], + /// SuppPubInfo as defined by the application pub pub_info: &'p [u8], + /// SuppPrivInfo as defined by the application pub prv_info: &'p [u8], } @@ -24,6 +31,7 @@ impl ConcatKDF where H: Digest, { + /// Perform the key derivation and write the result to the provided buffer pub fn derive_key( message: &[u8], params: ConcatKDFParams<'_>, @@ -50,6 +58,7 @@ where } } +/// Core hashing implementation of the multi-pass key derivation #[derive(Debug)] pub struct ConcatKDFHash { hasher: H, @@ -57,6 +66,7 @@ pub struct ConcatKDFHash { } impl ConcatKDFHash { + /// Create a new instance pub fn new() -> Self { Self { hasher: H::new(), @@ -64,15 +74,18 @@ impl ConcatKDFHash { } } + /// Start a new pass of the key derivation pub fn start_pass(&mut self) { self.hasher.update(self.counter.to_be_bytes()); self.counter += 1; } + /// Hash input to the key derivation pub fn hash_message(&mut self, data: &[u8]) { self.hasher.update(data); } + /// Hash the parameters of the key derivation pub fn hash_params(&mut self, params: ConcatKDFParams<'_>) { let hash = &mut self.hasher; hash.update((params.alg.len() as u32).to_be_bytes()); @@ -85,6 +98,7 @@ impl ConcatKDFHash { hash.update(params.prv_info); } + /// Complete this pass of the key derivation, returning the result pub fn finish_pass(&mut self) -> GenericArray { self.hasher.finalize_reset() } diff --git a/askar-crypto/src/kdf/ecdh_1pu.rs b/askar-crypto/src/kdf/ecdh_1pu.rs index 618db626..262f2794 100644 --- a/askar-crypto/src/kdf/ecdh_1pu.rs +++ b/askar-crypto/src/kdf/ecdh_1pu.rs @@ -1,3 +1,5 @@ +//! ECDH-1PU key derivation + use sha2::Sha256; use zeroize::Zeroize; @@ -7,6 +9,7 @@ use super::{ }; use crate::error::Error; +/// An instantiation of the ECDH-1PU key derivation #[derive(Debug)] pub struct Ecdh1PU<'d, Key: KeyExchange + ?Sized> { ephem_key: &'d Key, @@ -18,6 +21,7 @@ pub struct Ecdh1PU<'d, Key: KeyExchange + ?Sized> { } impl<'d, Key: KeyExchange + ?Sized> Ecdh1PU<'d, Key> { + /// Create a new KDF instance pub fn new( ephem_key: &'d Key, send_key: &'d Key, diff --git a/askar-crypto/src/kdf/ecdh_es.rs b/askar-crypto/src/kdf/ecdh_es.rs index d177158c..4399f849 100644 --- a/askar-crypto/src/kdf/ecdh_es.rs +++ b/askar-crypto/src/kdf/ecdh_es.rs @@ -1,3 +1,5 @@ +//! ECDH-ES key derivation + use sha2::Sha256; use zeroize::Zeroize; @@ -7,6 +9,7 @@ use super::{ }; use crate::error::Error; +/// An instantiation of the ECDH-ES key derivation #[derive(Debug)] pub struct EcdhEs<'d, Key> where @@ -20,6 +23,7 @@ where } impl<'d, Key: KeyExchange + ?Sized> EcdhEs<'d, Key> { + /// Create a new KDF instance pub fn new( ephem_key: &'d Key, recip_key: &'d Key, diff --git a/askar-crypto/src/kdf/mod.rs b/askar-crypto/src/kdf/mod.rs index 06fe548e..321ed22f 100644 --- a/askar-crypto/src/kdf/mod.rs +++ b/askar-crypto/src/kdf/mod.rs @@ -1,3 +1,5 @@ +//! Key derivation function traits and implementations + #[cfg(feature = "alloc")] use crate::buffer::SecretBytes; use crate::{buffer::WriteBuffer, error::Error}; @@ -10,10 +12,13 @@ pub mod ecdh_1pu; pub mod ecdh_es; +/// Trait for keys supporting Diffie-Helman key exchange pub trait KeyExchange { + /// Perform a key exchange, writing the result to the provided buffer. fn key_exchange_buffer(&self, other: &Rhs, out: &mut dyn WriteBuffer) -> Result<(), Error>; #[cfg(feature = "alloc")] + /// Perform a key exchange and return a new allocated buffer. fn key_exchange_bytes(&self, other: &Rhs) -> Result { let mut buf = SecretBytes::with_capacity(128); self.key_exchange_buffer(other, &mut buf)?; @@ -21,15 +26,21 @@ pub trait KeyExchange { } } +/// Trait for instantiation from a key exchange pub trait FromKeyExchange: Sized { + /// Derive an instance of this key directly from a supported key exchange fn from_key_exchange(lhs: &Lhs, rhs: &Rhs) -> Result; } +/// Trait implemented by key derivation methods pub trait KeyDerivation { + /// Derive the raw bytes of a key from this KDF fn derive_key_bytes(&mut self, key_output: &mut [u8]) -> Result<(), Error>; } +/// Trait for instantiation from a key derivation pub trait FromKeyDerivation { + /// Create a new instance of a key from a key derivation fn from_key_derivation(derive: D) -> Result where Self: Sized; diff --git a/askar-crypto/src/lib.rs b/askar-crypto/src/lib.rs index 2fae8225..fa58dff4 100644 --- a/askar-crypto/src/lib.rs +++ b/askar-crypto/src/lib.rs @@ -1,3 +1,5 @@ +//! Cryptography primitives and operations for aries-askar. + #![no_std] #![deny(missing_debug_implementations)] // #![deny(missing_docs)] diff --git a/askar-crypto/src/random.rs b/askar-crypto/src/random.rs index 0abeceda..9395e222 100644 --- a/askar-crypto/src/random.rs +++ b/askar-crypto/src/random.rs @@ -1,3 +1,5 @@ +//! Support for random number generation + use aead::generic_array::{typenum::Unsigned, GenericArray}; use chacha20::{ cipher::{NewStreamCipher, SyncStreamCipher}, @@ -9,10 +11,13 @@ use rand::{rngs::OsRng, RngCore}; use crate::buffer::SecretBytes; use crate::error::Error; -pub const SEED_LENGTH: usize = ::KeySize::USIZE; +/// The expected length of a seed for `fill_random_deterministic` +pub const DETERMINISTIC_SEED_LENGTH: usize = ::KeySize::USIZE; +/// The type of the standard random number generator pub type StdRng = OsRng; +/// Perform an operation with a reference to the random number generator #[inline(always)] pub fn with_rng(f: impl FnOnce(&mut StdRng) -> O) -> O { // may need to substitute another RNG depending on the platform @@ -29,7 +34,7 @@ pub fn fill_random(value: &mut [u8]) { /// Written to be compatible with randombytes_deterministic in libsodium, /// used to generate a deterministic symmetric encryption key pub fn fill_random_deterministic(seed: &[u8], output: &mut [u8]) -> Result<(), Error> { - if seed.len() != SEED_LENGTH { + if seed.len() != DETERMINISTIC_SEED_LENGTH { return Err(err_msg!(Usage, "Invalid length for seed")); } let mut cipher = ChaCha20::new( diff --git a/askar-crypto/src/repr.rs b/askar-crypto/src/repr.rs index 9caeccb5..c4975fa1 100644 --- a/askar-crypto/src/repr.rs +++ b/askar-crypto/src/repr.rs @@ -1,22 +1,28 @@ +//! Traits for exposing key data representations + #[cfg(feature = "alloc")] use crate::buffer::SecretBytes; use crate::{buffer::WriteBuffer, error::Error, generic_array::ArrayLength}; -/// Generate a new random key. +/// Key generation operations pub trait KeyGen { + /// Generate a new random key. fn generate() -> Result where Self: Sized; } -/// Convert between key instance and key secret bytes. +/// Convert between key instance and key secret bytes pub trait KeySecretBytes { + /// Create a new key instance from a slice of key secret bytes. fn from_secret_bytes(key: &[u8]) -> Result where Self: Sized; + /// Access a temporary slice of the key secret bytes, if any. fn with_secret_bytes(&self, f: impl FnOnce(Option<&[u8]>) -> O) -> O; + /// Write the key secret bytes to a buffer. fn to_secret_bytes_buffer(&self, out: &mut B) -> Result<(), Error> { self.with_secret_bytes(|buf| { if let Some(buf) = buf { @@ -28,6 +34,7 @@ pub trait KeySecretBytes { } #[cfg(feature = "alloc")] + /// Write the key secret bytes to a new allocated buffer. fn to_secret_bytes(&self) -> Result { let mut buf = SecretBytes::with_capacity(128); self.to_secret_bytes_buffer(&mut buf)?; @@ -37,17 +44,21 @@ pub trait KeySecretBytes { /// Convert between key instance and key public bytes. pub trait KeyPublicBytes { + /// Create a new key instance from a slice of public key bytes. fn from_public_bytes(key: &[u8]) -> Result where Self: Sized; + /// Access a temporary slice of the key public bytes. fn with_public_bytes(&self, f: impl FnOnce(&[u8]) -> O) -> O; + /// Write the key public bytes to a buffer. fn to_public_bytes_buffer(&self, out: &mut B) -> Result<(), Error> { self.with_public_bytes(|buf| out.buffer_write(buf)) } #[cfg(feature = "alloc")] + /// Write the key public bytes to a new allocated buffer. fn to_public_bytes(&self) -> Result { let mut buf = SecretBytes::with_capacity(128); self.to_public_bytes_buffer(&mut buf)?; @@ -55,14 +66,17 @@ pub trait KeyPublicBytes { } } -/// Convert between keypair instance and keypair (secret and public) bytes. +/// Convert between keypair instance and keypair (secret and public) bytes pub trait KeypairBytes { + /// Create a new key instance from a slice of keypair bytes. fn from_keypair_bytes(key: &[u8]) -> Result where Self: Sized; + /// Create a new key instance from a slice of keypair bytes. fn with_keypair_bytes(&self, f: impl FnOnce(Option<&[u8]>) -> O) -> O; + /// Write the keypair bytes to a buffer. fn to_keypair_bytes_buffer(&self, out: &mut B) -> Result<(), Error> { self.with_keypair_bytes(|buf| { if let Some(buf) = buf { @@ -74,6 +88,7 @@ pub trait KeypairBytes { } #[cfg(feature = "alloc")] + /// Write the keypair bytes to a new allocated buffer. fn to_keypair_bytes(&self) -> Result { let mut buf = SecretBytes::with_capacity(128); self.to_keypair_bytes_buffer(&mut buf)?; @@ -83,11 +98,14 @@ pub trait KeypairBytes { /// For concrete secret key types pub trait KeyMeta { + /// The size of the key secret bytes type KeySize: ArrayLength; } /// For concrete secret + public key types pub trait KeypairMeta: KeyMeta { + /// The size of the key public bytes type PublicKeySize: ArrayLength; + /// The size of the secret bytes and public bytes combined type KeypairSize: ArrayLength; } diff --git a/askar-crypto/src/sign.rs b/askar-crypto/src/sign.rs index 05951bbe..5854ce44 100644 --- a/askar-crypto/src/sign.rs +++ b/askar-crypto/src/sign.rs @@ -1,8 +1,13 @@ +//! Signature traits and parameters + #[cfg(feature = "alloc")] use crate::buffer::SecretBytes; -use crate::{alg::BlsGroups, buffer::WriteBuffer, error::Error}; +use crate::{buffer::WriteBuffer, error::Error}; +/// Signature creation operations pub trait KeySign: KeySigVerify { + /// Create a signature of the requested type and write it to the + /// provided buffer. fn write_signature( &self, message: &[u8], @@ -11,6 +16,8 @@ pub trait KeySign: KeySigVerify { ) -> Result<(), Error>; #[cfg(feature = "alloc")] + /// Create a signature of the requested type and return an allocated + /// buffer. fn create_signature( &self, message: &[u8], @@ -22,7 +29,10 @@ pub trait KeySign: KeySigVerify { } } +/// Signature verification operations pub trait KeySigVerify { + /// Check the validity of signature over a message with the + /// specified signature type. fn verify_signature( &self, message: &[u8], @@ -31,22 +41,24 @@ pub trait KeySigVerify { ) -> Result; } +/// Supported signature types #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum SignatureType { - Bls12_1381(BlsGroups), + // Bls12_1381(BlsGroups), /// Standard signature output for ed25519 EdDSA, - // Elliptic curve DSA using P-256 and SHA-256 + /// Elliptic curve DSA using P-256 and SHA-256 ES256, - // Elliptic curve DSA using K-256 and SHA-256 + /// Elliptic curve DSA using K-256 and SHA-256 ES256K, } impl SignatureType { - pub const fn signature_size(&self) -> usize { + /// Get the length of the signature output. + pub const fn signature_length(&self) -> usize { match self { - Self::Bls12_1381(BlsGroups::G1) => 48, - Self::Bls12_1381(BlsGroups::G2) => 96, + // Self::Bls12_1381(BlsGroups::G1) => 48, + // Self::Bls12_1381(BlsGroups::G2) => 96, Self::EdDSA => 64, Self::ES256 => 64, Self::ES256K => 64, From 0dea23a0dca5d1bdef8dafb91877619bb11dff08 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Tue, 27 Apr 2021 16:02:50 -0700 Subject: [PATCH 066/116] add quick bench of any key encryption Signed-off-by: Andrew Whitehead --- askar-crypto/benches/enc.rs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/askar-crypto/benches/enc.rs b/askar-crypto/benches/enc.rs index 8286eef2..d56838b2 100644 --- a/askar-crypto/benches/enc.rs +++ b/askar-crypto/benches/enc.rs @@ -5,9 +5,13 @@ extern crate criterion; extern crate hex_literal; use askar_crypto::{ - alg::chacha20::{Chacha20Key, C20P}, + alg::{ + chacha20::{Chacha20Key, C20P}, + AnyKey, AnyKeyCreate, Chacha20Types, KeyAlg, + }, buffer::{SecretBytes, WriteBuffer, Writer}, encrypt::{KeyAeadInPlace, KeyAeadMeta}, + random::fill_random, repr::KeySecretBytes, }; @@ -38,6 +42,23 @@ fn criterion_benchmark(c: &mut Criterion) { key.encrypt_in_place(&mut buffer, &nonce, &[]).unwrap(); }) }); + c.bench_function(&format!("chacha20-poly1305 encrypt as any"), move |b| { + b.iter(|| { + let key = Box::::from_secret_bytes( + KeyAlg::Chacha20(Chacha20Types::C20P), + &key[..], + ) + .unwrap(); + let mut buffer = [0u8; 255]; + buffer[0..message.len()].copy_from_slice(black_box(&message[..])); + let mut nonce = [0u8; 255]; + let nonce_len = key.aead_params().nonce_length; + fill_random(&mut nonce[..nonce_len]); + let mut writer = Writer::from_slice_position(&mut buffer, message.len()); + key.encrypt_in_place(&mut writer, &nonce[..nonce_len], &[]) + .unwrap(); + }) + }); } } From fff1ad4f631a67a13132aff5e655b964ea2e0564 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Tue, 27 Apr 2021 17:10:27 -0700 Subject: [PATCH 067/116] doc cleanups Signed-off-by: Andrew Whitehead --- askar-crypto/Cargo.toml | 1 + askar-crypto/src/alg/mod.rs | 1 + askar-crypto/src/buffer/mod.rs | 3 +++ askar-crypto/src/buffer/writer.rs | 1 + askar-crypto/src/encrypt/{nacl_box.rs => crypto_box.rs} | 0 askar-crypto/src/encrypt/mod.rs | 3 ++- askar-crypto/src/jwk/mod.rs | 3 +++ askar-crypto/src/kdf/mod.rs | 1 + askar-crypto/src/lib.rs | 3 --- askar-crypto/src/random.rs | 1 + askar-crypto/src/repr.rs | 3 +++ askar-crypto/src/sign.rs | 1 + src/kms/envelope.rs | 2 +- 13 files changed, 18 insertions(+), 5 deletions(-) rename askar-crypto/src/encrypt/{nacl_box.rs => crypto_box.rs} (100%) diff --git a/askar-crypto/Cargo.toml b/askar-crypto/Cargo.toml index 6dd2fc2d..335adc80 100644 --- a/askar-crypto/Cargo.toml +++ b/askar-crypto/Cargo.toml @@ -11,6 +11,7 @@ categories = ["cryptography", "database"] keywords = ["hyperledger", "aries", "ssi", "verifiable", "credentials"] [package.metadata.docs.rs] +features = ["std"] rustdoc-args = ["--cfg", "docsrs"] [features] diff --git a/askar-crypto/src/alg/mod.rs b/askar-crypto/src/alg/mod.rs index 3a8d50bb..26dd485e 100644 --- a/askar-crypto/src/alg/mod.rs +++ b/askar-crypto/src/alg/mod.rs @@ -12,6 +12,7 @@ use crate::error::Error; #[cfg(any(test, feature = "any_key"))] mod any; #[cfg(any(test, feature = "any_key"))] +#[cfg_attr(docsrs, doc(cfg(feature = "any_key")))] pub use any::{AnyKey, AnyKeyCreate}; // pub mod bls; diff --git a/askar-crypto/src/buffer/mod.rs b/askar-crypto/src/buffer/mod.rs index d5208322..7eccdf0c 100644 --- a/askar-crypto/src/buffer/mod.rs +++ b/askar-crypto/src/buffer/mod.rs @@ -15,6 +15,7 @@ pub use self::hash::HashBuffer; #[cfg(feature = "alloc")] mod secret; #[cfg(feature = "alloc")] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] pub use self::secret::SecretBytes; mod string; @@ -51,6 +52,7 @@ pub trait ResizeBuffer: WriteBuffer + AsRef<[u8]> + AsMut<[u8]> { } #[cfg(feature = "alloc")] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] impl WriteBuffer for Vec { fn buffer_write(&mut self, data: &[u8]) -> Result<(), Error> { self.extend_from_slice(data); @@ -59,6 +61,7 @@ impl WriteBuffer for Vec { } #[cfg(feature = "alloc")] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] impl ResizeBuffer for Vec { fn buffer_insert(&mut self, pos: usize, data: &[u8]) -> Result<(), Error> { self.splice(pos..pos, data.into_iter().cloned()); diff --git a/askar-crypto/src/buffer/writer.rs b/askar-crypto/src/buffer/writer.rs index bdbe8663..72fe00bc 100644 --- a/askar-crypto/src/buffer/writer.rs +++ b/askar-crypto/src/buffer/writer.rs @@ -111,6 +111,7 @@ impl ResizeBuffer for Writer<'_, [u8]> { } #[cfg(feature = "alloc")] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] impl<'w> Writer<'w, Vec> { #[inline] pub fn from_vec(vec: &'w mut Vec) -> Self { diff --git a/askar-crypto/src/encrypt/nacl_box.rs b/askar-crypto/src/encrypt/crypto_box.rs similarity index 100% rename from askar-crypto/src/encrypt/nacl_box.rs rename to askar-crypto/src/encrypt/crypto_box.rs diff --git a/askar-crypto/src/encrypt/mod.rs b/askar-crypto/src/encrypt/mod.rs index c620f730..1c28a587 100644 --- a/askar-crypto/src/encrypt/mod.rs +++ b/askar-crypto/src/encrypt/mod.rs @@ -8,7 +8,8 @@ use crate::{ }; #[cfg(feature = "alloc")] // FIXME - support non-alloc? -pub mod nacl_box; +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] +pub mod crypto_box; /// Trait for key types which perform AEAD encryption pub trait KeyAeadInPlace { diff --git a/askar-crypto/src/jwk/mod.rs b/askar-crypto/src/jwk/mod.rs index 65822913..fe3dbbe5 100644 --- a/askar-crypto/src/jwk/mod.rs +++ b/askar-crypto/src/jwk/mod.rs @@ -25,6 +25,7 @@ pub trait ToJwk { fn to_jwk_encoder(&self, enc: &mut JwkEncoder<'_>) -> Result<(), Error>; #[cfg(feature = "alloc")] + #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] fn to_jwk_thumbprint(&self) -> Result { let mut v = Vec::with_capacity(43); jwk_thumbprint_buffer(self, &mut v)?; @@ -32,6 +33,7 @@ pub trait ToJwk { } #[cfg(feature = "alloc")] + #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] fn to_jwk_public(&self) -> Result { let mut v = Vec::with_capacity(128); let mut buf = JwkEncoder::new(&mut v, JwkEncoderMode::PublicKey)?; @@ -41,6 +43,7 @@ pub trait ToJwk { } #[cfg(feature = "alloc")] + #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] fn to_jwk_secret(&self) -> Result { let mut v = SecretBytes::with_capacity(128); let mut buf = JwkEncoder::new(&mut v, JwkEncoderMode::SecretKey)?; diff --git a/askar-crypto/src/kdf/mod.rs b/askar-crypto/src/kdf/mod.rs index 321ed22f..dfb0ffd2 100644 --- a/askar-crypto/src/kdf/mod.rs +++ b/askar-crypto/src/kdf/mod.rs @@ -18,6 +18,7 @@ pub trait KeyExchange { fn key_exchange_buffer(&self, other: &Rhs, out: &mut dyn WriteBuffer) -> Result<(), Error>; #[cfg(feature = "alloc")] + #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] /// Perform a key exchange and return a new allocated buffer. fn key_exchange_bytes(&self, other: &Rhs) -> Result { let mut buf = SecretBytes::with_capacity(128); diff --git a/askar-crypto/src/lib.rs b/askar-crypto/src/lib.rs index fa58dff4..f535298e 100644 --- a/askar-crypto/src/lib.rs +++ b/askar-crypto/src/lib.rs @@ -27,7 +27,6 @@ pub mod alg; pub mod buffer; pub mod encrypt; -pub use self::encrypt::{KeyAeadInPlace, KeyAeadMeta}; pub mod jwk; @@ -36,7 +35,5 @@ pub mod kdf; pub mod random; pub mod sign; -pub use self::sign::{KeySigVerify, KeySign, SignatureType}; pub mod repr; -pub use self::repr::{KeyGen, KeyMeta, KeyPublicBytes, KeySecretBytes, KeypairBytes, KeypairMeta}; diff --git a/askar-crypto/src/random.rs b/askar-crypto/src/random.rs index 9395e222..d3cc9421 100644 --- a/askar-crypto/src/random.rs +++ b/askar-crypto/src/random.rs @@ -46,6 +46,7 @@ pub fn fill_random_deterministic(seed: &[u8], output: &mut [u8]) -> Result<(), E } #[cfg(feature = "alloc")] +#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] /// Create a new `SecretBytes` instance with random data. #[inline(always)] pub fn random_secret(len: usize) -> SecretBytes { diff --git a/askar-crypto/src/repr.rs b/askar-crypto/src/repr.rs index c4975fa1..35920534 100644 --- a/askar-crypto/src/repr.rs +++ b/askar-crypto/src/repr.rs @@ -34,6 +34,7 @@ pub trait KeySecretBytes { } #[cfg(feature = "alloc")] + #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] /// Write the key secret bytes to a new allocated buffer. fn to_secret_bytes(&self) -> Result { let mut buf = SecretBytes::with_capacity(128); @@ -58,6 +59,7 @@ pub trait KeyPublicBytes { } #[cfg(feature = "alloc")] + #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] /// Write the key public bytes to a new allocated buffer. fn to_public_bytes(&self) -> Result { let mut buf = SecretBytes::with_capacity(128); @@ -88,6 +90,7 @@ pub trait KeypairBytes { } #[cfg(feature = "alloc")] + #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] /// Write the keypair bytes to a new allocated buffer. fn to_keypair_bytes(&self) -> Result { let mut buf = SecretBytes::with_capacity(128); diff --git a/askar-crypto/src/sign.rs b/askar-crypto/src/sign.rs index 5854ce44..6f410c23 100644 --- a/askar-crypto/src/sign.rs +++ b/askar-crypto/src/sign.rs @@ -16,6 +16,7 @@ pub trait KeySign: KeySigVerify { ) -> Result<(), Error>; #[cfg(feature = "alloc")] + #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] /// Create a signature of the requested type and return an allocated /// buffer. fn create_signature( diff --git a/src/kms/envelope.rs b/src/kms/envelope.rs index c3e62b4c..213e3eb7 100644 --- a/src/kms/envelope.rs +++ b/src/kms/envelope.rs @@ -5,7 +5,7 @@ use crate::{ crypto::{ alg::{x25519::X25519KeyPair, KeyAlg}, buffer::SecretBytes, - encrypt::nacl_box::{ + encrypt::crypto_box::{ crypto_box as nacl_box, crypto_box_open as nacl_box_open, crypto_box_seal as nacl_box_seal, crypto_box_seal_open as nacl_box_seal_open, CBOX_NONCE_SIZE, CBOX_TAG_SIZE, From f93438024e817e367b6550e0960a6819f59433c4 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Tue, 27 Apr 2021 21:15:57 -0700 Subject: [PATCH 068/116] add docstrings; rename a couple trait methods Signed-off-by: Andrew Whitehead --- askar-crypto/src/alg/aesgcm.rs | 15 +++++++++-- askar-crypto/src/alg/any.rs | 12 ++++----- askar-crypto/src/alg/chacha20.rs | 16 ++++++++++-- askar-crypto/src/alg/ed25519.rs | 15 ++++++++++- askar-crypto/src/alg/k256.rs | 15 ++++++++--- askar-crypto/src/alg/p256.rs | 15 ++++++++--- askar-crypto/src/alg/x25519.rs | 10 ++++++-- askar-crypto/src/buffer/secret.rs | 10 ++++++++ askar-crypto/src/buffer/writer.rs | 7 ++++++ askar-crypto/src/encrypt/crypto_box.rs | 28 +++++++++++++-------- askar-crypto/src/encrypt/mod.rs | 6 +++++ askar-crypto/src/jwk/encode.rs | 14 +++++++++++ askar-crypto/src/jwk/mod.rs | 22 +++++++++++----- askar-crypto/src/jwk/ops.rs | 14 +++++++++++ askar-crypto/src/jwk/parts.rs | 19 ++++++++------ askar-crypto/src/kdf/ecdh_1pu.rs | 5 ++-- askar-crypto/src/kdf/ecdh_es.rs | 2 +- askar-crypto/src/kdf/mod.rs | 4 +-- askar-crypto/src/lib.rs | 2 +- askar-crypto/test-no-std/.cargo/config.toml | 8 ++++++ askar-crypto/test-no-std/Cargo.toml | 15 +++++++++++ askar-crypto/test-no-std/src/main.rs | 14 +++++++++++ src/kms/envelope.rs | 8 +++--- src/kms/key.rs | 4 +-- 24 files changed, 224 insertions(+), 56 deletions(-) create mode 100644 askar-crypto/test-no-std/.cargo/config.toml create mode 100644 askar-crypto/test-no-std/Cargo.toml create mode 100644 askar-crypto/test-no-std/src/main.rs diff --git a/askar-crypto/src/alg/aesgcm.rs b/askar-crypto/src/alg/aesgcm.rs index 7bdc224b..51fcda16 100644 --- a/askar-crypto/src/alg/aesgcm.rs +++ b/askar-crypto/src/alg/aesgcm.rs @@ -18,15 +18,21 @@ use crate::{ repr::{KeyGen, KeyMeta, KeySecretBytes}, }; +/// The 'kty' value of a symmetric key JWK pub static JWK_KEY_TYPE: &'static str = "oct"; +/// A common trait among supported AES-GCM sizes pub trait AesGcmType: 'static { + /// The AEAD implementation type Aead: NewAead + Aead + AeadInPlace; + /// The associated algorithm type const ALG_TYPE: AesTypes; + /// The associated JWK algorithm name const JWK_ALG: &'static str; } +/// 128 bit AES-GCM #[derive(Debug)] pub struct A128GCM; @@ -37,6 +43,7 @@ impl AesGcmType for A128GCM { const JWK_ALG: &'static str = "A128GCM"; } +/// 256 bit AES-GCM #[derive(Debug)] pub struct A256GCM; @@ -53,6 +60,7 @@ type NonceSize = <::Aead as Aead>::NonceSize; type TagSize = <::Aead as Aead>::TagSize; +/// An AES-GCM symmetric encryption key #[derive(Serialize, Deserialize, Zeroize)] #[serde( transparent, @@ -65,8 +73,11 @@ type TagSize = <::Aead as Aead>::TagSize; pub struct AesGcmKey(KeyType); impl AesGcmKey { + /// The length of the secret key in bytes pub const KEY_LENGTH: usize = KeyType::::SIZE; + /// The length of the AEAD encryption nonce pub const NONCE_LENGTH: usize = NonceSize::::USIZE; + /// The length of the AEAD encryption tag pub const TAG_LENGTH: usize = TagSize::::USIZE; #[inline] @@ -198,7 +209,7 @@ impl KeyAeadInPlace for AesGcmKey { } impl ToJwk for AesGcmKey { - fn to_jwk_encoder(&self, enc: &mut JwkEncoder<'_>) -> Result<(), Error> { + fn encode_jwk(&self, enc: &mut JwkEncoder<'_>) -> Result<(), Error> { if enc.is_public() { return Err(err_msg!(Unsupported, "Cannot export as a public key")); } @@ -223,7 +234,7 @@ where // while it may be acceptable to just use the prefix if the output is longer? let mut key = Self::uninit(); let mut buf = Writer::from_slice(key.0.as_mut()); - lhs.key_exchange_buffer(rhs, &mut buf)?; + lhs.write_key_exchange(rhs, &mut buf)?; if buf.position() != Self::KEY_LENGTH { return Err(err_msg!(Usage, "Invalid length for key exchange output")); } diff --git a/askar-crypto/src/alg/any.rs b/askar-crypto/src/alg/any.rs index 5bd6c0e8..2920b3c7 100644 --- a/askar-crypto/src/alg/any.rs +++ b/askar-crypto/src/alg/any.rs @@ -304,18 +304,18 @@ fn convert_key_any(key: &AnyKey, alg: KeyAlg) -> Result { } impl KeyExchange for AnyKey { - fn key_exchange_buffer(&self, other: &AnyKey, out: &mut dyn WriteBuffer) -> Result<(), Error> { + fn write_key_exchange(&self, other: &AnyKey, out: &mut dyn WriteBuffer) -> Result<(), Error> { match self.key_type_id() { s if s != other.key_type_id() => Err(err_msg!(Unsupported, "Unsupported key exchange")), s if s == TypeId::of::() => Ok(self .assume::() - .key_exchange_buffer(other.assume::(), out)?), + .write_key_exchange(other.assume::(), out)?), s if s == TypeId::of::() => Ok(self .assume::() - .key_exchange_buffer(other.assume::(), out)?), + .write_key_exchange(other.assume::(), out)?), s if s == TypeId::of::() => Ok(self .assume::() - .key_exchange_buffer(other.assume::(), out)?), + .write_key_exchange(other.assume::(), out)?), #[allow(unreachable_patterns)] _ => return Err(err_msg!(Unsupported, "Unsupported key exchange")), } @@ -461,7 +461,7 @@ impl KeyAeadInPlace for AnyKey { } impl ToJwk for AnyKey { - fn to_jwk_encoder(&self, enc: &mut JwkEncoder<'_>) -> Result<(), Error> { + fn encode_jwk(&self, enc: &mut JwkEncoder<'_>) -> Result<(), Error> { let key: &dyn ToJwk = match_key_types! { self, AesGcmKey, @@ -474,7 +474,7 @@ impl ToJwk for AnyKey { P256KeyPair; "JWK export is not supported for this key type" }; - key.to_jwk_encoder(enc) + key.encode_jwk(enc) } } diff --git a/askar-crypto/src/alg/chacha20.rs b/askar-crypto/src/alg/chacha20.rs index 6c696c66..7a0ec0ab 100644 --- a/askar-crypto/src/alg/chacha20.rs +++ b/askar-crypto/src/alg/chacha20.rs @@ -19,15 +19,21 @@ use crate::{ repr::{KeyGen, KeyMeta, KeySecretBytes}, }; +/// The 'kty' value of a symmetric key JWK pub static JWK_KEY_TYPE: &'static str = "oct"; +/// A common trait among supported ChaCha20 key types pub trait Chacha20Type: 'static { + /// The AEAD implementation type Aead: NewAead + Aead + AeadInPlace; + /// The associated algorithm type const ALG_TYPE: Chacha20Types; + /// The associated JWK algorithm name const JWK_ALG: &'static str; } +/// ChaCha20-Poly1305 #[derive(Debug)] pub struct C20P; @@ -38,6 +44,7 @@ impl Chacha20Type for C20P { const JWK_ALG: &'static str = "C20P"; } +/// XChaCha20-Poly1305 #[derive(Debug)] pub struct XC20P; @@ -54,6 +61,7 @@ type NonceSize = <::Aead as Aead>::NonceSize; type TagSize = <::Aead as Aead>::TagSize; +/// A ChaCha20 symmetric encryption key #[derive(Serialize, Deserialize, Zeroize)] #[serde( transparent, @@ -66,8 +74,11 @@ type TagSize = <::Aead as Aead>::TagSize; pub struct Chacha20Key(KeyType); impl Chacha20Key { + /// The length of the secret key in bytes pub const KEY_LENGTH: usize = KeyType::::SIZE; + /// The length of the AEAD encryption nonce pub const NONCE_LENGTH: usize = NonceSize::::USIZE; + /// The length of the AEAD encryption tag pub const TAG_LENGTH: usize = TagSize::::USIZE; #[inline] @@ -75,6 +86,7 @@ impl Chacha20Key { Self(KeyType::::default()) } + /// Construct a new ChaCha20 key from a seed value // this is consistent with Indy's wallet wrapping key generation // FIXME - move to aries_askar, use from_key_secret_bytes pub fn from_seed(seed: &[u8]) -> Result { @@ -207,7 +219,7 @@ impl KeyAeadInPlace for Chacha20Key { } impl ToJwk for Chacha20Key { - fn to_jwk_encoder(&self, enc: &mut JwkEncoder<'_>) -> Result<(), Error> { + fn encode_jwk(&self, enc: &mut JwkEncoder<'_>) -> Result<(), Error> { if enc.is_public() { return Err(err_msg!(Unsupported, "Cannot export as a public key")); } @@ -232,7 +244,7 @@ where // while it may be acceptable to just use the prefix if the output is longer? let mut key = Self::uninit(); let mut buf = Writer::from_slice(key.0.as_mut()); - lhs.key_exchange_buffer(rhs, &mut buf)?; + lhs.write_key_exchange(rhs, &mut buf)?; if buf.position() != Self::KEY_LENGTH { return Err(err_msg!(Usage, "Invalid length for key exchange output")); } diff --git a/askar-crypto/src/alg/ed25519.rs b/askar-crypto/src/alg/ed25519.rs index 5d271772..19f5211d 100644 --- a/askar-crypto/src/alg/ed25519.rs +++ b/askar-crypto/src/alg/ed25519.rs @@ -24,15 +24,22 @@ use crate::{ // FIXME - check for low-order points when loading public keys? // https://github.com/tendermint/tmkms/pull/279 +/// The length of an EdDSA signature pub const EDDSA_SIGNATURE_LENGTH: usize = 64; +/// The length of a public key in bytes pub const PUBLIC_KEY_LENGTH: usize = 32; +/// The length of a secret key in bytes pub const SECRET_KEY_LENGTH: usize = 32; +/// The length of a keypair in bytes pub const KEYPAIR_LENGTH: usize = SECRET_KEY_LENGTH + PUBLIC_KEY_LENGTH; +/// The 'kty' value of an Ed25519 JWK pub static JWK_KEY_TYPE: &'static str = "OKP"; +/// The 'crv' value of an Ed25519 JWK pub static JWK_CURVE: &'static str = "Ed25519"; +/// An Ed25519 public key or keypair pub struct Ed25519KeyPair { // SECURITY: SecretKey zeroizes on drop secret: Option, @@ -49,12 +56,14 @@ impl Ed25519KeyPair { } } + /// Create a signing key from the secret key pub fn to_signing_key(&self) -> Option> { self.secret .as_ref() .map(|sk| Ed25519SigningKey(ExpandedSecretKey::from(sk), &self.public)) } + /// Convert this keypair to an X25519 keypair pub fn to_x25519_keypair(&self) -> X25519KeyPair { if let Some(secret) = self.secret.as_ref() { let hash = sha2::Sha512::digest(secret.as_bytes()); @@ -74,10 +83,12 @@ impl Ed25519KeyPair { } } + /// Sign a message with the secret key pub fn sign(&self, message: &[u8]) -> Option<[u8; EDDSA_SIGNATURE_LENGTH]> { self.to_signing_key().map(|sk| sk.sign(message)) } + /// Verify a signature against the public key pub fn verify_signature(&self, message: &[u8], signature: &[u8]) -> bool { if let Ok(sig) = Signature::try_from(signature) { self.public.verify_strict(message, &sig).is_ok() @@ -239,7 +250,7 @@ impl KeySigVerify for Ed25519KeyPair { } impl ToJwk for Ed25519KeyPair { - fn to_jwk_encoder(&self, enc: &mut JwkEncoder<'_>) -> Result<(), Error> { + fn encode_jwk(&self, enc: &mut JwkEncoder<'_>) -> Result<(), Error> { enc.add_str("crv", JWK_CURVE)?; enc.add_str("kty", JWK_KEY_TYPE)?; self.with_public_bytes(|buf| enc.add_as_base64("x", buf))?; @@ -280,10 +291,12 @@ impl FromJwk for Ed25519KeyPair { } } +/// An Ed25519 expanded secret key used for signing // SECURITY: ExpandedSecretKey zeroizes on drop pub struct Ed25519SigningKey<'p>(ExpandedSecretKey, &'p PublicKey); impl Ed25519SigningKey<'_> { + /// Sign a message with the secret key pub fn sign(&self, message: &[u8]) -> [u8; EDDSA_SIGNATURE_LENGTH] { self.0.sign(message, &self.1).to_bytes() } diff --git a/askar-crypto/src/alg/k256.rs b/askar-crypto/src/alg/k256.rs index e7bee20e..3f11cfe6 100644 --- a/askar-crypto/src/alg/k256.rs +++ b/askar-crypto/src/alg/k256.rs @@ -23,17 +23,24 @@ use crate::{ sign::{KeySigVerify, KeySign, SignatureType}, }; +/// The length of an ES256K signature pub const ES256K_SIGNATURE_LENGTH: usize = 64; -pub const PUBLIC_KEY_LENGTH: usize = 33; // compressed size +/// The length of a compressed public key in bytes +pub const PUBLIC_KEY_LENGTH: usize = 33; +/// The length of a secret key pub const SECRET_KEY_LENGTH: usize = 32; +/// The length of a keypair in bytes pub const KEYPAIR_LENGTH: usize = SECRET_KEY_LENGTH + PUBLIC_KEY_LENGTH; +/// The 'kty' value of an elliptic curve key JWK pub static JWK_KEY_TYPE: &'static str = "EC"; +/// The 'crv' value of a K-256 key JWK pub static JWK_CURVE: &'static str = "secp256k1"; type FieldSize = ::FieldSize; +/// A K-256 (secp256k1) public key or keypair #[derive(Clone, Debug)] pub struct K256KeyPair { // SECURITY: SecretKey zeroizes on drop @@ -55,6 +62,7 @@ impl K256KeyPair { self.secret.as_ref().map(SigningKey::from) } + /// Sign a message with the secret key pub fn sign(&self, message: &[u8]) -> Option<[u8; ES256K_SIGNATURE_LENGTH]> { if let Some(skey) = self.to_signing_key() { let sig: Signature = skey.sign(message); @@ -65,6 +73,7 @@ impl K256KeyPair { } } + /// Verify a signature with the public key pub fn verify_signature(&self, message: &[u8], signature: &[u8]) -> bool { if let Ok(sig) = Signature::try_from(signature) { let vk = VerifyingKey::from(self.public.as_affine()); @@ -200,7 +209,7 @@ impl KeySigVerify for K256KeyPair { } impl ToJwk for K256KeyPair { - fn to_jwk_encoder(&self, enc: &mut JwkEncoder<'_>) -> Result<(), Error> { + fn encode_jwk(&self, enc: &mut JwkEncoder<'_>) -> Result<(), Error> { let pk_enc = EncodedPoint::encode(self.public, false); let (x, y) = match pk_enc.coordinates() { Coordinates::Identity => { @@ -261,7 +270,7 @@ impl FromJwk for K256KeyPair { } impl KeyExchange for K256KeyPair { - fn key_exchange_buffer(&self, other: &Self, out: &mut dyn WriteBuffer) -> Result<(), Error> { + fn write_key_exchange(&self, other: &Self, out: &mut dyn WriteBuffer) -> Result<(), Error> { match self.secret.as_ref() { Some(sk) => { let xk = diffie_hellman(sk.secret_scalar(), other.public.as_affine()); diff --git a/askar-crypto/src/alg/p256.rs b/askar-crypto/src/alg/p256.rs index cbe3083a..031e8c6c 100644 --- a/askar-crypto/src/alg/p256.rs +++ b/askar-crypto/src/alg/p256.rs @@ -23,17 +23,24 @@ use crate::{ sign::{KeySigVerify, KeySign, SignatureType}, }; +/// The length of an ES256 signature pub const ES256_SIGNATURE_LENGTH: usize = 64; -pub const PUBLIC_KEY_LENGTH: usize = 33; // compressed size +/// The length of a compressed public key in bytes +pub const PUBLIC_KEY_LENGTH: usize = 33; +/// The length of a secret key pub const SECRET_KEY_LENGTH: usize = 32; +/// The length of a keypair in bytes pub const KEYPAIR_LENGTH: usize = SECRET_KEY_LENGTH + PUBLIC_KEY_LENGTH; +/// The 'kty' value of an elliptic curve key JWK pub static JWK_KEY_TYPE: &'static str = "EC"; +/// The 'crv' value of a P-256 key JWK pub static JWK_CURVE: &'static str = "P-256"; type FieldSize = ::FieldSize; +/// A P-256 (secp256r1) public key or keypair #[derive(Clone, Debug)] pub struct P256KeyPair { // SECURITY: SecretKey zeroizes on drop @@ -55,6 +62,7 @@ impl P256KeyPair { self.secret.clone().map(SigningKey::from) } + /// Sign a message with the secret key pub fn sign(&self, message: &[u8]) -> Option<[u8; ES256_SIGNATURE_LENGTH]> { if let Some(skey) = self.to_signing_key() { let sig: Signature = skey.sign(message); @@ -65,6 +73,7 @@ impl P256KeyPair { } } + /// Verify a signature with the public key pub fn verify_signature(&self, message: &[u8], signature: &[u8]) -> bool { if let Ok(sig) = Signature::try_from(signature) { let vk = VerifyingKey::from(&self.public); @@ -200,7 +209,7 @@ impl KeySigVerify for P256KeyPair { } impl ToJwk for P256KeyPair { - fn to_jwk_encoder(&self, enc: &mut JwkEncoder<'_>) -> Result<(), Error> { + fn encode_jwk(&self, enc: &mut JwkEncoder<'_>) -> Result<(), Error> { let pk_enc = EncodedPoint::encode(self.public, false); let (x, y) = match pk_enc.coordinates() { Coordinates::Identity => { @@ -261,7 +270,7 @@ impl FromJwk for P256KeyPair { } impl KeyExchange for P256KeyPair { - fn key_exchange_buffer(&self, other: &Self, out: &mut dyn WriteBuffer) -> Result<(), Error> { + fn write_key_exchange(&self, other: &Self, out: &mut dyn WriteBuffer) -> Result<(), Error> { match self.secret.as_ref() { Some(sk) => { let xk = diffie_hellman(sk.secret_scalar(), other.public.as_affine()); diff --git a/askar-crypto/src/alg/x25519.rs b/askar-crypto/src/alg/x25519.rs index e4feb6d2..5ab52f17 100644 --- a/askar-crypto/src/alg/x25519.rs +++ b/askar-crypto/src/alg/x25519.rs @@ -19,13 +19,19 @@ use crate::{ repr::{KeyGen, KeyMeta, KeyPublicBytes, KeySecretBytes, KeypairBytes, KeypairMeta}, }; +/// The length of a public key in bytes pub const PUBLIC_KEY_LENGTH: usize = 32; +/// The length of a secret key in bytes pub const SECRET_KEY_LENGTH: usize = 32; +/// The length of a keypair in bytes pub const KEYPAIR_LENGTH: usize = SECRET_KEY_LENGTH + PUBLIC_KEY_LENGTH; +/// The 'kty' value of an X25519 JWK pub static JWK_KEY_TYPE: &'static str = "OKP"; +/// The 'crv' value of an X25519 JWK pub static JWK_CURVE: &'static str = "X25519"; +/// An X25519 public key or keypair #[derive(Clone)] pub struct X25519KeyPair { // SECURITY: SecretKey (StaticSecret) zeroizes on drop @@ -177,7 +183,7 @@ impl KeyPublicBytes for X25519KeyPair { } impl ToJwk for X25519KeyPair { - fn to_jwk_encoder(&self, enc: &mut JwkEncoder<'_>) -> Result<(), Error> { + fn encode_jwk(&self, enc: &mut JwkEncoder<'_>) -> Result<(), Error> { enc.add_str("crv", JWK_CURVE)?; enc.add_str("kty", JWK_KEY_TYPE)?; self.with_public_bytes(|buf| enc.add_as_base64("x", buf))?; @@ -223,7 +229,7 @@ impl FromJwk for X25519KeyPair { } impl KeyExchange for X25519KeyPair { - fn key_exchange_buffer(&self, other: &Self, out: &mut dyn WriteBuffer) -> Result<(), Error> { + fn write_key_exchange(&self, other: &Self, out: &mut dyn WriteBuffer) -> Result<(), Error> { match self.secret.as_ref() { Some(sk) => { let xk = sk.diffie_hellman(&other.public); diff --git a/askar-crypto/src/buffer/secret.rs b/askar-crypto/src/buffer/secret.rs index 772f221f..50832750 100644 --- a/askar-crypto/src/buffer/secret.rs +++ b/askar-crypto/src/buffer/secret.rs @@ -16,6 +16,7 @@ use crate::error::Error; pub struct SecretBytes(Vec); impl SecretBytes { + /// Create a new buffer using an initializer for the data pub fn new_with(len: usize, f: impl FnOnce(&mut [u8])) -> Self { let mut slf = Self::with_capacity(len); slf.0.resize(len, 0u8); @@ -23,11 +24,13 @@ impl SecretBytes { slf } + /// Create a new, empty buffer with an initial capacity #[inline] pub fn with_capacity(max_len: usize) -> Self { Self(Vec::with_capacity(max_len)) } + /// Create a new buffer from a slice #[inline] pub fn from_slice(data: &[u8]) -> Self { let mut v = Vec::with_capacity(data.len()); @@ -35,6 +38,7 @@ impl SecretBytes { Self(v) } + /// Create a new buffer from a slice, with extra space reserved #[inline] pub fn from_slice_reserve(data: &[u8], reserve: usize) -> Self { let mut v = Vec::with_capacity(data.len() + reserve); @@ -42,6 +46,7 @@ impl SecretBytes { Self(v) } + /// Accessor for the length of the buffer contents #[inline] pub fn len(&self) -> usize { self.0.len() @@ -52,6 +57,7 @@ impl SecretBytes { core::str::from_utf8(self.0.as_slice()).ok() } + /// Ensure that data can be appended to the buffer without resizing pub fn ensure_capacity(&mut self, min_cap: usize) { let cap = self.0.capacity(); if cap == 0 { @@ -66,17 +72,20 @@ impl SecretBytes { } } + /// Extend the buffer from a byte slice #[inline] pub fn extend_from_slice(&mut self, data: &[u8]) { self.reserve(data.len()); self.0.extend_from_slice(data); } + /// Reserve extra space in the buffer #[inline] pub fn reserve(&mut self, extra: usize) { self.ensure_capacity(self.len() + extra) } + /// Convert this buffer into a boxed slice pub fn into_boxed_slice(mut self) -> Box<[u8]> { let len = self.0.len(); if self.0.capacity() > len { @@ -91,6 +100,7 @@ impl SecretBytes { } } + /// Unwrap this buffer into a Vec #[inline] pub fn into_vec(mut self) -> Vec { // FIXME zeroize extra capacity in case it was used previously? diff --git a/askar-crypto/src/buffer/writer.rs b/askar-crypto/src/buffer/writer.rs index 72fe00bc..d769e397 100644 --- a/askar-crypto/src/buffer/writer.rs +++ b/askar-crypto/src/buffer/writer.rs @@ -5,6 +5,7 @@ use core::{fmt, ops::Range}; use super::{ResizeBuffer, WriteBuffer}; use crate::error::Error; +/// A structure wrapping a mutable pointer to a buffer #[derive(Debug)] pub struct Writer<'w, B: ?Sized> { inner: &'w mut B, @@ -12,12 +13,14 @@ pub struct Writer<'w, B: ?Sized> { } impl Writer<'_, B> { + /// Accessor for the writer position pub fn position(&self) -> usize { self.pos } } impl<'w> Writer<'w, [u8]> { + /// Create a new writer from a mutable byte slice #[inline] pub fn from_slice(slice: &'w mut [u8]) -> Self { Writer { @@ -26,6 +29,7 @@ impl<'w> Writer<'w, [u8]> { } } + /// Create a new writer from a mutable byte slice, skipping a prefix #[inline] pub fn from_slice_position(slice: &'w mut [u8], pos: usize) -> Self { Writer { inner: slice, pos } @@ -113,11 +117,13 @@ impl ResizeBuffer for Writer<'_, [u8]> { #[cfg(feature = "alloc")] #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] impl<'w> Writer<'w, Vec> { + /// Create a new writer from a mutable Vec pointer #[inline] pub fn from_vec(vec: &'w mut Vec) -> Self { Writer { inner: vec, pos: 0 } } + /// Create a new writer from a mutable Vec pointer, skipping a prefix #[inline] pub fn from_vec_skip(vec: &'w mut Vec, pos: usize) -> Self { Writer { inner: vec, pos } @@ -157,6 +163,7 @@ impl ResizeBuffer for Writer<'_, B> { } impl<'b, B: ?Sized> Writer<'b, B> { + /// Create a new writer from a pointer to a buffer implementation pub fn from_buffer(buf: &'b mut B) -> Writer<'b, B> { Writer { inner: buf, pos: 0 } } diff --git a/askar-crypto/src/encrypt/crypto_box.rs b/askar-crypto/src/encrypt/crypto_box.rs index 0faf6af8..00d8fe61 100644 --- a/askar-crypto/src/encrypt/crypto_box.rs +++ b/askar-crypto/src/encrypt/crypto_box.rs @@ -15,9 +15,12 @@ use crate::{ repr::{KeyGen, KeyPublicBytes}, }; -pub const CBOX_NONCE_SIZE: usize = NonceSize::::USIZE; -pub const CBOX_KEY_SIZE: usize = crate::alg::x25519::PUBLIC_KEY_LENGTH; -pub const CBOX_TAG_SIZE: usize = TagSize::::USIZE; +/// The size of the salsa box nonce +pub const CBOX_NONCE_LENGTH: usize = NonceSize::::USIZE; +/// The size of the salsa box key size +pub const CBOX_KEY_LENGTH: usize = crate::alg::x25519::PUBLIC_KEY_LENGTH; +/// The size of the salsa box tag size +pub const CBOX_TAG_LENGTH: usize = TagSize::::USIZE; type NonceSize = ::NonceSize; @@ -41,6 +44,7 @@ fn nonce_from(nonce: &[u8]) -> Result<&GenericArray>, Er } } +/// Encrypt a message into a crypto box with a given nonce pub fn crypto_box( recip_pk: &X25519KeyPair, sender_sk: &X25519KeyPair, @@ -57,6 +61,7 @@ pub fn crypto_box( Ok(()) } +/// Unencrypt a crypto box pub fn crypto_box_open( recip_sk: &X25519KeyPair, sender_pk: &X25519KeyPair, @@ -82,38 +87,41 @@ pub fn crypto_box_open( Ok(()) } +/// Construct a deterministic nonce for an ephemeral and recipient key pub fn crypto_box_seal_nonce( ephemeral_pk: &[u8], recip_pk: &[u8], -) -> Result<[u8; CBOX_NONCE_SIZE], Error> { - let mut key_hash = VarBlake2b::new(CBOX_NONCE_SIZE).unwrap(); +) -> Result<[u8; CBOX_NONCE_LENGTH], Error> { + let mut key_hash = VarBlake2b::new(CBOX_NONCE_LENGTH).unwrap(); key_hash.update(ephemeral_pk); key_hash.update(recip_pk); - let mut nonce = [0u8; CBOX_NONCE_SIZE]; + let mut nonce = [0u8; CBOX_NONCE_LENGTH]; key_hash.finalize_variable(|hash| nonce.copy_from_slice(hash)); Ok(nonce) } +/// Encrypt a message for a recipient using an ephemeral key and deterministic nonce // Could add a non-alloc version, if needed pub fn crypto_box_seal(recip_pk: &X25519KeyPair, message: &[u8]) -> Result { let ephem_kp = X25519KeyPair::generate()?; let ephem_pk_bytes = ephem_kp.public.as_bytes(); - let buf_len = CBOX_KEY_SIZE + message.len() + TagSize::::USIZE; + let buf_len = CBOX_KEY_LENGTH + message.len() + TagSize::::USIZE; let mut buffer = SecretBytes::with_capacity(buf_len); buffer.buffer_write(ephem_pk_bytes)?; buffer.buffer_write(message)?; - let mut writer = Writer::from_vec_skip(buffer.as_vec_mut(), CBOX_KEY_SIZE); + let mut writer = Writer::from_vec_skip(buffer.as_vec_mut(), CBOX_KEY_LENGTH); let nonce = crypto_box_seal_nonce(ephem_pk_bytes, recip_pk.public.as_bytes())?.to_vec(); crypto_box(recip_pk, &ephem_kp, &mut writer, &nonce[..])?; Ok(buffer) } +/// Unseal a sealed crypto box pub fn crypto_box_seal_open( recip_sk: &X25519KeyPair, ciphertext: &[u8], ) -> Result { - let ephem_pk = X25519KeyPair::from_public_bytes(&ciphertext[..CBOX_KEY_SIZE])?; - let mut buffer = SecretBytes::from_slice(&ciphertext[CBOX_KEY_SIZE..]); + let ephem_pk = X25519KeyPair::from_public_bytes(&ciphertext[..CBOX_KEY_LENGTH])?; + let mut buffer = SecretBytes::from_slice(&ciphertext[CBOX_KEY_LENGTH..]); let nonce = crypto_box_seal_nonce(ephem_pk.public.as_bytes(), recip_sk.public.as_bytes())?; crypto_box_open(recip_sk, &ephem_pk, &mut buffer, &nonce)?; Ok(buffer) diff --git a/askar-crypto/src/encrypt/mod.rs b/askar-crypto/src/encrypt/mod.rs index 1c28a587..2f046f8b 100644 --- a/askar-crypto/src/encrypt/mod.rs +++ b/askar-crypto/src/encrypt/mod.rs @@ -35,9 +35,12 @@ pub trait KeyAeadInPlace { /// For concrete key types with fixed nonce and tag sizes pub trait KeyAeadMeta { + /// The size of the AEAD nonce type NonceSize: ArrayLength; + /// The size of the AEAD tag type TagSize: ArrayLength; + /// Generate a new random nonce fn random_nonce() -> GenericArray { let mut nonce = GenericArray::default(); fill_random(nonce.as_mut_slice()); @@ -45,8 +48,11 @@ pub trait KeyAeadMeta { } } +/// A structure combining the AEAD parameters #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct KeyAeadParams { + /// The length of the nonce pub nonce_length: usize, + /// The length of the tag pub tag_length: usize, } diff --git a/askar-crypto/src/jwk/encode.rs b/askar-crypto/src/jwk/encode.rs index 75cced3a..c6ee6d9b 100644 --- a/askar-crypto/src/jwk/encode.rs +++ b/askar-crypto/src/jwk/encode.rs @@ -7,13 +7,18 @@ use crate::{ use super::ops::KeyOpsSet; +/// Supported modes for JWK encoding #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum JwkEncoderMode { + /// Encoding a public key PublicKey, + /// Encoding a secret key SecretKey, + /// Encoding a public key thumbprint Thumbprint, } +/// A helper structure which writes a JWK to a buffer #[derive(Debug)] pub struct JwkEncoder<'b> { buffer: &'b mut dyn WriteBuffer, @@ -22,6 +27,7 @@ pub struct JwkEncoder<'b> { } impl<'b> JwkEncoder<'b> { + /// Create a new instance pub fn new(buffer: &'b mut B, mode: JwkEncoderMode) -> Result { Ok(Self { buffer, @@ -45,6 +51,7 @@ impl JwkEncoder<'_> { Ok(()) } + /// Add a string attribute pub fn add_str(&mut self, key: &str, value: &str) -> Result<(), Error> { self.start_attr(key)?; let buffer = &mut *self.buffer; @@ -54,6 +61,7 @@ impl JwkEncoder<'_> { Ok(()) } + /// Add a binary attribute to be encoded as unpadded base64-URL pub fn add_as_base64(&mut self, key: &str, value: &[u8]) -> Result<(), Error> { self.start_attr(key)?; let buffer = &mut *self.buffer; @@ -68,6 +76,7 @@ impl JwkEncoder<'_> { Ok(()) } + /// Add key operations to the JWK pub fn add_key_ops(&mut self, ops: impl Into) -> Result<(), Error> { self.start_attr("key_ops")?; let buffer = &mut *self.buffer; @@ -84,22 +93,27 @@ impl JwkEncoder<'_> { Ok(()) } + /// Accessor for the encoder mode pub fn mode(&self) -> JwkEncoderMode { self.mode } + /// Check if the mode is public pub fn is_public(&self) -> bool { matches!(self.mode, JwkEncoderMode::PublicKey) } + /// Check if the mode is secret pub fn is_secret(&self) -> bool { matches!(self.mode, JwkEncoderMode::SecretKey) } + /// Check if the mode is thumbprint pub fn is_thumbprint(&self) -> bool { matches!(self.mode, JwkEncoderMode::Thumbprint) } + /// Complete the JWK pub fn finalize(self) -> Result<(), Error> { if !self.empty { self.buffer.buffer_write(b"}")?; diff --git a/askar-crypto/src/jwk/mod.rs b/askar-crypto/src/jwk/mod.rs index fe3dbbe5..0c18ac62 100644 --- a/askar-crypto/src/jwk/mod.rs +++ b/askar-crypto/src/jwk/mod.rs @@ -21,59 +21,69 @@ pub use self::ops::{KeyOps, KeyOpsSet}; mod parts; pub use self::parts::JwkParts; +/// Support for converting a key into a JWK pub trait ToJwk { - fn to_jwk_encoder(&self, enc: &mut JwkEncoder<'_>) -> Result<(), Error>; + /// Write the JWK representation to an encoder + fn encode_jwk(&self, enc: &mut JwkEncoder<'_>) -> Result<(), Error>; + /// Create the JWK thumbprint of the key #[cfg(feature = "alloc")] #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] fn to_jwk_thumbprint(&self) -> Result { let mut v = Vec::with_capacity(43); - jwk_thumbprint_buffer(self, &mut v)?; + write_jwk_thumbprint(self, &mut v)?; Ok(String::from_utf8(v).unwrap()) } + /// Create a JWK of the public key #[cfg(feature = "alloc")] #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] fn to_jwk_public(&self) -> Result { let mut v = Vec::with_capacity(128); let mut buf = JwkEncoder::new(&mut v, JwkEncoderMode::PublicKey)?; - self.to_jwk_encoder(&mut buf)?; + self.encode_jwk(&mut buf)?; buf.finalize()?; Ok(String::from_utf8(v).unwrap()) } + /// Create a JWK of the secret key #[cfg(feature = "alloc")] #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] fn to_jwk_secret(&self) -> Result { let mut v = SecretBytes::with_capacity(128); let mut buf = JwkEncoder::new(&mut v, JwkEncoderMode::SecretKey)?; - self.to_jwk_encoder(&mut buf)?; + self.encode_jwk(&mut buf)?; buf.finalize()?; Ok(v) } } -pub fn jwk_thumbprint_buffer( +/// Encode a key's JWK into a buffer +pub fn write_jwk_thumbprint( key: &K, output: &mut dyn ResizeBuffer, ) -> Result<(), Error> { let mut hasher = HashBuffer::::new(); let mut buf = JwkEncoder::new(&mut hasher, JwkEncoderMode::Thumbprint)?; - key.to_jwk_encoder(&mut buf)?; + key.encode_jwk(&mut buf)?; buf.finalize()?; let hash = hasher.finalize(); base64::encode_config_slice(&hash, base64::URL_SAFE_NO_PAD, output.buffer_extend(43)?); Ok(()) } +/// Support for loading a key instance from a JWK pub trait FromJwk: Sized { + /// Import the key from a JWK string reference fn from_jwk(jwk: &str) -> Result { JwkParts::from_str(jwk).and_then(Self::from_jwk_parts) } + /// Import the key from a JWK byte slice fn from_jwk_slice(jwk: &[u8]) -> Result { JwkParts::from_slice(jwk).and_then(Self::from_jwk_parts) } + /// Import the key from a pre-parsed JWK fn from_jwk_parts(jwk: JwkParts<'_>) -> Result; } diff --git a/askar-crypto/src/jwk/ops.rs b/askar-crypto/src/jwk/ops.rs index 7282792e..fb34504f 100644 --- a/askar-crypto/src/jwk/ops.rs +++ b/askar-crypto/src/jwk/ops.rs @@ -14,16 +14,25 @@ static OPS: &[KeyOps] = &[ KeyOps::DeriveBits, ]; +/// Supported JWK key operations #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] #[repr(usize)] pub enum KeyOps { + /// Allows encryption Encrypt = 1 << 0, + /// Allows decryption Decrypt = 1 << 1, + /// Allows signature creation Sign = 1 << 2, + /// Allows signature verification Verify = 1 << 3, + /// Allows key wrapping WrapKey = 1 << 4, + /// Allows key unwrapping UnwrapKey = 1 << 5, + /// Allows key derivation DeriveKey = 1 << 6, + /// Allows derivation of bytes DeriveBits = 1 << 7, } @@ -34,6 +43,7 @@ impl Display for KeyOps { } impl KeyOps { + /// String representation of the key operation pub fn as_str(&self) -> &'static str { match self { Self::Encrypt => "encrypt", @@ -47,6 +57,7 @@ impl KeyOps { } } + /// Parse a key operation from a string reference pub fn from_str(key: &str) -> Option { match key { "sign" => Some(Self::Sign), @@ -72,6 +83,7 @@ impl BitOr for KeyOps { } } +/// A set of key operations #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] pub struct KeyOpsSet { @@ -79,10 +91,12 @@ pub struct KeyOpsSet { } impl KeyOpsSet { + /// Create a new, empty operation set pub const fn new() -> Self { Self { value: 0 } } + /// Check if an operation set is empty pub fn is_empty(&self) -> bool { self.value == 0 } diff --git a/askar-crypto/src/jwk/parts.rs b/askar-crypto/src/jwk/parts.rs index bb0da0be..f13e35b0 100644 --- a/askar-crypto/src/jwk/parts.rs +++ b/askar-crypto/src/jwk/parts.rs @@ -8,31 +8,34 @@ use serde::de::{Deserialize, Deserializer, MapAccess, SeqAccess, Visitor}; use super::ops::{KeyOps, KeyOpsSet}; use crate::error::Error; +/// A parsed JWK #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct JwkParts<'a> { - // key type + /// Key type pub kty: &'a str, - // key ID + /// Key ID pub kid: OptAttr<'a>, - // curve type + /// Curve type pub crv: OptAttr<'a>, - // curve key public y coordinate + /// Curve key public x coordinate pub x: OptAttr<'a>, - // curve key public y coordinate + /// Curve key public y coordinate pub y: OptAttr<'a>, - // curve key private key bytes + /// Curve key private key bytes pub d: OptAttr<'a>, - // used by symmetric keys like AES + /// Used by symmetric keys like AES pub k: OptAttr<'a>, - // recognized key operations + /// Recognized key operations pub key_ops: Option, } impl<'de> JwkParts<'de> { + /// Parse a JWK from a string reference pub fn from_str(jwk: &'de str) -> Result { serde_json::from_str(jwk).map_err(err_map!(InvalidData, "Error parsing JWK")) } + /// Parse a JWK from a byte slice pub fn from_slice(jwk: &'de [u8]) -> Result { serde_json::from_slice(jwk).map_err(err_map!(InvalidData, "Error parsing JWK")) } diff --git a/askar-crypto/src/kdf/ecdh_1pu.rs b/askar-crypto/src/kdf/ecdh_1pu.rs index 262f2794..04111290 100644 --- a/askar-crypto/src/kdf/ecdh_1pu.rs +++ b/askar-crypto/src/kdf/ecdh_1pu.rs @@ -51,9 +51,8 @@ impl KeyDerivation for Ecdh1PU<'_, Key> { // hash Zs and Ze directly into the KDF self.ephem_key - .key_exchange_buffer(self.recip_key, &mut kdf)?; - self.send_key - .key_exchange_buffer(self.recip_key, &mut kdf)?; + .write_key_exchange(self.recip_key, &mut kdf)?; + self.send_key.write_key_exchange(self.recip_key, &mut kdf)?; kdf.hash_params(ConcatKDFParams { alg: self.alg, diff --git a/askar-crypto/src/kdf/ecdh_es.rs b/askar-crypto/src/kdf/ecdh_es.rs index 4399f849..d632c8e6 100644 --- a/askar-crypto/src/kdf/ecdh_es.rs +++ b/askar-crypto/src/kdf/ecdh_es.rs @@ -51,7 +51,7 @@ impl KeyDerivation for EcdhEs<'_, Key> { // hash Z directly into the KDF self.ephem_key - .key_exchange_buffer(self.recip_key, &mut kdf)?; + .write_key_exchange(self.recip_key, &mut kdf)?; kdf.hash_params(ConcatKDFParams { alg: self.alg, diff --git a/askar-crypto/src/kdf/mod.rs b/askar-crypto/src/kdf/mod.rs index dfb0ffd2..31cd8488 100644 --- a/askar-crypto/src/kdf/mod.rs +++ b/askar-crypto/src/kdf/mod.rs @@ -15,14 +15,14 @@ pub mod ecdh_es; /// Trait for keys supporting Diffie-Helman key exchange pub trait KeyExchange { /// Perform a key exchange, writing the result to the provided buffer. - fn key_exchange_buffer(&self, other: &Rhs, out: &mut dyn WriteBuffer) -> Result<(), Error>; + fn write_key_exchange(&self, other: &Rhs, out: &mut dyn WriteBuffer) -> Result<(), Error>; #[cfg(feature = "alloc")] #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] /// Perform a key exchange and return a new allocated buffer. fn key_exchange_bytes(&self, other: &Rhs) -> Result { let mut buf = SecretBytes::with_capacity(128); - self.key_exchange_buffer(other, &mut buf)?; + self.write_key_exchange(other, &mut buf)?; Ok(buf) } } diff --git a/askar-crypto/src/lib.rs b/askar-crypto/src/lib.rs index f535298e..6f7751e1 100644 --- a/askar-crypto/src/lib.rs +++ b/askar-crypto/src/lib.rs @@ -2,7 +2,7 @@ #![no_std] #![deny(missing_debug_implementations)] -// #![deny(missing_docs)] +#![deny(missing_docs)] #[cfg(feature = "alloc")] extern crate alloc; diff --git a/askar-crypto/test-no-std/.cargo/config.toml b/askar-crypto/test-no-std/.cargo/config.toml new file mode 100644 index 00000000..62879354 --- /dev/null +++ b/askar-crypto/test-no-std/.cargo/config.toml @@ -0,0 +1,8 @@ +[target.'cfg(target_os = "linux")'] +rustflags = ["-C", "link-arg=-nostartfiles"] + +[target.'cfg(target_os = "windows")'] +rustflags = ["-C", "link-args=/ENTRY:_start /SUBSYSTEM:console"] + +[target.'cfg(target_os = "macos")'] +rustflags = ["-C", "link-args=-e __start -static -nostartfiles"] diff --git a/askar-crypto/test-no-std/Cargo.toml b/askar-crypto/test-no-std/Cargo.toml new file mode 100644 index 00000000..fe298bfb --- /dev/null +++ b/askar-crypto/test-no-std/Cargo.toml @@ -0,0 +1,15 @@ +[workspace] + +[package] +name = "test-no-std" +version = "0.1.0" +edition = "2018" + +[dependencies] +askar-crypto = { path = ".." } + +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" diff --git a/askar-crypto/test-no-std/src/main.rs b/askar-crypto/test-no-std/src/main.rs new file mode 100644 index 00000000..43f9387f --- /dev/null +++ b/askar-crypto/test-no-std/src/main.rs @@ -0,0 +1,14 @@ +#![no_std] +#![no_main] + +use core::panic::PanicInfo; + +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + loop {} +} + +#[no_mangle] +pub extern "C" fn _start() -> ! { + loop {} +} diff --git a/src/kms/envelope.rs b/src/kms/envelope.rs index 213e3eb7..e397813d 100644 --- a/src/kms/envelope.rs +++ b/src/kms/envelope.rs @@ -8,7 +8,7 @@ use crate::{ encrypt::crypto_box::{ crypto_box as nacl_box, crypto_box_open as nacl_box_open, crypto_box_seal as nacl_box_seal, crypto_box_seal_open as nacl_box_seal_open, - CBOX_NONCE_SIZE, CBOX_TAG_SIZE, + CBOX_NONCE_LENGTH, CBOX_TAG_LENGTH, }, kdf::{ecdh_1pu::Ecdh1PU, ecdh_es::EcdhEs}, random::fill_random, @@ -26,8 +26,8 @@ fn cast_x25519(key: &LocalKey) -> Result<&X25519KeyPair, Error> { } /// Generate a new random nonce for crypto_box -pub fn crypto_box_random_nonce() -> Result<[u8; CBOX_NONCE_SIZE], Error> { - let mut nonce = [0u8; CBOX_NONCE_SIZE]; +pub fn crypto_box_random_nonce() -> Result<[u8; CBOX_NONCE_LENGTH], Error> { + let mut nonce = [0u8; CBOX_NONCE_LENGTH]; fill_random(&mut nonce); Ok(nonce) } @@ -41,7 +41,7 @@ pub fn crypto_box( ) -> Result, Error> { let recip_pk = cast_x25519(recip_x25519)?; let sender_sk = cast_x25519(sender_x25519)?; - let mut buffer = SecretBytes::from_slice_reserve(message, CBOX_TAG_SIZE); + let mut buffer = SecretBytes::from_slice_reserve(message, CBOX_TAG_LENGTH); nacl_box(recip_pk, sender_sk, &mut buffer, nonce)?; Ok(buffer.into_vec()) } diff --git a/src/kms/key.rs b/src/kms/key.rs index 2c303fe8..432ea2c8 100644 --- a/src/kms/key.rs +++ b/src/kms/key.rs @@ -173,11 +173,11 @@ impl LocalKey { } impl KeyExchange for LocalKey { - fn key_exchange_buffer( + fn write_key_exchange( &self, other: &LocalKey, out: &mut dyn WriteBuffer, ) -> Result<(), CryptoError> { - self.inner.key_exchange_buffer(&other.inner, out) + self.inner.write_key_exchange(&other.inner, out) } } From c6f6435456064b74ec7707e69263f39066d3088e Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Tue, 27 Apr 2021 21:53:05 -0700 Subject: [PATCH 069/116] rename error Signed-off-by: Andrew Whitehead --- wrappers/python/aries_askar/__init__.py | 6 +-- wrappers/python/aries_askar/bindings.py | 18 ++++----- wrappers/python/aries_askar/error.py | 6 +-- wrappers/python/aries_askar/store.py | 54 ++++++++++++------------- 4 files changed, 42 insertions(+), 42 deletions(-) diff --git a/wrappers/python/aries_askar/__init__.py b/wrappers/python/aries_askar/__init__.py index 6704c64b..9092252e 100644 --- a/wrappers/python/aries_askar/__init__.py +++ b/wrappers/python/aries_askar/__init__.py @@ -1,7 +1,7 @@ """aries-askar Python wrapper library""" from .bindings import generate_raw_key, version -from .error import StoreError, StoreErrorCode +from .error import AskarError, AskarErrorCode from .key import ( Key, crypto_box, @@ -25,11 +25,11 @@ "derive_key_ecdh_es", "generate_raw_key", "version", + "AskarError", + "AskarErrorCode", "Entry", "Key", "KeyAlg", "Session", "Store", - "StoreError", - "StoreErrorCode", ) diff --git a/wrappers/python/aries_askar/bindings.py b/wrappers/python/aries_askar/bindings.py index 3f12df0e..be0cce6c 100644 --- a/wrappers/python/aries_askar/bindings.py +++ b/wrappers/python/aries_askar/bindings.py @@ -23,7 +23,7 @@ from ctypes.util import find_library from typing import Optional, Union -from .error import StoreError, StoreErrorCode +from .error import AskarError, AskarErrorCode from .types import EntryOperation, KeyAlg @@ -325,14 +325,14 @@ def _load_library(lib_name: str) -> CDLL: lib_path = find_library(lib_name) if not lib_path: - raise StoreError( - StoreErrorCode.WRAPPER, f"Library not found in path: {lib_path}" + raise AskarError( + AskarErrorCode.WRAPPER, f"Library not found in path: {lib_path}" ) try: return CDLL(lib_path) except OSError as e: - raise StoreError( - StoreErrorCode.WRAPPER, f"Error loading library: {lib_path}" + raise AskarError( + AskarErrorCode.WRAPPER, f"Error loading library: {lib_path}" ) from e @@ -499,7 +499,7 @@ def encode_bytes( return buf -def get_current_error(expect: bool = False) -> Optional[StoreError]: +def get_current_error(expect: bool = False) -> Optional[AskarError]: """ Get the error result from the previous failed API method. @@ -514,12 +514,12 @@ def get_current_error(expect: bool = False) -> Optional[StoreError]: LOGGER.warning("JSON decode error for askar_get_current_error") msg = None if msg and "message" in msg and "code" in msg: - return StoreError( - StoreErrorCode(msg["code"]), msg["message"], msg.get("extra") + return AskarError( + AskarErrorCode(msg["code"]), msg["message"], msg.get("extra") ) if not expect: return None - return StoreError(StoreErrorCode.WRAPPER, "Unknown error") + return AskarError(AskarErrorCode.WRAPPER, "Unknown error") def generate_raw_key(seed: Union[str, bytes] = None) -> str: diff --git a/wrappers/python/aries_askar/error.py b/wrappers/python/aries_askar/error.py index 318d6c09..dd1b4994 100644 --- a/wrappers/python/aries_askar/error.py +++ b/wrappers/python/aries_askar/error.py @@ -3,7 +3,7 @@ from enum import IntEnum -class StoreErrorCode(IntEnum): +class AskarErrorCode(IntEnum): SUCCESS = 0 BACKEND = 1 BUSY = 2 @@ -16,8 +16,8 @@ class StoreErrorCode(IntEnum): WRAPPER = 99 -class StoreError(Exception): - def __init__(self, code: StoreErrorCode, message: str, extra: str = None): +class AskarError(Exception): + def __init__(self, code: AskarErrorCode, message: str, extra: str = None): super().__init__(message) self.code = code self.extra = extra diff --git a/wrappers/python/aries_askar/store.py b/wrappers/python/aries_askar/store.py index 2c231cfd..c0989b3b 100644 --- a/wrappers/python/aries_askar/store.py +++ b/wrappers/python/aries_askar/store.py @@ -15,7 +15,7 @@ SessionHandle, StoreHandle, ) -from .error import StoreError, StoreErrorCode +from .error import AskarError, AskarErrorCode from .key import Key from .types import EntryOperation @@ -217,8 +217,8 @@ async def __anext__(self): if self._handle is None: (store, profile, category, tag_filter, offset, limit) = self.params if not store.handle: - raise StoreError( - StoreErrorCode.WRAPPER, "Cannot scan from closed store" + raise AskarError( + AskarErrorCode.WRAPPER, "Cannot scan from closed store" ) self._handle = await bindings.scan_start( store.handle, profile, category, tag_filter, offset, limit @@ -370,14 +370,14 @@ def store(self) -> Store: async def count(self, category: str, tag_filter: Union[str, dict] = None) -> int: if not self._handle: - raise StoreError(StoreErrorCode.WRAPPER, "Cannot count from closed session") + raise AskarError(AskarErrorCode.WRAPPER, "Cannot count from closed session") return await bindings.session_count(self._handle, category, tag_filter) async def fetch( self, category: str, name: str, *, for_update: bool = False ) -> Optional[Entry]: if not self._handle: - raise StoreError(StoreErrorCode.WRAPPER, "Cannot fetch from closed session") + raise AskarError(AskarErrorCode.WRAPPER, "Cannot fetch from closed session") result_handle = await bindings.session_fetch( self._handle, category, name, for_update ) @@ -392,7 +392,7 @@ async def fetch_all( for_update: bool = False, ) -> EntryList: if not self._handle: - raise StoreError(StoreErrorCode.WRAPPER, "Cannot fetch from closed session") + raise AskarError(AskarErrorCode.WRAPPER, "Cannot fetch from closed session") return EntryList( await bindings.session_fetch_all( self._handle, category, tag_filter, limit, for_update @@ -409,7 +409,7 @@ async def insert( value_json=None, ): if not self._handle: - raise StoreError(StoreErrorCode.WRAPPER, "Cannot update closed session") + raise AskarError(AskarErrorCode.WRAPPER, "Cannot update closed session") if value is None and value_json is not None: value = json.dumps(value_json) await bindings.session_update( @@ -426,7 +426,7 @@ async def replace( value_json=None, ): if not self._handle: - raise StoreError(StoreErrorCode.WRAPPER, "Cannot update closed session") + raise AskarError(AskarErrorCode.WRAPPER, "Cannot update closed session") if value is None and value_json is not None: value = json.dumps(value_json) await bindings.session_update( @@ -439,7 +439,7 @@ async def remove( name: str, ): if not self._handle: - raise StoreError(StoreErrorCode.WRAPPER, "Cannot update closed session") + raise AskarError(AskarErrorCode.WRAPPER, "Cannot update closed session") await bindings.session_update( self._handle, EntryOperation.REMOVE, category, name ) @@ -450,8 +450,8 @@ async def remove_all( tag_filter: Union[str, dict] = None, ) -> int: if not self._handle: - raise StoreError( - StoreErrorCode.WRAPPER, "Cannot remove all for closed session" + raise AskarError( + AskarErrorCode.WRAPPER, "Cannot remove all for closed session" ) return await bindings.session_remove_all(self._handle, category, tag_filter) @@ -465,8 +465,8 @@ async def insert_key( expiry_ms: int = None, ) -> str: if not self._handle: - raise StoreError( - StoreErrorCode.WRAPPER, "Cannot insert key with closed session" + raise AskarError( + AskarErrorCode.WRAPPER, "Cannot insert key with closed session" ) return str( await bindings.session_insert_key( @@ -478,8 +478,8 @@ async def fetch_key( self, name: str, *, for_update: bool = False ) -> Optional[KeyEntry]: if not self._handle: - raise StoreError( - StoreErrorCode.WRAPPER, "Cannot fetch key from closed session" + raise AskarError( + AskarErrorCode.WRAPPER, "Cannot fetch key from closed session" ) result_handle = await bindings.session_fetch_key(self._handle, name, for_update) return next(KeyEntryList(result_handle, 1)) if result_handle else None @@ -493,32 +493,32 @@ async def update_key( expiry_ms: int = None, ): if not self._handle: - raise StoreError( - StoreErrorCode.WRAPPER, "Cannot update key with closed session" + raise AskarError( + AskarErrorCode.WRAPPER, "Cannot update key with closed session" ) await bindings.session_update_key(self._handle, name, metadata, tags, expiry_ms) async def remove_key(self, name: str): if not self._handle: - raise StoreError( - StoreErrorCode.WRAPPER, "Cannot remove key with closed session" + raise AskarError( + AskarErrorCode.WRAPPER, "Cannot remove key with closed session" ) await bindings.session_remove_key(self._handle, name) async def commit(self): if not self._is_txn: - raise StoreError(StoreErrorCode.WRAPPER, "Session is not a transaction") + raise AskarError(AskarErrorCode.WRAPPER, "Session is not a transaction") if not self._handle: - raise StoreError(StoreErrorCode.WRAPPER, "Cannot commit closed transaction") + raise AskarError(AskarErrorCode.WRAPPER, "Cannot commit closed transaction") await self._handle.close(commit=True) self._handle = None async def rollback(self): if not self._is_txn: - raise StoreError(StoreErrorCode.WRAPPER, "Session is not a transaction") + raise AskarError(AskarErrorCode.WRAPPER, "Session is not a transaction") if not self._handle: - raise StoreError( - StoreErrorCode.WRAPPER, "Cannot rollback closed transaction" + raise AskarError( + AskarErrorCode.WRAPPER, "Cannot rollback closed transaction" ) await self._handle.close(commit=False) self._handle = None @@ -546,11 +546,11 @@ def is_transaction(self) -> bool: async def _open(self) -> Session: if not self._store.handle: - raise StoreError( - StoreErrorCode.WRAPPER, "Cannot start session from closed store" + raise AskarError( + AskarErrorCode.WRAPPER, "Cannot start session from closed store" ) if self._session: - raise StoreError(StoreErrorCode.WRAPPER, "Session already opened") + raise AskarError(AskarErrorCode.WRAPPER, "Session already opened") self._session = Session( self._store, await bindings.session_start( From 96ddacfa59ead1d876049b1e63192e503c695f0b Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Tue, 27 Apr 2021 23:58:10 -0700 Subject: [PATCH 070/116] prepend tag in crypto box Signed-off-by: Andrew Whitehead --- askar-crypto/src/buffer/writer.rs | 5 ++-- askar-crypto/src/encrypt/crypto_box.rs | 39 +++++++++++++++++--------- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/askar-crypto/src/buffer/writer.rs b/askar-crypto/src/buffer/writer.rs index d769e397..b7cfd297 100644 --- a/askar-crypto/src/buffer/writer.rs +++ b/askar-crypto/src/buffer/writer.rs @@ -150,11 +150,12 @@ impl AsMut<[u8]> for Writer<'_, B> { impl ResizeBuffer for Writer<'_, B> { fn buffer_insert(&mut self, pos: usize, data: &[u8]) -> Result<(), Error> { - self.inner.buffer_insert(pos, data) + self.inner.buffer_insert(self.pos + pos, data) } fn buffer_remove(&mut self, range: Range) -> Result<(), Error> { - self.inner.buffer_remove(range) + self.inner + .buffer_remove((self.pos + range.start)..(self.pos + range.end)) } fn buffer_resize(&mut self, len: usize) -> Result<(), Error> { diff --git a/askar-crypto/src/encrypt/crypto_box.rs b/askar-crypto/src/encrypt/crypto_box.rs index 00d8fe61..4e8c1547 100644 --- a/askar-crypto/src/encrypt/crypto_box.rs +++ b/askar-crypto/src/encrypt/crypto_box.rs @@ -15,11 +15,11 @@ use crate::{ repr::{KeyGen, KeyPublicBytes}, }; -/// The size of the salsa box nonce +/// The length of the salsa box nonce pub const CBOX_NONCE_LENGTH: usize = NonceSize::::USIZE; -/// The size of the salsa box key size +/// The length of the salsa box key (x25519 public key) pub const CBOX_KEY_LENGTH: usize = crate::alg::x25519::PUBLIC_KEY_LENGTH; -/// The size of the salsa box tag size +/// The length of the salsa box tag pub const CBOX_TAG_LENGTH: usize = TagSize::::USIZE; type NonceSize = ::NonceSize; @@ -57,7 +57,7 @@ pub fn crypto_box( let tag = box_inst .encrypt_in_place_detached(nonce, &[], buffer.as_mut()) .map_err(|_| err_msg!(Encryption, "Crypto box AEAD encryption error"))?; - buffer.buffer_write(&tag[..])?; + buffer.buffer_insert(0, &tag[..])?; Ok(()) } @@ -71,19 +71,16 @@ pub fn crypto_box_open( let recip_sk = secret_key_from(recip_sk)?; let nonce = nonce_from(nonce)?; let buf_len = buffer.as_ref().len(); - if buf_len < TagSize::::USIZE { + if buf_len < CBOX_TAG_LENGTH { return Err(err_msg!(Encryption, "Invalid size for encrypted data")); } - let tag_start = buf_len - TagSize::::USIZE; - let mut tag = GenericArray::default(); - tag.clone_from_slice(&buffer.as_ref()[tag_start..]); - + // the tag is prepended + let tag = GenericArray::clone_from_slice(&buffer.as_ref()[..CBOX_TAG_LENGTH]); let box_inst = SalsaBox::new(&sender_pk.public, &recip_sk); - box_inst - .decrypt_in_place_detached(nonce, &[], &mut buffer.as_mut()[..tag_start], &tag) + .decrypt_in_place_detached(nonce, &[], &mut buffer.as_mut()[CBOX_TAG_LENGTH..], &tag) .map_err(|_| err_msg!(Encryption, "Crypto box AEAD decryption error"))?; - buffer.buffer_resize(tag_start)?; + buffer.buffer_remove(0..CBOX_TAG_LENGTH)?; Ok(()) } @@ -105,7 +102,7 @@ pub fn crypto_box_seal_nonce( pub fn crypto_box_seal(recip_pk: &X25519KeyPair, message: &[u8]) -> Result { let ephem_kp = X25519KeyPair::generate()?; let ephem_pk_bytes = ephem_kp.public.as_bytes(); - let buf_len = CBOX_KEY_LENGTH + message.len() + TagSize::::USIZE; + let buf_len = CBOX_KEY_LENGTH + CBOX_TAG_LENGTH + message.len(); let mut buffer = SecretBytes::with_capacity(buf_len); buffer.buffer_write(ephem_pk_bytes)?; buffer.buffer_write(message)?; @@ -149,7 +146,7 @@ mod tests { crypto_box(&pk, &sk, &mut buffer, nonce).unwrap(); assert_eq!( buffer, - &hex!("1cc8721d567baa8f2b5583848dc97d373f7aa2223b57780c60f773")[..] + &hex!("848dc97d373f7aa2223b57780c60f7731cc8721d567baa8f2b5583")[..] ); crypto_box_open(&sk, &pk, &mut buffer, nonce).unwrap(); @@ -170,4 +167,18 @@ mod tests { let open = crypto_box_seal_open(&recip, &sealed).unwrap(); assert_eq!(open, &message[..]); } + + #[test] + fn crypto_box_unseal_expected() { + use crate::alg::ed25519::Ed25519KeyPair; + let recip = Ed25519KeyPair::from_secret_bytes(b"testseed000000000000000000000001") + .unwrap() + .to_x25519_keypair(); + let ciphertext = hex!( + "ed443c0377a579857f2f00543e0da0f2585b6119cd9e43c871e4f1114c7ce9050b + a8811edf39d257bbeec0d423a0a7ff98d424fbfa9d52e0c5b3f674738f75d8e727f + 5526296482fd0fd013d71d50ce4ce5ebe9c2fa1c230298419a9" + ); + crypto_box_seal_open(&recip, &ciphertext).unwrap(); + } } From df6eed8294a3284ad19b13c465d056526a24efad Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Wed, 28 Apr 2021 00:29:27 -0700 Subject: [PATCH 071/116] restore Entry.raw_value Signed-off-by: Andrew Whitehead --- wrappers/python/aries_askar/bindings.py | 4 ++-- wrappers/python/aries_askar/store.py | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/wrappers/python/aries_askar/bindings.py b/wrappers/python/aries_askar/bindings.py index be0cce6c..e6723007 100644 --- a/wrappers/python/aries_askar/bindings.py +++ b/wrappers/python/aries_askar/bindings.py @@ -119,11 +119,11 @@ def get_name(self, index: int) -> str: ) return str(name) - def get_value(self, index: int) -> bytes: + def get_value(self, index: int) -> memoryview: """Get the entry value.""" val = ByteBuffer() do_call("askar_entry_list_get_value", self, c_int32(index), byref(val)) - return bytes(val) + return memoryview(val.raw) def get_tags(self, index: int) -> dict: """Get the entry tags.""" diff --git a/wrappers/python/aries_askar/store.py b/wrappers/python/aries_askar/store.py index c0989b3b..7013c8c3 100644 --- a/wrappers/python/aries_askar/store.py +++ b/wrappers/python/aries_askar/store.py @@ -39,9 +39,14 @@ def name(self) -> str: """Accessor for the entry name.""" return self._list.get_name(self._pos) - @cached_property + @property def value(self) -> bytes: """Accessor for the entry value.""" + return bytes(self.raw_value) + + @cached_property + def raw_value(self) -> memoryview: + """Accessor for the entry raw value.""" return self._list.get_value(self._pos) @property From f9fabf7175523908d40c4b05a77b0015e6101121 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Wed, 28 Apr 2021 15:49:31 -0700 Subject: [PATCH 072/116] implement signature type normalization Signed-off-by: Andrew Whitehead --- askar-crypto/src/alg/mod.rs | 120 ++++++++++++++++++++++++------------ askar-crypto/src/sign.rs | 17 ++++- src/kms/key.rs | 16 ++++- 3 files changed, 108 insertions(+), 45 deletions(-) diff --git a/askar-crypto/src/alg/mod.rs b/askar-crypto/src/alg/mod.rs index 26dd485e..4954de33 100644 --- a/askar-crypto/src/alg/mod.rs +++ b/askar-crypto/src/alg/mod.rs @@ -7,7 +7,10 @@ use core::{ use zeroize::Zeroize; -use crate::error::Error; +use crate::{ + buffer::{WriteBuffer, Writer}, + error::Error, +}; #[cfg(any(test, feature = "any_key"))] mod any; @@ -71,47 +74,80 @@ impl FromStr for KeyAlg { type Err = Error; fn from_str(s: &str) -> Result { - if match_alg(s, &["a128gcm", "aes128gcm"]) { - return Ok(Self::Aes(AesTypes::A128GCM)); - } - if match_alg(s, &["a256gcm", "aes256gcm"]) { - return Ok(Self::Aes(AesTypes::A256GCM)); - } - if match_alg(s, &["c20p", "chacha20poly1305"]) { - return Ok(Self::Chacha20(Chacha20Types::C20P)); - } - if match_alg(s, &["xc20p", "xchacha20poly1305"]) { - return Ok(Self::Chacha20(Chacha20Types::XC20P)); - } - if match_alg(s, &["ed25519"]) { - return Ok(Self::Ed25519); - } - if match_alg(s, &["x25519"]) { - return Ok(Self::X25519); - } - if match_alg(s, &["k256", "secp256k1"]) { - return Ok(Self::EcCurve(EcCurves::Secp256k1)); + match normalize_alg(s)? { + a if a == "a128gcm" || a == "aes128gcm" => Ok(Self::Aes(AesTypes::A128GCM)), + a if a == "a256gcm" || a == "aes256gcm" => Ok(Self::Aes(AesTypes::A256GCM)), + a if a == "c20p" || a == "chacha20poly1305" => Ok(Self::Chacha20(Chacha20Types::C20P)), + a if a == "xc20p" || a == "xchacha20poly1305" => { + Ok(Self::Chacha20(Chacha20Types::XC20P)) + } + a if a == "ed25519" => Ok(Self::Ed25519), + a if a == "x25519" => Ok(Self::X25519), + a if a == "k256" || a == "secp256k1" => Ok(Self::EcCurve(EcCurves::Secp256k1)), + a if a == "p256" || a == "secp256r1" => Ok(Self::EcCurve(EcCurves::Secp256r1)), + _ => Err(err_msg!(Unsupported, "Unknown key algorithm")), } - if match_alg(s, &["p256", "secp256r1"]) { - return Ok(Self::EcCurve(EcCurves::Secp256r1)); + } +} + +#[inline(always)] +pub(crate) fn normalize_alg(alg: &str) -> Result { + NormalizedAlg::new(alg) +} + +// Going through some hoops to avoid allocating. +// This struct stores up to 64 bytes of a normalized +// algorithm name in order to speed up comparisons +// when matching. +pub(crate) struct NormalizedAlg { + len: usize, + buf: [u8; 64], +} + +impl NormalizedAlg { + fn new(val: &str) -> Result { + let mut slf = Self { + len: 0, + buf: [0; 64], + }; + let mut cu = [0u8; 4]; + let mut writer = Writer::from_slice(slf.buf.as_mut()); + for c in NormalizedIter::new(val) { + let s = c.encode_utf8(&mut cu); + writer.buffer_write(s.as_bytes())?; } - Err(err_msg!(Unsupported, "Unknown key algorithm")) + slf.len = writer.position(); + Ok(slf) } } -struct NormalizeAlg<'a> { +impl AsRef<[u8]> for NormalizedAlg { + fn as_ref(&self) -> &[u8] { + &self.buf[..self.len] + } +} + +impl> PartialEq for NormalizedAlg { + fn eq(&self, other: &T) -> bool { + self.as_ref() == other.as_ref() + } +} + +struct NormalizedIter<'a> { chars: core::str::Chars<'a>, } -fn normalize_alg(alg: &str) -> NormalizeAlg<'_> { - NormalizeAlg { chars: alg.chars() } +impl<'a> NormalizedIter<'a> { + pub fn new(val: &'a str) -> Self { + Self { chars: val.chars() } + } } -impl Iterator for NormalizeAlg<'_> { +impl Iterator for NormalizedIter<'_> { type Item = char; fn next(&mut self) -> Option { while let Some(c) = self.chars.next() { - if c != '-' && c != '_' { + if c != '-' && c != '_' && c != ' ' { return Some(c.to_ascii_lowercase()); } } @@ -119,18 +155,6 @@ impl Iterator for NormalizeAlg<'_> { } } -fn match_alg(alg: &str, pats: &[&str]) -> bool { - 'pats: for pat in pats { - for (a, b) in pat.chars().zip(normalize_alg(alg)) { - if a != b { - continue 'pats; - } - } - return true; - } - false -} - impl Display for KeyAlg { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.write_str(self.as_str()) @@ -179,3 +203,17 @@ pub trait HasKeyAlg: Debug { /// Get the corresponding key algorithm. fn algorithm(&self) -> KeyAlg; } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn cmp_normalize() { + assert_eq!(normalize_alg("Test").unwrap() == "test", true); + assert_eq!(normalize_alg("t-e-s-t").unwrap() == "test", true); + assert_eq!(normalize_alg("--TE__ST--").unwrap() == "test", true); + assert_eq!(normalize_alg("t-e-s-t").unwrap() == "tes", false); + assert_eq!(normalize_alg("t-e-s-t").unwrap() == "testt", false); + } +} diff --git a/askar-crypto/src/sign.rs b/askar-crypto/src/sign.rs index 6f410c23..9035f70f 100644 --- a/askar-crypto/src/sign.rs +++ b/askar-crypto/src/sign.rs @@ -1,8 +1,10 @@ //! Signature traits and parameters +use core::str::FromStr; + #[cfg(feature = "alloc")] use crate::buffer::SecretBytes; -use crate::{buffer::WriteBuffer, error::Error}; +use crate::{alg::normalize_alg, buffer::WriteBuffer, error::Error}; /// Signature creation operations pub trait KeySign: KeySigVerify { @@ -54,6 +56,19 @@ pub enum SignatureType { ES256K, } +impl FromStr for SignatureType { + type Err = Error; + + fn from_str(s: &str) -> Result { + match normalize_alg(s)? { + a if a == "eddsa" => Ok(Self::EdDSA), + a if a == "es256" => Ok(Self::ES256), + a if a == "es256k" => Ok(Self::ES256K), + _ => Err(err_msg!(Unsupported, "Unknown signature algorithm")), + } + } +} + impl SignatureType { /// Get the length of the signature output. pub const fn signature_length(&self) -> usize { diff --git a/src/kms/key.rs b/src/kms/key.rs index 432ea2c8..3e8c0373 100644 --- a/src/kms/key.rs +++ b/src/kms/key.rs @@ -1,3 +1,5 @@ +use std::str::FromStr; + pub use crate::crypto::{ alg::KeyAlg, buffer::{SecretBytes, WriteBuffer}, @@ -9,7 +11,7 @@ use crate::{ jwk::{FromJwk, ToJwk}, kdf::{KeyDerivation, KeyExchange}, random::fill_random, - sign::{KeySigVerify, KeySign}, + sign::{KeySigVerify, KeySign, SignatureType}, Error as CryptoError, }, error::Error, @@ -157,7 +159,11 @@ impl LocalKey { /// Sign a message with this private signing key pub fn sign_message(&self, message: &[u8], sig_type: Option<&str>) -> Result, Error> { let mut sig = Vec::new(); - self.inner.write_signature(message, None, &mut sig)?; + self.inner.write_signature( + message, + sig_type.map(SignatureType::from_str).transpose()?, + &mut sig, + )?; Ok(sig) } @@ -168,7 +174,11 @@ impl LocalKey { signature: &[u8], sig_type: Option<&str>, ) -> Result { - Ok(self.inner.verify_signature(message, signature, None)?) + Ok(self.inner.verify_signature( + message, + signature, + sig_type.map(SignatureType::from_str).transpose()?, + )?) } } From 588cf488f28d90edbf1da5716f0849189dff2d66 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Wed, 28 Apr 2021 16:17:43 -0700 Subject: [PATCH 073/116] access aead params via FFI Signed-off-by: Andrew Whitehead --- src/ffi/key.rs | 24 ++++++++++++++++++++++++ src/kms/key.rs | 13 +++++++++++++ wrappers/python/aries_askar/bindings.py | 22 ++++++++++++++++++++++ wrappers/python/aries_askar/key.py | 3 +++ wrappers/python/demo/test.py | 3 +++ 5 files changed, 65 insertions(+) diff --git a/src/ffi/key.rs b/src/ffi/key.rs index 9b306000..a3d653c1 100644 --- a/src/ffi/key.rs +++ b/src/ffi/key.rs @@ -10,6 +10,12 @@ use crate::kms::{ pub type LocalKeyHandle = ArcHandle; +#[repr(C)] +pub struct AeadParams { + nonce_length: i32, + tag_length: i32, +} + #[no_mangle] pub extern "C" fn askar_key_generate( alg: FfiStr<'_>, @@ -226,6 +232,24 @@ pub extern "C" fn askar_key_aead_random_nonce( } } +#[no_mangle] +pub extern "C" fn askar_key_aead_get_params( + handle: LocalKeyHandle, + out: *mut AeadParams, +) -> ErrorCode { + catch_err! { + trace!("AEAD get params: {}", handle); + check_useful_c_ptr!(out); + let key = handle.load()?; + let params = key.aead_params()?; + unsafe { *out = AeadParams { + nonce_length: params.nonce_length as i32, + tag_length: params.tag_length as i32 + } }; + Ok(ErrorCode::Success) + } +} + #[no_mangle] pub extern "C" fn askar_key_aead_encrypt( handle: LocalKeyHandle, diff --git a/src/kms/key.rs b/src/kms/key.rs index 3e8c0373..f1c74595 100644 --- a/src/kms/key.rs +++ b/src/kms/key.rs @@ -3,6 +3,7 @@ use std::str::FromStr; pub use crate::crypto::{ alg::KeyAlg, buffer::{SecretBytes, WriteBuffer}, + encrypt::KeyAeadParams, }; use crate::{ crypto::{ @@ -122,6 +123,18 @@ impl LocalKey { }) } + /// Fetch the AEAD parameter lengths + pub fn aead_params(&self) -> Result { + let params = self.inner.aead_params(); + if params.nonce_length == 0 { + return Err(err_msg!( + Unsupported, + "AEAD is not supported for this key type" + )); + } + Ok(params) + } + /// Create a new random nonce for AEAD message encryption pub fn aead_random_nonce(&self) -> Result, Error> { let nonce_len = self.inner.aead_params().nonce_length; diff --git a/wrappers/python/aries_askar/bindings.py b/wrappers/python/aries_askar/bindings.py index e6723007..f3591213 100644 --- a/wrappers/python/aries_askar/bindings.py +++ b/wrappers/python/aries_askar/bindings.py @@ -294,6 +294,22 @@ def __del__(self): get_library().askar_string_free(self) +class AeadParams(Structure): + """A byte buffer allocated by the library.""" + + _fields_ = [ + ("nonce_length", c_int32), + ("tag_length", c_int32), + ] + + def __repr__(self) -> str: + """Format AEAD params as a string.""" + return ( + f"" + ) + + def get_library() -> CDLL: """Return the CDLL instance, loading it if necessary.""" global LIB @@ -956,6 +972,12 @@ def key_get_jwk_thumbprint(handle: LocalKeyHandle) -> str: return str(thumb) +def key_aead_get_params(handle: LocalKeyHandle) -> AeadParams: + params = AeadParams() + do_call("askar_key_aead_get_params", handle, byref(params)) + return params + + def key_aead_random_nonce(handle: LocalKeyHandle) -> ByteBuffer: nonce = ByteBuffer() do_call("askar_key_aead_random_nonce", handle, byref(nonce)) diff --git a/wrappers/python/aries_askar/key.py b/wrappers/python/aries_askar/key.py index 2821bd7d..0d6f12ef 100644 --- a/wrappers/python/aries_askar/key.py +++ b/wrappers/python/aries_askar/key.py @@ -60,6 +60,9 @@ def get_jwk_secret(self) -> str: def get_jwk_thumbprint(self) -> str: return bindings.key_get_jwk_thumbprint(self._handle) + def aead_params(self) -> bindings.AeadParams: + return bindings.key_aead_get_params(self._handle) + def aead_random_nonce(self) -> bytes: return bytes(bindings.key_aead_random_nonce(self._handle)) diff --git a/wrappers/python/demo/test.py b/wrappers/python/demo/test.py index 0217b299..6cb0071d 100644 --- a/wrappers/python/demo/test.py +++ b/wrappers/python/demo/test.py @@ -64,6 +64,9 @@ def keys_test(): data = b"test message" nonce = key.aead_random_nonce() + params = key.aead_params() + assert params.nonce_length == 12 + assert params.tag_length == 16 enc = key.aead_encrypt(data, nonce, b"aad") dec = key.aead_decrypt(enc, nonce, b"aad") assert data == bytes(dec) From 2c90f202be911a83d90d63a57a11174d063addb4 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Wed, 28 Apr 2021 16:23:17 -0700 Subject: [PATCH 074/116] lint for rust_2018_idioms Signed-off-by: Andrew Whitehead --- askar-crypto/src/jwk/parts.rs | 6 +++--- askar-crypto/src/lib.rs | 3 +-- src/lib.rs | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/askar-crypto/src/jwk/parts.rs b/askar-crypto/src/jwk/parts.rs index f13e35b0..34ab8a64 100644 --- a/askar-crypto/src/jwk/parts.rs +++ b/askar-crypto/src/jwk/parts.rs @@ -120,7 +120,7 @@ struct JwkMapVisitor<'de>(PhantomData<&'de ()>); impl<'de> Visitor<'de> for JwkMapVisitor<'de> { type Value = JwkParts<'de>; - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("an object representing a JWK") } @@ -194,7 +194,7 @@ struct KeyOpsVisitor; impl<'de> Visitor<'de> for KeyOpsVisitor { type Value = KeyOpsSet; - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("an array of key operations") } @@ -239,7 +239,7 @@ mod tests { "key_ops": ["sign", "verify"], "kid": "FdFYFzERwC2uCBB46pZQi4GG85LujR8obt-KWRBICVQ" }"#; - let parts = serde_json::from_str::(jwk).unwrap(); + let parts = serde_json::from_str::>(jwk).unwrap(); assert_eq!(parts.kty, "OKP"); assert_eq!( parts.kid, diff --git a/askar-crypto/src/lib.rs b/askar-crypto/src/lib.rs index 6f7751e1..d8e045ad 100644 --- a/askar-crypto/src/lib.rs +++ b/askar-crypto/src/lib.rs @@ -1,8 +1,7 @@ //! Cryptography primitives and operations for aries-askar. #![no_std] -#![deny(missing_debug_implementations)] -#![deny(missing_docs)] +#![deny(missing_docs, missing_debug_implementations, rust_2018_idioms)] #[cfg(feature = "alloc")] extern crate alloc; diff --git a/src/lib.rs b/src/lib.rs index 95685750..cfb1c2f4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ //! Secure storage designed for Hyperledger Aries agents #![cfg_attr(docsrs, feature(doc_cfg))] -#![warn(missing_docs, missing_debug_implementations, rust_2018_idioms)] +#![deny(missing_docs, missing_debug_implementations, rust_2018_idioms)] #[macro_use] mod error; From 0cb48670c44116a698c0d1ac387df8c114458951 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Wed, 28 Apr 2021 16:34:00 -0700 Subject: [PATCH 075/116] implement KeyDerivation for Argon2 Signed-off-by: Andrew Whitehead --- askar-crypto/src/kdf/argon2.rs | 54 ++++++++++++++++++++++------------ src/protect/kdf/argon2.rs | 4 ++- 2 files changed, 39 insertions(+), 19 deletions(-) diff --git a/askar-crypto/src/kdf/argon2.rs b/askar-crypto/src/kdf/argon2.rs index 27f8c14e..9fb62b45 100644 --- a/askar-crypto/src/kdf/argon2.rs +++ b/askar-crypto/src/kdf/argon2.rs @@ -1,12 +1,13 @@ //! Argon2 key derivation from a password +pub use argon2::{Algorithm, Version}; + +use super::KeyDerivation; use crate::{ error::Error, generic_array::typenum::{Unsigned, U16}, }; -pub use argon2::{Algorithm, Version}; - /// The size of the password salt pub type SaltSize = U16; @@ -38,31 +39,45 @@ pub struct Params { } /// Struct wrapping the KDF functionality -#[derive(Clone, Copy, Debug)] -pub struct Argon2; +#[derive(Debug)] +pub struct Argon2<'a> { + password: &'a [u8], + salt: &'a [u8], + params: Params, +} -impl Argon2 { - /// Derive the key and write it to the provided buffer - pub fn derive_key( - password: &[u8], - salt: &[u8], - params: Params, - output: &mut [u8], - ) -> Result<(), Error> { +impl<'a> Argon2<'a> { + /// Create a new Argon2 key derivation instance + pub fn new(password: &'a [u8], salt: &'a [u8], params: Params) -> Result { if salt.len() < SALT_LENGTH { return Err(err_msg!(Usage, "Invalid salt for argon2i hash")); } - if output.len() > u32::MAX as usize { + Ok(Self { + password, + salt, + params, + }) + } +} + +impl KeyDerivation for Argon2<'_> { + fn derive_key_bytes(&mut self, key_output: &mut [u8]) -> Result<(), Error> { + if key_output.len() > u32::MAX as usize { return Err(err_msg!( Usage, "Output length exceeds max for argon2i hash" )); } - let context = - argon2::Argon2::new(None, params.time_cost, params.mem_cost, 1, params.version) - .map_err(|_| err_msg!(Unexpected, "Error creating hasher"))?; + let context = argon2::Argon2::new( + None, + self.params.time_cost, + self.params.mem_cost, + 1, + self.params.version, + ) + .map_err(|_| err_msg!(Unexpected, "Error creating hasher"))?; context - .hash_password_into(params.alg, password, salt, &[], output) + .hash_password_into(self.params.alg, self.password, self.salt, &[], key_output) .map_err(|_| err_msg!(Unexpected, "Error deriving key")) } } @@ -76,7 +91,10 @@ mod tests { let pass = b"my password"; let salt = b"long enough salt"; let mut output = [0u8; 32]; - Argon2::derive_key(pass, salt, PARAMS_INTERACTIVE, &mut output).unwrap(); + Argon2::new(pass, salt, PARAMS_INTERACTIVE) + .unwrap() + .derive_key_bytes(&mut output) + .unwrap(); assert_eq!( output, hex!("9ef87bcf828c46c0136a0d1d9e391d713f75b327c6dc190455bd36c1bae33259") diff --git a/src/protect/kdf/argon2.rs b/src/protect/kdf/argon2.rs index 73819319..0afc0866 100644 --- a/src/protect/kdf/argon2.rs +++ b/src/protect/kdf/argon2.rs @@ -1,3 +1,5 @@ +use askar_crypto::kdf::KeyDerivation; + use crate::{ crypto::{ buffer::ArrayKey, @@ -55,7 +57,7 @@ impl Level { pub fn derive_key(&self, password: &[u8], salt: &[u8]) -> Result { let mut key = ArrayKey::<::KeySize>::default(); - Argon2::derive_key(password, salt, *self.params(), key.as_mut())?; + Argon2::new(password, salt, *self.params())?.derive_key_bytes(key.as_mut())?; Ok(StoreKey::from(StoreKeyType::from_secret_bytes( key.as_ref(), )?)) From 39674f97dac57382203f86b12e467036e4c5a068 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Wed, 28 Apr 2021 16:47:22 -0700 Subject: [PATCH 076/116] clean up crypto error mapping Signed-off-by: Andrew Whitehead --- askar-crypto/src/error.rs | 4 ++-- src/error.rs | 19 +++++++++++++++---- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/askar-crypto/src/error.rs b/askar-crypto/src/error.rs index e0b3d3c9..3df82d6b 100644 --- a/askar-crypto/src/error.rs +++ b/askar-crypto/src/error.rs @@ -84,8 +84,8 @@ impl Error { } /// Accessor for the error message - pub fn message(&self) -> Option<&'static str> { - self.message + pub fn message(&self) -> &'static str { + self.message.unwrap_or_else(|| self.kind.as_str()) } #[cfg(feature = "std")] diff --git a/src/error.rs b/src/error.rs index bb95f6c1..a9925f3e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,6 +1,8 @@ use std::error::Error as StdError; use std::fmt::{self, Display, Formatter}; +use crate::crypto::{Error as CryptoError, ErrorKind as CryptoErrorKind}; + /// The possible kinds of error produced by the crate #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum ErrorKind { @@ -157,10 +159,19 @@ impl From for Error { } } -impl From for Error { - fn from(err: askar_crypto::Error) -> Self { - // FIXME map error types - Error::from_opt_msg(ErrorKind::Input, err.message()) +impl From for Error { + fn from(err: CryptoError) -> Self { + let kind = match err.kind() { + CryptoErrorKind::Encryption => ErrorKind::Encryption, + CryptoErrorKind::ExceededBuffer | CryptoErrorKind::Unexpected => ErrorKind::Unexpected, + CryptoErrorKind::InvalidData + | CryptoErrorKind::InvalidKeyData + | CryptoErrorKind::InvalidNonce + | CryptoErrorKind::MissingSecretKey + | CryptoErrorKind::Usage => ErrorKind::Input, + CryptoErrorKind::Unsupported => ErrorKind::Unsupported, + }; + Error::from_msg(kind, err.message()) } } From b9e1cec88f1ab1a68f8072ee991b741054ccd5db Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Wed, 28 Apr 2021 17:11:26 -0700 Subject: [PATCH 077/116] remove bls for now Signed-off-by: Andrew Whitehead --- askar-crypto/src/alg/bls.rs | 520 ------------------------------------ askar-crypto/src/alg/mod.rs | 13 - askar-crypto/src/sign.rs | 7 +- 3 files changed, 1 insertion(+), 539 deletions(-) delete mode 100644 askar-crypto/src/alg/bls.rs diff --git a/askar-crypto/src/alg/bls.rs b/askar-crypto/src/alg/bls.rs deleted file mode 100644 index e771719b..00000000 --- a/askar-crypto/src/alg/bls.rs +++ /dev/null @@ -1,520 +0,0 @@ -use std::convert::TryFrom; - -use digest::{ - generic_array::{typenum::U48, GenericArray}, - BlockInput, Digest, -}; -use group::{ - prime::{PrimeCurveAffine, PrimeGroup}, - GroupEncoding, -}; -use std::{convert::TryInto, iter::FromIterator, marker::PhantomData, ops::Mul}; - -use bls12_381::{ - hash_to_curve::{ExpandMessage, ExpandMsgXmd, HashToCurve, HashToField}, - pairing, G1Affine, G1Projective, G2Affine, G2Projective, Gt, Scalar, -}; -use rand::{rngs::OsRng, RngCore}; -use zeroize::Zeroize; - -use crate::{ - error::Error, - keys::any::{AnyPrivateKey, AnyPublicKey}, - keys::caps::{KeyAlg, KeyCapSign, KeyCapVerify, SignatureFormat, SignatureType}, - types::SecretBytes, -}; - -const SIG_SUITE_G1_SSWU_NUL: &'static [u8] = b"BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_"; -const SIG_SUITE_G2_SSWU_NUL: &'static [u8] = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_"; -const SIG_SUITE_G1_SSWU_AUG: &'static [u8] = b"BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_AUG_"; -const SIG_SUITE_G2_SSWU_AUG: &'static [u8] = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_AUG_"; -const SIG_SUITE_G1_SSWU_POP: &'static [u8] = b"BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_POP_"; -const SIG_SUITE_G2_SSWU_POP: &'static [u8] = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_"; - -#[derive(Clone, Debug)] -pub struct Bls12PrivateKey(Scalar); - -impl Bls12PrivateKey { - pub fn generate() -> Result { - let mut secret = [0u8; 64]; - OsRng.fill_bytes(&mut secret); - let inner = Scalar::from_bytes_wide(&secret); - secret.zeroize(); - Ok(Self(inner)) - } - - // FIXME - this is the draft 7 version, - // draft 10 hashes the salt and loops until a non-zero SK is found - pub fn from_seed(ikm: &[u8]) -> Result { - const SALT: &[u8] = b"BLS-SIG-KEYGEN-SALT-"; - let mut okm = GenericArray::::default(); - let mut extract = hkdf::HkdfExtract::::new(Some(SALT)); - extract.input_ikm(ikm); - extract.input_ikm(&[0u8]); - let (_, hkdf) = extract.finalize(); - hkdf.expand(&(48 as u16).to_be_bytes(), &mut okm) - .expect("HDKF extract failure"); - let inner = Scalar::from_okm(&okm); - Ok(Self(inner)) - } - - pub fn from_bytes(sk: &[u8]) -> Result { - if sk.len() != 32 { - return Err(err_msg!(InvalidKeyLength)); - } - let mut skb = [0u8; 32]; - skb.copy_from_slice(sk); - skb.reverse(); - let result: Option = Scalar::from_bytes(&skb).into(); - // turn into little-endian format - skb.zeroize(); - Ok(Self(result.ok_or_else(|| err_msg!(InvalidKeyData))?)) - } - - pub fn to_bytes(&self) -> SecretBytes { - let mut skb = self.0.to_bytes(); - // turn into big-endian format - skb.reverse(); - let v = skb.to_vec(); - skb.zeroize(); - SecretBytes::from(v) - } - - pub fn g1_public_key(&self) -> Bls12G1PublicKey { - Bls12G1PublicKey(G1Affine::from(G1Projective::generator() * self.0)) - } - - pub fn g2_public_key(&self) -> Bls12G2PublicKey { - Bls12G2PublicKey(G2Affine::from(G2Projective::generator() * self.0)) - } - - pub fn g1_sign_basic( - &self, - message: &[u8], - domain: Option<&[u8]>, - ) -> Vec { - >>::sign( - &self.0, - message, - domain.unwrap_or(SIG_SUITE_G1_SSWU_NUL), - ) - } - - pub fn g2_sign_basic( - &self, - message: &[u8], - domain: Option<&[u8]>, - ) -> Vec { - >>::sign( - &self.0, - message, - domain.unwrap_or(SIG_SUITE_G2_SSWU_NUL), - ) - } - - pub fn g1_sign_aug( - &self, - message: &[u8], - domain: Option<&[u8]>, - ) -> Vec { - >>::sign( - &self.0, - message, - domain.unwrap_or(SIG_SUITE_G1_SSWU_AUG), - ) - } - - pub fn g2_sign_aug( - &self, - message: &[u8], - domain: Option<&[u8]>, - ) -> Vec { - >>::sign( - &self.0, - message, - domain.unwrap_or(SIG_SUITE_G2_SSWU_AUG), - ) - } - - // pub fn verify(&self, message: &[u8], signature: [u8; 64]) -> bool { - // self.0.verify_strict(message, &signature.into()).is_ok() - // } -} - -trait HasSignature { - type SigPt; - type Repr: AsRef<[u8]>; - - fn public_key(sk: &Scalar) -> Self; - - fn hash_to_point(message: &[u8], domain: &[u8]) -> Self::SigPt; - - fn create_signature(sk: &Scalar, pt: &Self::SigPt) -> Self::Repr; - - fn verify_signature(&self, sig: &[u8], pt: &Self::SigPt) -> bool; -} - -impl HasSignature for G1Affine -where - G2Projective: HashToCurve, -{ - type SigPt = G2Projective; - type Repr = ::Repr; - - fn public_key(sk: &Scalar) -> Self { - G1Affine::from(G1Projective::generator() * sk) - } - - fn hash_to_point(message: &[u8], domain: &[u8]) -> Self::SigPt { - G2Projective::hash_to_curve(message, domain) - } - - fn create_signature(sk: &Scalar, pt: &Self::SigPt) -> Self::Repr { - G2Affine::from(pt * sk).to_bytes() - } - - fn verify_signature(&self, sig: &[u8], pt: &G2Projective) -> bool { - if let Some(sig) = <&[u8; 96]>::try_from(sig) - .ok() - .and_then(|arr| Option::::from(G2Affine::from_compressed(arr))) - { - pairing(self, &G2Affine::from(pt)) == pairing(&G1Affine::generator(), &sig) - } else { - false - } - } -} - -impl HasSignature for G2Affine -where - G1Projective: HashToCurve, -{ - type SigPt = G1Projective; - type Repr = ::Repr; - - fn public_key(sk: &Scalar) -> Self { - G2Affine::from(G2Projective::generator() * sk) - } - - fn hash_to_point(message: &[u8], domain: &[u8]) -> Self::SigPt { - G1Projective::hash_to_curve(message, domain) - } - - fn create_signature(sk: &Scalar, pt: &Self::SigPt) -> Self::Repr { - G1Affine::from(pt * sk).to_bytes() - } - - fn verify_signature(&self, sig: &[u8], pt: &G1Projective) -> bool { - if let Some(sig) = <&[u8; 48]>::try_from(sig) - .ok() - .and_then(|arr| Option::::from(G1Affine::from_compressed(arr))) - { - pairing(&G1Affine::from(pt), self) == pairing(&sig, &G2Affine::generator()) - } else { - false - } - } -} - -trait BlsSignBasic { - type Pk: HasSignature + GroupEncoding; - - fn sign(sk: &Scalar, message: &[u8], domain: &[u8]) -> Vec { - let pt = Self::Pk::hash_to_point(message, domain); - >::create_signature(sk, &pt) - .as_ref() - .to_vec() - } - - fn verify(pk: &Self::Pk, sig: &[u8], message: &[u8], domain: &[u8]) -> bool { - let q = Self::Pk::hash_to_point(&message, domain); - pk.verify_signature(&sig, &q) - } -} - -impl BlsSignBasic for T -where - T: HasSignature + GroupEncoding, -{ - type Pk = Self; -} - -fn augment_message(pk: &impl GroupEncoding, message: &[u8]) -> Vec { - let pk = pk.to_bytes(); - let mut ext_msg = Vec::with_capacity(pk.len() + message.len()); - ext_msg.extend_from_slice(&pk.as_ref()); - ext_msg.extend_from_slice(&message); - ext_msg -} - -trait BlsSignAug: BlsSignBasic { - fn sign(sk: &Scalar, message: &[u8], domain: &[u8]) -> Vec { - let pk = Self::Pk::public_key(sk); - let ext_msg = augment_message(&pk, message); - >::sign(sk, &ext_msg, domain) - } - - fn verify(pk: &Self::Pk, sig: &[u8], message: &[u8], domain: &[u8]) -> bool { - let ext_msg = augment_message(pk, message); - >::verify(pk, sig, &ext_msg, domain) - } -} - -impl BlsSignAug for T where - T: BlsSignBasic + PrimeCurveAffine -{ -} - -// impl KeyCapSign for Bls12PrivateKey { -// fn key_sign( -// &self, -// data: &[u8], -// sig_type: Option, -// sig_format: Option, -// ) -> Result, Error> { -// match sig_type { -// None | Some(SignatureType::Bls12_1381(G1)) => { -// let sig = self.sign(data); -// encode_signature(&sig, sig_format) -// } -// #[allow(unreachable_patterns)] -// _ => Err(err_msg!(Unsupported, "Unsupported signature type")), -// } -// } -// } - -// impl KeyCapVerify for Ed25519KeyPair { -// fn key_verify( -// &self, -// data: &[u8], -// signature: &[u8], -// sig_type: Option, -// sig_format: Option, -// ) -> Result { -// match sig_type { -// None | Some(SignatureType::EdDSA) => { -// let mut sig = [0u8; 64]; -// decode_signature(signature, &mut sig, sig_format)?; -// Ok(self.verify(data, sig)) -// } -// #[allow(unreachable_patterns)] -// _ => Err(err_msg!(Unsupported, "Unsupported signature type")), -// } -// } -// } - -// impl TryFrom<&AnyPrivateKey> for Ed25519KeyPair { -// type Error = Error; - -// fn try_from(value: &AnyPrivateKey) -> Result { -// if value.alg == KeyAlg::Ed25519 { -// Self::from_bytes(value.data.as_ref()) -// } else { -// Err(err_msg!(Unsupported, "Expected ed25519 key type")) -// } -// } -// } - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Bls12G1PublicKey(G1Affine); - -impl Bls12G1PublicKey { - // pub fn from_str(key: impl AsRef) -> Result { - // let key = key.as_ref(); - // let key = key.strip_suffix(":ed25519").unwrap_or(key); - // let mut bval = [0u8; 32]; - // bs58::decode(key) - // .into(&mut bval) - // .map_err(|_| err_msg!("Invalid base58 public key"))?; - // Self::from_bytes(bval) - // } - - // pub fn from_bytes(pk: impl AsRef<[u8]>) -> Result { - // let pk = - // PublicKey::from_bytes(pk.as_ref()).map_err(|_| err_msg!("Invalid public key bytes"))?; - // Ok(Self(pk)) - // } - - // pub fn to_base58(&self) -> String { - // bs58::encode(self.to_bytes()).into_string() - // } - - // pub fn to_string(&self) -> String { - // let mut sval = String::with_capacity(64); - // bs58::encode(self.to_bytes()).into(&mut sval).unwrap(); - // sval.push_str(":ed25519"); - // sval - // } - - // pub fn to_bytes(&self) -> [u8; 32] { - // self.0.to_bytes() - // } - - // pub fn to_jwk(&self) -> Result { - // let x = base64::encode_config(self.to_bytes(), base64::URL_SAFE_NO_PAD); - // Ok(json!({ - // "kty": "OKP", - // "crv": "Ed25519", - // "x": x, - // "key_ops": ["verify"] - // })) - // } - - pub fn verify_basic( - &self, - signature: &[u8], - message: &[u8], - domain: Option<&[u8]>, - ) -> bool { - >>::verify( - &self.0, - signature, - message, - domain.unwrap_or(SIG_SUITE_G2_SSWU_NUL), - ) - } - - pub fn verify_aug( - &self, - signature: &[u8], - message: &[u8], - domain: Option<&[u8]>, - ) -> bool { - >>::verify( - &self.0, - signature, - message, - domain.unwrap_or(SIG_SUITE_G2_SSWU_AUG), - ) - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Bls12G2PublicKey(G2Affine); - -impl Bls12G2PublicKey { - pub fn verify_basic( - &self, - signature: &[u8], - message: &[u8], - domain: Option<&[u8]>, - ) -> bool { - >>::verify( - &self.0, - signature, - message, - domain.unwrap_or(SIG_SUITE_G1_SSWU_NUL), - ) - } - - pub fn verify_aug( - &self, - signature: &[u8], - message: &[u8], - domain: Option<&[u8]>, - ) -> bool { - >>::verify( - &self.0, - signature, - message, - domain.unwrap_or(SIG_SUITE_G1_SSWU_AUG), - ) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn sig_basic_g1() { - // test vectors from https://github.com/algorand/bls_sigs_ref/pull/7/files - // First lines of test-vectors/sig_g1_basic/fips_186_3_B283 - let tests: &[(&str,&str,&str)] = &[ - ("067f27bbcecbad85277fa3629da11a24b2f19ba1e65a69d827fad430346c9d102e1b4452d04147c8133acc1e268490cd342a54065a1bd6470aabbad42fbddc54a9a76c68aceba397cb350327c5e6f5a6df0b5b5560f04700d536b384dd4b412e74fd1b8f782611e9426bf8ca77b2448d9a9f415bcfee30dda1ccb49737994f2d","0299ff06e019b5f78a1aec39706b22213abb601bd62b9979bf9bc89fb702e724e3ada994","89a67d9ba659d37eec881d172e04ae25b667dc5b98ab7967f889294e116be9089ded2b3b074ed80450735d3b2a3f237f"), - ("44adcb7e2462247b44c59608cbe228ada574ecb9f6f38baf30e42b589fb9b157bb0560e5a2aa5523b71cc0d7f583b502bec45d9b8352f29ee1842f42a17a5b16136feaa2efa4a0ae306402940ecd6b71e57d1467c98e7960de2a97f88b43487e4f4016af1292381d70c18c7e6eed99a14cdeb5b3caf73688658e4c5b54c81e08","009c2804f8cab768248fb3fff8a055b3f4585c00de5c1615a19f9425b9432ea09afba8f2","978d59bb5a235726c7fef7823130729b1d638d1d8094d45b76b8f25437d09a79c07fd41b249f046ef55bb96ef6436ddb"), - ("cffee6252c7eb6d91d8fe100a1e62f0ad9f862d78ca2b747a6c17b8c9ea8980dc239b3b673310e6e7483582399163e39d889abc1a613fe77849ebc09b4f7f4fe0688b8a9869ae918a88294c7ee199be50ee9460db14725ae70b449d0cb48f30e7d817ec02c0cd586119341dba0b74f0279330807cfccc99c8c340b72c1764a45","02e625a6bc6d0ce7c06231de827068bdb0abc8ffb57c82b35ee3a0f873b9473905974d34","8ee9e81799098380f1bb22ff1b9d625df88257033161463faa5a02fb42362d44deba1b76082f4be7e0bbefc68ff1f0e0"), - ("d058ab5dc07228253707ef224897ea0fcd09c3d5cc91fdce9e03c1c59c53fb4596be2ed929c7455e67ac7f4891aed3eb06ad88f2c4aaaabff045b959f900d1019d706b60526375851bb891494e99995928e4cd51c9616aa651ec77bd7e398916bb9ed3156391bf7fb1e29181e2b011dae2edaf803607def2ac6b194929a57f45","0376ac24e1b86f8a55c052d92a0bdc6472fa03acdcdbccbf7c321ec0ccd97aa0a66b4181","8af01de91658881604f3f759f8fe6770c315beaa84ee5b3d05242dacdad44a2b6e1008455d44f91e5ca8222aea5b10e3"), - ("c86f2cc7ab5df5cf1a236fd83792769474cef464032800ffe98a44cf29dbfb6f24088160eb31a11a382ff2a49f3e05e983462f5304272f96c0a002b69af3d233aebe867ee63fa46666760a6889d022c18645b491f8d71b6a3b6b4ef058e280cf625198715b64b025bf0449445d3dd7e1f27153926e617bd2c96638345431d1ed","02b50a6395fc02b9ac1841323de4520292f913519bc0d6a471aa28021322fc4dbcd7b802","a065875d636cb557fae8adb2b4e149f9670a9a76406d21912e73362a6d2b8e1c37078cda48555597a5348e4b952e6d3d"), - ]; - for (message, sk_hex, expect_hex) in tests { - let message = hex::decode(message).unwrap(); - let seed = hex::decode(sk_hex).unwrap(); - let sk = Bls12PrivateKey::from_seed(&seed).unwrap(); - let sig = sk.g1_sign_basic::(&message, None); - assert!(sk - .g2_public_key() - .verify_basic::(&sig, &message, None)); - assert_eq!(&hex::encode(sig), expect_hex); - } - } - - #[test] - fn sig_g2_basic() { - // test vectors from https://github.com/algorand/bls_sigs_ref/pull/7/files - // First lines of test-vectors/sig_g2_basic/fips_186_3_B283 - let tests: &[(&str,&str,&str)] = &[ - ("067f27bbcecbad85277fa3629da11a24b2f19ba1e65a69d827fad430346c9d102e1b4452d04147c8133acc1e268490cd342a54065a1bd6470aabbad42fbddc54a9a76c68aceba397cb350327c5e6f5a6df0b5b5560f04700d536b384dd4b412e74fd1b8f782611e9426bf8ca77b2448d9a9f415bcfee30dda1ccb49737994f2d","0299ff06e019b5f78a1aec39706b22213abb601bd62b9979bf9bc89fb702e724e3ada994","a9285367fc83373703663146c2a533c2ebcfdb71dda9f031bb20ca3b168908ff12fe5ae086e4e1e0f74e85cacf4f3ef20ed98849c4c8d45d0536d1759cd6208a5ba3d1966422a908ef344af4d4742b8b09fa88711b7d2c957bc073c0072ebdf7"), - ("44adcb7e2462247b44c59608cbe228ada574ecb9f6f38baf30e42b589fb9b157bb0560e5a2aa5523b71cc0d7f583b502bec45d9b8352f29ee1842f42a17a5b16136feaa2efa4a0ae306402940ecd6b71e57d1467c98e7960de2a97f88b43487e4f4016af1292381d70c18c7e6eed99a14cdeb5b3caf73688658e4c5b54c81e08","009c2804f8cab768248fb3fff8a055b3f4585c00de5c1615a19f9425b9432ea09afba8f2","a287aa25075030d5741ad7707f68ea61fe0b965bebc7bf6bff49be60c104a7abdf2de75cd53b9beb64f90acf667851a007d0547a0e25068afd3c52a45e5bebe7a4ab1a4de9dbed8a25bee03468265169512d6755bf6cc8e53b90d2bafd10934d"), - ("cffee6252c7eb6d91d8fe100a1e62f0ad9f862d78ca2b747a6c17b8c9ea8980dc239b3b673310e6e7483582399163e39d889abc1a613fe77849ebc09b4f7f4fe0688b8a9869ae918a88294c7ee199be50ee9460db14725ae70b449d0cb48f30e7d817ec02c0cd586119341dba0b74f0279330807cfccc99c8c340b72c1764a45","02e625a6bc6d0ce7c06231de827068bdb0abc8ffb57c82b35ee3a0f873b9473905974d34","b90b74be15b24343c7b0d8647ea4625bdba477fdee5b28e847ab451f19bba7b93abfb14a6fa3394dadc055e59bcb1fba056f721c4af27b6870371dde1750646e1a29194956b41a711c363a0fdc66609cdf812ec1e2a8723fe87acd1bae751704"), - ("d058ab5dc07228253707ef224897ea0fcd09c3d5cc91fdce9e03c1c59c53fb4596be2ed929c7455e67ac7f4891aed3eb06ad88f2c4aaaabff045b959f900d1019d706b60526375851bb891494e99995928e4cd51c9616aa651ec77bd7e398916bb9ed3156391bf7fb1e29181e2b011dae2edaf803607def2ac6b194929a57f45","0376ac24e1b86f8a55c052d92a0bdc6472fa03acdcdbccbf7c321ec0ccd97aa0a66b4181","803e8f4be33388a272ff3a0f80986f7edd9ef35505d6754c2a63ca6a9296e0576f0b069b6761e9ae95006262f847e33b0cc660a0f0a05d19032e1a15d61c524da28869aa7aee963b4c35eaa8b2d4d3e4eeaa361619e8be911f97c5ef8c69df27"), - ("c86f2cc7ab5df5cf1a236fd83792769474cef464032800ffe98a44cf29dbfb6f24088160eb31a11a382ff2a49f3e05e983462f5304272f96c0a002b69af3d233aebe867ee63fa46666760a6889d022c18645b491f8d71b6a3b6b4ef058e280cf625198715b64b025bf0449445d3dd7e1f27153926e617bd2c96638345431d1ed","02b50a6395fc02b9ac1841323de4520292f913519bc0d6a471aa28021322fc4dbcd7b802","8f8b7a649bb0eb1341eb9de65e94d9b3eade5173604a6b538771308d28d55d05615eb9dfd34a9e70dcf7f92c672854fa062d66edd7cb8b5391b799f2d4cf5cbd9555b61f26f4ab62b3589a0c4d9ee050597798cf7d6403a63403729de1b82e58"), - ]; - for (message, sk_hex, expect_hex) in tests { - let message = hex::decode(message).unwrap(); - let seed = hex::decode(sk_hex).unwrap(); - let sk = Bls12PrivateKey::from_seed(&seed).unwrap(); - let sig = sk.g2_sign_basic::(&message, None); - assert!(sk - .g1_public_key() - .verify_basic::(&sig, &message, None)); - assert_eq!(&hex::encode(sig), expect_hex); - } - } - - #[test] - fn sig_g1_aug() { - // test vectors from https://github.com/algorand/bls_sigs_ref/pull/7/files - // First lines of test-vectors/sig_g1_aug/fips_186_3_B283 - let tests: &[(&str,&str,&str)] = &[ - ("067f27bbcecbad85277fa3629da11a24b2f19ba1e65a69d827fad430346c9d102e1b4452d04147c8133acc1e268490cd342a54065a1bd6470aabbad42fbddc54a9a76c68aceba397cb350327c5e6f5a6df0b5b5560f04700d536b384dd4b412e74fd1b8f782611e9426bf8ca77b2448d9a9f415bcfee30dda1ccb49737994f2d","0299ff06e019b5f78a1aec39706b22213abb601bd62b9979bf9bc89fb702e724e3ada994","aaa1ba14a100902b89aa04239faadd48be036d118acbbc12fd7847bfc492534b77ded3b05fbf0a9fc863f77a2fed548d"), - ("44adcb7e2462247b44c59608cbe228ada574ecb9f6f38baf30e42b589fb9b157bb0560e5a2aa5523b71cc0d7f583b502bec45d9b8352f29ee1842f42a17a5b16136feaa2efa4a0ae306402940ecd6b71e57d1467c98e7960de2a97f88b43487e4f4016af1292381d70c18c7e6eed99a14cdeb5b3caf73688658e4c5b54c81e08","009c2804f8cab768248fb3fff8a055b3f4585c00de5c1615a19f9425b9432ea09afba8f2","a648266d72c8c47293dda9ecefb9f7ec4eff5b1a7676b87b03e9cf3ab41cc985f787ce25c55c87a2d782f8827f46a3d4"), - ("cffee6252c7eb6d91d8fe100a1e62f0ad9f862d78ca2b747a6c17b8c9ea8980dc239b3b673310e6e7483582399163e39d889abc1a613fe77849ebc09b4f7f4fe0688b8a9869ae918a88294c7ee199be50ee9460db14725ae70b449d0cb48f30e7d817ec02c0cd586119341dba0b74f0279330807cfccc99c8c340b72c1764a45","02e625a6bc6d0ce7c06231de827068bdb0abc8ffb57c82b35ee3a0f873b9473905974d34","a75a5dbdf03daba8467d50ad2af6b400becc3bcdcd89a72d8da94c5d8f44d3497758bcd58995e10570425cda4f58ee56"), - ("d058ab5dc07228253707ef224897ea0fcd09c3d5cc91fdce9e03c1c59c53fb4596be2ed929c7455e67ac7f4891aed3eb06ad88f2c4aaaabff045b959f900d1019d706b60526375851bb891494e99995928e4cd51c9616aa651ec77bd7e398916bb9ed3156391bf7fb1e29181e2b011dae2edaf803607def2ac6b194929a57f45","0376ac24e1b86f8a55c052d92a0bdc6472fa03acdcdbccbf7c321ec0ccd97aa0a66b4181","ad256fbe6c2634f0ff3e374e8f28ea6784c57b458942b939972c4fa7cb2ea930d237b65a5b5be4ef81855a8e1fca4a06"), - ("c86f2cc7ab5df5cf1a236fd83792769474cef464032800ffe98a44cf29dbfb6f24088160eb31a11a382ff2a49f3e05e983462f5304272f96c0a002b69af3d233aebe867ee63fa46666760a6889d022c18645b491f8d71b6a3b6b4ef058e280cf625198715b64b025bf0449445d3dd7e1f27153926e617bd2c96638345431d1ed","02b50a6395fc02b9ac1841323de4520292f913519bc0d6a471aa28021322fc4dbcd7b802","a676ae116b734121d9a0263a37da6bdc63b9cd60a97e98e45570ae2433a75ac323b396ec5a188fda20e86992301d28ed"), - ]; - for (message, sk_hex, expect_hex) in tests { - let message = hex::decode(message).unwrap(); - let seed = hex::decode(sk_hex).unwrap(); - let sk = Bls12PrivateKey::from_seed(&seed).unwrap(); - let sig = sk.g1_sign_aug::(&message, None); - assert!(sk - .g2_public_key() - .verify_aug::(&sig, &message, None)); - assert_eq!(&hex::encode(sig), expect_hex); - } - } - - #[test] - fn sig_g2_aug() { - // test vectors from https://github.com/algorand/bls_sigs_ref/pull/7/files - // First lines of test-vectors/sig_g2_aug/fips_186_3_B283 - let tests: &[(&str,&str,&str)] = &[ - ("067f27bbcecbad85277fa3629da11a24b2f19ba1e65a69d827fad430346c9d102e1b4452d04147c8133acc1e268490cd342a54065a1bd6470aabbad42fbddc54a9a76c68aceba397cb350327c5e6f5a6df0b5b5560f04700d536b384dd4b412e74fd1b8f782611e9426bf8ca77b2448d9a9f415bcfee30dda1ccb49737994f2d","0299ff06e019b5f78a1aec39706b22213abb601bd62b9979bf9bc89fb702e724e3ada994","905c02745f0f549b3c6e2c4f246a8f65a4062f8e3d82dc32a9c97607eac2ad749c01083e3e13cd573e8229e227d798861400b9ad2cfc91126059c1a097ae9762791e4940c5f11a686250f83565e78a111f2977bfad30ac15b5dc4828b34eb9d0"), - ("44adcb7e2462247b44c59608cbe228ada574ecb9f6f38baf30e42b589fb9b157bb0560e5a2aa5523b71cc0d7f583b502bec45d9b8352f29ee1842f42a17a5b16136feaa2efa4a0ae306402940ecd6b71e57d1467c98e7960de2a97f88b43487e4f4016af1292381d70c18c7e6eed99a14cdeb5b3caf73688658e4c5b54c81e08","009c2804f8cab768248fb3fff8a055b3f4585c00de5c1615a19f9425b9432ea09afba8f2","aa16169aaaf4ea98f5a20d132dce6388f533c7f01ff7aaf015c9b78f6e1072a5642b3192119d7f169caca757e923b44b0381bf45909591455b750356fcdaba451f4293090322e5ddc9a3b17b91ed2550ec983314b7cc9f4f762dd57a07a8d4af"), - ("cffee6252c7eb6d91d8fe100a1e62f0ad9f862d78ca2b747a6c17b8c9ea8980dc239b3b673310e6e7483582399163e39d889abc1a613fe77849ebc09b4f7f4fe0688b8a9869ae918a88294c7ee199be50ee9460db14725ae70b449d0cb48f30e7d817ec02c0cd586119341dba0b74f0279330807cfccc99c8c340b72c1764a45","02e625a6bc6d0ce7c06231de827068bdb0abc8ffb57c82b35ee3a0f873b9473905974d34","a1abbf78718265c73643ac29aa823c8a11b02c34562c2b06a4a449fd964e19c57341d90e44f2db2849bea7d06a32b5bf114c80312057dd9eb3f46b8466233c52abbd6131314f1d5ec8aa277b58bbd09cedcf15d98dac3f723c534d56c9fef01d"), - ("d058ab5dc07228253707ef224897ea0fcd09c3d5cc91fdce9e03c1c59c53fb4596be2ed929c7455e67ac7f4891aed3eb06ad88f2c4aaaabff045b959f900d1019d706b60526375851bb891494e99995928e4cd51c9616aa651ec77bd7e398916bb9ed3156391bf7fb1e29181e2b011dae2edaf803607def2ac6b194929a57f45","0376ac24e1b86f8a55c052d92a0bdc6472fa03acdcdbccbf7c321ec0ccd97aa0a66b4181","a00990857bc78b952f7e1b8279de59ecad9b24367b6b03fa5d1d3bbf70d06e8895802faaefa41b514f3dae706d305a86048cd87a3d92da71979f220f4f6649fb6696804f581867b46961b1b10d368a4656386d6f0b52340902bd292157358445"), - ("c86f2cc7ab5df5cf1a236fd83792769474cef464032800ffe98a44cf29dbfb6f24088160eb31a11a382ff2a49f3e05e983462f5304272f96c0a002b69af3d233aebe867ee63fa46666760a6889d022c18645b491f8d71b6a3b6b4ef058e280cf625198715b64b025bf0449445d3dd7e1f27153926e617bd2c96638345431d1ed","02b50a6395fc02b9ac1841323de4520292f913519bc0d6a471aa28021322fc4dbcd7b802","91f183d0c29ccf5cd8116ace89605a7ae0f4b6e980060dce7e87aa364b04506f59679e0c9d3305a8869ff067e39265780442756b48bcfc07eb5dd289b0fd4062e54d30cf0b4f9a5dd9dae41e7b2cb916266cd88e7c3380c638e9704a58ad025f"), - ]; - for (message, sk_hex, expect_hex) in tests { - let message = hex::decode(message).unwrap(); - let seed = hex::decode(sk_hex).unwrap(); - let sk = Bls12PrivateKey::from_seed(&seed).unwrap(); - let sig = sk.g2_sign_aug::(&message, None); - assert!(sk - .g1_public_key() - .verify_aug::(&sig, &message, None)); - assert_eq!(&hex::encode(sig), expect_hex); - } - } -} diff --git a/askar-crypto/src/alg/mod.rs b/askar-crypto/src/alg/mod.rs index 4954de33..71b39815 100644 --- a/askar-crypto/src/alg/mod.rs +++ b/askar-crypto/src/alg/mod.rs @@ -18,8 +18,6 @@ mod any; #[cfg_attr(docsrs, doc(cfg(feature = "any_key")))] pub use any::{AnyKey, AnyKeyCreate}; -// pub mod bls; - pub mod aesgcm; pub mod chacha20; @@ -44,8 +42,6 @@ pub enum KeyAlg { X25519, /// Elliptic Curve key for signing or key exchange EcCurve(EcCurves), - // /// BLS12-1381 signing key in group G1 or G2 - // BLS12_1381(BlsGroup), } impl KeyAlg { @@ -161,15 +157,6 @@ impl Display for KeyAlg { } } -/// Supported BLS12-381 groups -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Zeroize)] -pub enum BlsGroups { - /// A key or signature represented by an element from the BLS12-381 G1 group - G1, - /// A key or signature represented by an element from the BLS12-381 G2 group - G2, -} - /// Supported algorithms for AES #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Zeroize)] pub enum AesTypes { diff --git a/askar-crypto/src/sign.rs b/askar-crypto/src/sign.rs index 9035f70f..abdd6b41 100644 --- a/askar-crypto/src/sign.rs +++ b/askar-crypto/src/sign.rs @@ -47,7 +47,6 @@ pub trait KeySigVerify { /// Supported signature types #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum SignatureType { - // Bls12_1381(BlsGroups), /// Standard signature output for ed25519 EdDSA, /// Elliptic curve DSA using P-256 and SHA-256 @@ -73,11 +72,7 @@ impl SignatureType { /// Get the length of the signature output. pub const fn signature_length(&self) -> usize { match self { - // Self::Bls12_1381(BlsGroups::G1) => 48, - // Self::Bls12_1381(BlsGroups::G2) => 96, - Self::EdDSA => 64, - Self::ES256 => 64, - Self::ES256K => 64, + Self::EdDSA | Self::ES256 | Self::ES256K => 64, } } } From 4912f33c79b5600612537d73be00b67747ba04e7 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Wed, 28 Apr 2021 22:39:51 -0700 Subject: [PATCH 078/116] remove indy_compat Signed-off-by: Andrew Whitehead --- src/indy_compat/mod.rs | 201 ----------------------------------------- src/lib.rs | 5 - 2 files changed, 206 deletions(-) delete mode 100644 src/indy_compat/mod.rs diff --git a/src/indy_compat/mod.rs b/src/indy_compat/mod.rs deleted file mode 100644 index ce776fc8..00000000 --- a/src/indy_compat/mod.rs +++ /dev/null @@ -1,201 +0,0 @@ -use std::collections::{BTreeMap, HashMap}; -use std::io::{stdout, Write}; - -use futures_lite::stream::StreamExt; -use indy_utils::base58; -use itertools::Itertools; -use serde::Serialize; -use sqlx::{sqlite::SqliteRow as DbRow, Row, SqlitePool as DbPool}; - -use super::{ - error::Result, - keys::kdf::argon2::Level, - keys::store::{decrypt, EncKey, HmacKey, StoreKey}, - types::{Entry, EntryTag}, -}; - -const CHUNK_SIZE: usize = 20; - -#[derive(Debug, Serialize)] -struct PrintEntry { - category: String, - name: String, - value: String, - tags: HashMap, -} - -impl PrintEntry { - pub fn new(entry: Entry) -> Self { - let value = String::from_utf8(entry.value.to_vec()).expect("Error parsing value as utf-8"); - let mut tags = HashMap::new(); - if let Some(entry_tags) = entry.tags { - for tag in entry_tags { - match tag { - EntryTag::Encrypted(name, value) => { - tags.insert(name, value); - } - EntryTag::Plaintext(name, value) => { - tags.insert(format!("~{}", name), value); - } - } - } - } - Self { - category: entry.category, - name: entry.name, - value, - tags, - } - } -} - -// test method for dumping the contents of the wallet -pub async fn print_records<'a>(path: &str, password: &str) -> Result<()> { - let pool = DbPool::connect(path).await?; - - let wallet_key = { - let metadata = sqlx::query("SELECT value from metadata") - .fetch_one(&pool) - .await?; - let enc_key = metadata.try_get(0)?; - decode_wallet_key(enc_key, &password)? - }; - - let tag_q = format!( - "SELECT * FROM (SELECT 1 as encrypted, item_id, name, value FROM tags_encrypted - UNION SELECT 0 as encrypted, item_id, name, value FROM tags_plaintext) - WHERE item_id IN ({})", - std::iter::repeat("?") - .take(CHUNK_SIZE) - .intersperse(", ") - .collect::() - ); - - let mut rows = sqlx::query("SELECT id, type, name, value, key FROM items").fetch(&pool); - let mut done = false; - let mut chunk = Vec::with_capacity(CHUNK_SIZE); - let mut ids = Vec::with_capacity(CHUNK_SIZE); - let mut writer = stdout(); - - while !done { - chunk.clear(); - ids.clear(); - let mut tag_query = sqlx::query(&tag_q); - for idx in 0..CHUNK_SIZE { - if let Some(enc_row) = rows.next().await { - let (row_id, row) = decode_row(&wallet_key, enc_row?)?; - chunk.push(row); - ids.push(row_id); - tag_query = tag_query.bind(row_id); - } else { - for _ in idx..CHUNK_SIZE { - tag_query = tag_query.bind(0); - } - done = true; - break; - } - } - - let mut tags = collect_tags(&wallet_key, tag_query.fetch_all(&pool).await?)?; - for (idx, id) in ids.iter().enumerate() { - chunk[idx].tags = tags.remove(id); - } - for entry in chunk.drain(..) { - serde_json::to_writer_pretty(&writer, &PrintEntry::new(entry)).unwrap(); - writer.write(b"\n").unwrap(); - } - } - drop(rows); - - pool.close().await; - Ok(()) -} - -#[inline] -fn get_slice<'a>(row: &'a DbRow, index: usize) -> Result<&'a [u8]> { - row.try_get(index) - .map_err(err_map!(Unexpected, "Error fetching column")) -} - -fn decode_row(key: &StoreKey, row: DbRow) -> Result<(i64, Entry)> { - let value_key_enc = get_slice(&row, 4)?; - let value_key = EncKey::from_slice(decrypt(&key.value_key, value_key_enc)?); - let value = decrypt(&value_key, get_slice(&row, 3)?)?; - - let entry = Entry::new( - decode_utf8(key.decrypt_category(get_slice(&row, 1)?)?)?, - decode_utf8(key.decrypt_name(get_slice(&row, 2)?)?)?, - value, - None, - ); - Ok((row.try_get(0)?, entry)) -} - -fn collect_tags(key: &StoreKey, tags: Vec) -> Result>> { - let mut result = BTreeMap::new(); - for row in tags { - let entry = result.entry(row.try_get(1)?).or_insert_with(Vec::new); - let name = decode_utf8(key.decrypt_tag_name(get_slice(&row, 2)?)?)?; - if row.try_get(0)? { - // encrypted value - let value = decode_utf8(key.decrypt_tag_value(get_slice(&row, 3)?)?)?; - entry.push(EntryTag::Encrypted(name, value)) - } else { - let value = decode_utf8(get_slice(&row, 3)?.to_vec())?; - entry.push(EntryTag::Plaintext(name, value)); - }; - } - Ok(result) -} - -#[derive(Deserialize, Debug)] -struct EncStorageKey { - keys: Vec, - master_key_salt: Vec, -} - -pub fn decode_wallet_key(enc_key: &[u8], password: &str) -> Result { - let key = - serde_json::from_slice::(enc_key).map_err(err_map!("Invalid wallet key"))?; - - let keys = decrypt_key(key, password)?; - let data = rmp_serde::from_slice::<[serde_bytes::ByteBuf; 7]>(keys.as_slice()).unwrap(); - let wallet_key = StoreKey { - category_key: EncKey::from_slice(&data[0]), - name_key: EncKey::from_slice(&data[1]), - value_key: EncKey::from_slice(&data[2]), - item_hmac_key: HmacKey::from_slice(&data[3]), - tag_name_key: EncKey::from_slice(&data[4]), - tag_value_key: EncKey::from_slice(&data[5]), - tags_hmac_key: HmacKey::from_slice(&data[6]), - }; - - Ok(wallet_key) -} - -fn decrypt_key(key: EncStorageKey, password: &str) -> Result> { - // check for a raw key in base58 format - if let Ok(raw_key) = base58::decode(password) { - if raw_key.len() == 32 { - let master_key = EncKey::from_slice(&raw_key); - return Ok(decrypt(&master_key, key.keys.as_slice())?); - } - } - - let salt = &key.master_key_salt[..16]; - - // derive key with libsodium 'moderate' settings - let master_key = Level::Moderate.derive_key(salt, password)?; - if let Ok(keys) = decrypt(&master_key, key.keys.as_slice()) { - Ok(keys) - } else { - // derive key with libsodium 'interactive' settings - let master_key = Level::Interactive.derive_key(salt, password)?; - Ok(decrypt(&master_key, key.keys.as_slice())?) - } -} - -#[inline] -fn decode_utf8(value: Vec) -> Result { - String::from_utf8(value).map_err(err_map!(Encryption)) -} diff --git a/src/lib.rs b/src/lib.rs index cfb1c2f4..0d63bbe0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,11 +38,6 @@ pub use askar_crypto as crypto; #[doc(hidden)] pub mod future; -#[cfg(feature = "indy_compat")] -#[cfg_attr(docsrs, doc(cfg(feature = "indy_compat")))] -/// Indy wallet compatibility support -pub mod indy_compat; - #[cfg(feature = "ffi")] #[macro_use] extern crate serde_json; From 2a4c5dd50b55298a97183629fe3b0ad9971ffeca Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Wed, 28 Apr 2021 22:40:50 -0700 Subject: [PATCH 079/116] clean up dependencies Signed-off-by: Andrew Whitehead --- askar-crypto/Cargo.toml | 6 +----- askar-crypto/src/random.rs | 15 +++++++++------ 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/askar-crypto/Cargo.toml b/askar-crypto/Cargo.toml index 335adc80..9499c8fd 100644 --- a/askar-crypto/Cargo.toml +++ b/askar-crypto/Cargo.toml @@ -40,17 +40,13 @@ aes-gcm = { version = "0.8", default-features = false, features = ["aes"] } argon2 = { version = "0.1", default-features = false, features = ["password-hash"] } base64 = { version = "0.13", default-features = false } blake2 = { version = "0.9", default-features = false } -# bls12_381 = { version = "0.4.0", path = "../../bls12_381" } -bs58 = { version = "0.4", default-features = false } -chacha20 = "0.6" # should match version from chacha20poly1305 +chacha20 = "*" # inherit from chacha20poly1305 chacha20poly1305 = { version = "0.7", default-features = false, features = ["chacha20"] } crypto_box = { version = "0.5", default-features = false, features = ["u64_backend"] } curve25519-dalek = { version = "3.0", default-features = false, features = ["u64_backend"] } ed25519-dalek = { version = "1.0", default-features = false, features = ["u64_backend"] } digest = "0.9" group = "0.9" -hex = { version = "0.4", default-features = false } -hkdf = "0.10" k256 = { version = "0.7", default-features = false, features = ["arithmetic", "ecdsa", "ecdh", "sha256", "zeroize"] } p256 = { version = "0.7", default-features = false, features = ["arithmetic", "ecdsa", "ecdh", "zeroize"] } rand = { version = "0.7", default-features = false, features = ["getrandom"] } diff --git a/askar-crypto/src/random.rs b/askar-crypto/src/random.rs index d3cc9421..f311e753 100644 --- a/askar-crypto/src/random.rs +++ b/askar-crypto/src/random.rs @@ -5,7 +5,7 @@ use chacha20::{ cipher::{NewStreamCipher, SyncStreamCipher}, ChaCha20, }; -use rand::{rngs::OsRng, RngCore}; +use rand::{CryptoRng, RngCore}; #[cfg(feature = "alloc")] use crate::buffer::SecretBytes; @@ -14,14 +14,17 @@ use crate::error::Error; /// The expected length of a seed for `fill_random_deterministic` pub const DETERMINISTIC_SEED_LENGTH: usize = ::KeySize::USIZE; -/// The type of the standard random number generator -pub type StdRng = OsRng; +/// Combined trait for CryptoRng and RngCore +pub trait Rng: CryptoRng + RngCore {} + +impl Rng for T {} /// Perform an operation with a reference to the random number generator #[inline(always)] -pub fn with_rng(f: impl FnOnce(&mut StdRng) -> O) -> O { - // may need to substitute another RNG depending on the platform - f(&mut OsRng) +pub fn with_rng(f: impl FnOnce(&mut dyn Rng) -> O) -> O { + // FIXME may wish to support platforms without 'getrandom' by adding + // a method to initialize with a custom RNG (or fill_bytes function) + f(&mut ::rand::rngs::OsRng) } /// Fill a mutable slice with random data using the From c44977a0ecdb3c37b0ac2ff87cedccbc8505e7f7 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Thu, 29 Apr 2021 15:05:26 -0700 Subject: [PATCH 080/116] match version Signed-off-by: Andrew Whitehead --- askar-crypto/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/askar-crypto/Cargo.toml b/askar-crypto/Cargo.toml index 9499c8fd..24121e8c 100644 --- a/askar-crypto/Cargo.toml +++ b/askar-crypto/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "askar-crypto" -version = "0.1.0" +version = "0.2.0" authors = ["Hyperledger Aries Contributors "] edition = "2018" description = "Hyperledger Aries Askar cryptography" From d59d27712f388163f1aefac035de8bb0c16474ed Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Thu, 29 Apr 2021 15:06:03 -0700 Subject: [PATCH 081/116] use new indy-wql package; switch to tokio Signed-off-by: Andrew Whitehead --- Cargo.toml | 17 +++++---------- src/backend/postgres/test_db.rs | 13 +++++------- src/error.rs | 26 ----------------------- src/ffi/mod.rs | 37 --------------------------------- src/future.rs | 32 ++++++++++++++++++++-------- src/storage/wql/mod.rs | 2 +- src/storage/wql/tags.rs | 4 ++-- 7 files changed, 36 insertions(+), 95 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2a1cf736..e20c3201 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,6 @@ default = ["all_backends", "ffi", "logger"] all_backends = ["any", "postgres", "sqlite"] any = [] ffi = ["any", "ffi-support", "logger"] -indy_compat = ["sqlx", "sqlx/sqlite"] jemalloc = ["jemallocator"] logger = ["env_logger", "log"] postgres = ["sqlx", "sqlx/postgres", "sqlx/tls"] @@ -39,12 +38,9 @@ pg_test = ["postgres"] hex-literal = "0.3" [dependencies] -askar-crypto = { path = "./askar-crypto", features = ["std"] } -async-global-executor = { version = "1.4", features = ["async-io"] } +askar-crypto = { version = "0.2", path = "./askar-crypto", features = ["std"] } async-mutex = "1.4" async-stream = "0.3" -async-std = "1.7" # temporary addition to encourage common dependencies with sqlx -blocking = "1.0" bs58 = "0.4" chrono = "0.4" digest = "0.9" @@ -53,6 +49,7 @@ ffi-support = { version = "0.4", optional = true } futures-lite = "1.7" hex = "0.4" hmac = "0.10" +indy-wql = "0.4" itertools = "0.9" jemallocator = { version = "0.3", optional = true } log = { version = "0.4", optional = true } @@ -64,19 +61,15 @@ serde_bytes = "0.11" serde_cbor = "0.11" serde_json = "1.0" sha2 = "0.9" +tokio = { version = "1.5", features = ["time"] } url = { version = "2.1", default-features = false } uuid = { version = "0.8", features = ["v4"] } -zeroize = { version = "1.1.0", features = ["zeroize_derive"] } - -[dependencies.indy-utils] -version = "=0.3.9" -default-features = false -features = ["serde_support", "wql"] +zeroize = "1.1" [dependencies.sqlx] version = "=0.5.1" default-features = false -features = ["chrono", "runtime-async-std-rustls"] +features = ["chrono", "runtime-tokio-rustls"] optional = true [profile.release] diff --git a/src/backend/postgres/test_db.rs b/src/backend/postgres/test_db.rs index ecefd69b..c719f260 100644 --- a/src/backend/postgres/test_db.rs +++ b/src/backend/postgres/test_db.rs @@ -11,7 +11,7 @@ use super::PostgresStore; use crate::{ backend::db_utils::{init_keys, random_profile_name}, error::Error, - future::{block_on, unblock}, + future::{block_on, sleep, timeout, unblock}, protect::{generate_raw_store_key, KeyCache, StoreKeyMethod}, storage::Store, }; @@ -55,7 +55,7 @@ impl TestDB { break lock_txn; } lock_txn.close().await?; - async_std::task::sleep(Duration::from_millis(50)).await; + sleep(Duration::from_millis(50)).await; }; let mut init_txn = conn_pool.begin().await?; @@ -97,12 +97,9 @@ impl Drop for TestDB { block_on(lock_txn.close()).expect("Error closing database connection"); } if let Some(inst) = self.inst.take() { - block_on(async_std::future::timeout( - Duration::from_secs(30), - inst.close(), - )) - .expect("Timed out waiting for the pool connection to close") - .expect("Error closing connection pool"); + block_on(timeout(Duration::from_secs(30), inst.close())) + .expect("Timed out waiting for the pool connection to close") + .expect("Error closing connection pool"); } } } diff --git a/src/error.rs b/src/error.rs index a9925f3e..9990819b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -70,14 +70,6 @@ impl Error { } } - pub(crate) fn from_opt_msg>(kind: ErrorKind, msg: Option) -> Self { - Self { - kind, - cause: None, - message: msg.map(Into::into), - } - } - /// Accessor for the error kind pub fn kind(&self) -> ErrorKind { self.kind @@ -141,24 +133,6 @@ impl From for Error { } } -impl From for Error { - fn from(err: indy_utils::EncryptionError) -> Self { - Error::from_opt_msg(ErrorKind::Encryption, err.context) - } -} - -impl From for Error { - fn from(err: indy_utils::UnexpectedError) -> Self { - Error::from_opt_msg(ErrorKind::Unexpected, err.context) - } -} - -impl From for Error { - fn from(err: indy_utils::ValidationError) -> Self { - Error::from_opt_msg(ErrorKind::Input, err.context) - } -} - impl From for Error { fn from(err: CryptoError) -> Self { let kind = match err.kind() { diff --git a/src/ffi/mod.rs b/src/ffi/mod.rs index 5637b76a..516f152b 100644 --- a/src/ffi/mod.rs +++ b/src/ffi/mod.rs @@ -16,15 +16,10 @@ mod handle; mod macros; mod error; - mod key; - mod log; - mod result_list; - mod secret; - mod store; use self::error::ErrorCode; @@ -60,38 +55,6 @@ impl)> Drop for EnsureCallback { } } -// #[no_mangle] -// pub extern "C" fn askar_verify_signature( -// signer_vk: FfiStr<'_>, -// message: ByteBuffer, -// signature: ByteBuffer, -// cb: Option, -// cb_id: CallbackId, -// ) -> ErrorCode { -// catch_err! { -// trace!("Verify signature"); -// let signer_vk = signer_vk.into_opt_string().ok_or_else(|| err_msg!("Signer verkey not provided"))?; -// let message = message.as_slice().to_vec(); -// let signature = signature.as_slice().to_vec(); -// let cb = cb.ok_or_else(|| err_msg!("No callback provided"))?; -// let cb = EnsureCallback::new(move |result| -// match result { -// Ok(verify) => cb(cb_id, ErrorCode::Success, verify as i8), -// Err(err) => cb(cb_id, set_last_error(Some(err)), 0), -// } -// ); -// spawn_ok(async move { -// let result = unblock(move || verify_signature( -// &signer_vk, -// &message, -// &signature -// )).await; -// cb.resolve(result); -// }); -// Ok(ErrorCode::Success) -// } -// } - #[no_mangle] pub extern "C" fn askar_version() -> *mut c_char { rust_string_to_c(LIB_VERSION.to_owned()) diff --git a/src/future.rs b/src/future.rs index e79e07c7..0aace01d 100644 --- a/src/future.rs +++ b/src/future.rs @@ -1,13 +1,15 @@ -use std::future::Future; -use std::pin::Pin; +use std::{future::Future, pin::Pin, time::Duration}; -pub use async_global_executor::block_on; -// use once_cell::sync::Lazy; -// use suspend_exec::ThreadPool; +use once_cell::sync::Lazy; +use tokio::runtime::Runtime; pub type BoxFuture<'a, T> = Pin + Send + 'a>>; -// pub static THREAD_POOL: Lazy = Lazy::new(ThreadPool::default); +static RUNTIME: Lazy = Lazy::new(|| Runtime::new().expect("Error creating tokio runtime")); + +pub fn block_on(f: impl Future) -> R { + RUNTIME.block_on(f) +} #[inline] pub async fn unblock(f: F) -> T @@ -15,11 +17,23 @@ where T: Send + 'static, F: FnOnce() -> T + Send + 'static, { - // THREAD_POOL.run(f).await.unwrap() - blocking::unblock(f).await + RUNTIME + .spawn_blocking(f) + .await + .expect("Error running blocking task") } #[inline] pub fn spawn_ok(fut: impl Future + Send + 'static) { - async_global_executor::spawn(fut).detach(); + RUNTIME.spawn(fut); +} + +pub async fn sleep(dur: Duration) { + let _rt = RUNTIME.enter(); + tokio::time::sleep(dur).await +} + +pub async fn timeout(dur: Duration, f: impl Future) -> Option { + let _rt = RUNTIME.enter(); + tokio::time::timeout(dur, f).await.ok() } diff --git a/src/storage/wql/mod.rs b/src/storage/wql/mod.rs index 4b007b19..a34d1a49 100644 --- a/src/storage/wql/mod.rs +++ b/src/storage/wql/mod.rs @@ -1,4 +1,4 @@ -pub use indy_utils::wql::{AbstractQuery, Query}; +pub use indy_wql::{AbstractQuery, Query}; pub mod sql; pub mod tags; diff --git a/src/storage/wql/tags.rs b/src/storage/wql/tags.rs index 43909124..35bdbf3c 100644 --- a/src/storage/wql/tags.rs +++ b/src/storage/wql/tags.rs @@ -5,9 +5,9 @@ pub type TagQuery = AbstractQuery; pub fn tag_query(query: Query) -> Result { let result = query - .map_names(&mut |k| { + .map_names(|k| { if k.starts_with("~") { - Ok(TagName::Plaintext(k[1..].to_string())) + Result::<_, ()>::Ok(TagName::Plaintext(k[1..].to_string())) } else { Ok(TagName::Encrypted(k)) } From fcb75c13458541073721182da2b350da4882e66d Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Thu, 29 Apr 2021 15:17:53 -0700 Subject: [PATCH 082/116] fix warning Signed-off-by: Andrew Whitehead --- src/storage/wql/sql.rs | 4 +--- src/storage/wql/tags.rs | 17 ++++++----------- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/src/storage/wql/sql.rs b/src/storage/wql/sql.rs index e429019e..3d548c52 100644 --- a/src/storage/wql/sql.rs +++ b/src/storage/wql/sql.rs @@ -94,9 +94,7 @@ where is_plaintext: bool, negate: bool, ) -> Result, Error> { - let args_in = std::iter::repeat("$$") - .take(enc_values.len()) - .intersperse(", ") + let args_in = Itertools::intersperse(std::iter::repeat("$$").take(enc_values.len()), ", ") .collect::(); let query = format!( "i.id IN (SELECT item_id FROM items_tags WHERE name = $$ AND value {} ({}) AND plaintext = {})", diff --git a/src/storage/wql/tags.rs b/src/storage/wql/tags.rs index 35bdbf3c..f877f4de 100644 --- a/src/storage/wql/tags.rs +++ b/src/storage/wql/tags.rs @@ -331,11 +331,8 @@ mod tests { negate: bool, ) -> Result, Error> { let op = if negate { "NOT IN" } else { "IN" }; - let value = values - .iter() - .map(|v| v.as_str()) - .intersperse(", ") - .collect::(); + let value = + Itertools::intersperse(values.iter().map(|v| v.as_str()), ", ").collect::(); Ok(Some(format!("{} {} ({})", name, op, value))) } @@ -346,12 +343,10 @@ mod tests { ) -> Result, Error> { let mut r = String::new(); r.push_str("("); - r.extend( - clauses - .iter() - .map(String::as_str) - .intersperse(op.as_sql_str()), - ); + r.extend(Itertools::intersperse( + clauses.iter().map(String::as_str), + op.as_sql_str(), + )); r.push_str(")"); Ok(Some(r)) } From b3ed6ea7957fd1ca5b3563b72a7931612e7f0c68 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Thu, 29 Apr 2021 15:21:15 -0700 Subject: [PATCH 083/116] remove faber test Signed-off-by: Andrew Whitehead --- Cargo.toml | 4 ---- tests/faber.agent372766/sqlite.db | Bin 77824 -> 0 bytes tests/faber.rs | 9 --------- 3 files changed, 13 deletions(-) delete mode 100644 tests/faber.agent372766/sqlite.db delete mode 100644 tests/faber.rs diff --git a/Cargo.toml b/Cargo.toml index e20c3201..7a77786a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,7 +78,3 @@ codegen-units = 1 [[test]] name = "backends" - -[[test]] -name = "faber" -required-features = ["indy_compat"] diff --git a/tests/faber.agent372766/sqlite.db b/tests/faber.agent372766/sqlite.db deleted file mode 100644 index 76061fd4de2edb6c756e66fcb6c7c24c971ea3b7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 77824 zcmeFa1z1&Wy70e&O>AltN_UsQrc?v70eNQfX^0)jzGm(r!QG)OlHhze5j zU)z~^&zUoGzIos8KQrgMzHhI~z2b>Ge)qi|_VX+jYTr|J@Uo$^b@gzz@S@`f5r9xp zL3iosKp+qr@F501f7x(=6YyUyfFsI(I{)uZ(LmfHSa|qKAUv#BAgB+1HU2%^5qw-+ z3!Ebyf5X4yZzINCHR#NCHR#NCHR#NCJOLz#9*f^bR`;9O~d=ZR6+QXXa&L z=V|8VWZ~fAW#i{%27HTgHgmB4>r#9fEjcM2IXWeESvh^W|F#pIhC1C}cVzpsICfWD zOj2<`E#`lt*2luh+vczP;{5}qzqJY{3;bIU>Qg(KfNFFZ>OQ-)L_^;i<{<9h#DQQ)?|B4Hn?$6sVW$GF_bn5q2Rk?mE z{*%vtTmEOvK!9}Q6y&t%{kZ7Y01gUY00U}$Z7xT?aAg~&Hh&nfCP}0Q-mi{IweNzs=|0TC)8X{{N@)8T}vT^KXRA{*=$Z zU1IE8Xn49|bt7Lmul z+Or|LbI~D!!48)~#Uv#qMG3d~HDvv&|FuK=tAPHg-*0hru>N0+F$n2@XXyEZv3Iob z|2snd^Z50bQHLFliGoQ=O^pKolMjeHz#2IH%MtajJo!s0-G5}||I(ZPZmj*gmf!&) zL&2#A0soL2k^qtbk^qtbk^qtbk^qtbk^qvx|8WFjR8YX6ClrAUz-H2m0~~{OJg&t0LB6R`1lb$fOEhaL^WU=KcJZx=nVrz@&dg8clZzv0}@~`ptT4f0WgUV zFccvLum>h03J4bjn(`t1gCSf%n8nKllmmhRdtjnKYrrprX#g9bAs;{vCJcN4o&aDW zghs#ygzNlV04oGGgl-t%EevP?_yx3t2?8a&qFjKZB7i+WfiQ4D^xzT!G$UezFdwi5 zaEuS=C5#9Ha0CL#2lPXj_{#tUEuaiQD2k8+5CTg0fQ~SJ7?%*B1mPA?ihu$ffh)iz zzzja1nh)V59|9zR0RdVFumkW)7?2Gl2uuW^0$2b;fI@%+4uD-iGr%-Np#Ts+0l+Rq z8~`zha08%!1rKO~NInDxgcAS}VL&zzNSFYi>z9561VAUE2fz~$1-J?12ErbM9YAjc z9APd%HNtwJ4UlreTmWjoE(EGyCIcLRW^#T13~~Se1L7g00X@dq+t94CR_~8 z7M@-<9%jIuZ02d<xJT%?I3%f6h$Y;^jn(l0l89sM1GX46k+ieFTI++)vg!q%|BC%p_5wN|2z@l`s{hs`wo=Z z%j!jDb0J$@%HXoq->`^7NLpa?D)ve+t(Ddz`epzh{_vGp1drI0aZF>)HW2F?j zaF_M%l)hW5@Tp%r?kwP`N|HzY0R@Y>C6o}m67UjfoOR1sZ5ndCZ&D#v{5oIjU;d0e z7h}~o`&sU@gX7NW)+-!?trf;FR`0u8aP9~rDLdG*{HlG7oQFU5Mt@L9rAuIh)sG*r zt=E+>4UadO{e!;jKAxwNj$%jWemLU{yT0eXXxRIhF)B9_Pttpr`Qh1J5d;$Y{g+y( zU_3?tg|MR)R@(Z~86k#TvL4kl<_8qzb#1;w0$(L9HvHD^;u2U76WsaH>stvsH7~>d zqO!HOWfke16>x?cIl?F3x0qY@tN&ox%*CEr%ELFL5Y>XkWkWB9q6Nm^uZgV@K85N zG7mJRgHMg^?FXG_G)CE*dGHpJwAa%lohPZEzkFp;4ysXVb{w67zQ&V`@V0c*iZhdP z8tTwUp`ETj9O4Q2_#->b`|h>bjhzWn%ghe^MPAZ=Jo!(BIL%@{&ELI_%}V3Hy@syl z$=616n7~JUUDw751}s{J?m02Yrq0{CYc&y%v)hF^(>xYFOqUYi-3)8wuk%$E?+uU} zSvpu)dizu(RPs%M!bPdqN#IQvo!|!E9&7vcl`A>&`MB>q`d>{Sv-vP`tG1gl^PXDo zCti}T->BkvH=yaoR9c|7NZ{BmvmYxd=C9c8*K~{ai3F-ZNS0h)@vuGB&$le{!e6bQ zlY1XE#>t%37-kGQ8=bYS`s!6+LAvt>IfjScc|_S(dG@-$jI@|8kt4pu{FD@`2~TrU ztV5^o41lkE__WDg?v|g`dpjwh9{m8TcQg4AwVK$ru@%F`{b8t;_C{u}0+meXTRsVm zf(?Qb?xCLcKu!!rny)O0t*b?&(PtvWYYe@IUIKO4sV3USz7uPFS?cD^{i|UooB2P` zXDZ%#E}*m~y`Q-xG7sz;J}Hxg(sGi-U9XrAy>pHjZT9FmBEoX-O{$)~=%Sv)Q>0_b zJ#x{tCK@^7vgCLjq?%Gy@%(;mb%SgSRfpY=mf7qg8{rvL^3ogMI(7`Yd1nTdDluqK zBtyIV=K4J!nm`*QS}$cEDwOJJDx^Twx4BxcM_CCe+&#gLGqM!-h#~#Sk;D#|Er zPCA{udckhCz!8svAvCaD+pH927ssK$x7oQJp57}-x3=-MwHcQeY>I=&?_+fo%8eiI(0 z7QFCOjZKBB_{M-ed+X_%@Vywm<>s6AC4~lYiSrFOJW?Kes#niYG<~NM)?)8?)TKri z5>MNm7GQn)5LAO&Es=Uv{7qX7uOm((e_WwFchi5V^##@pUDx-HCP!+>gd9m;6{OTO zuj^(fqZQwy4yhM=ZT40wk&mN`&EHTfX&K_)gT_sZG?_(|5apU|!dZ-zc?4raW$PtZ z++46m;xW!C#vGGQ-Cxb;hbQ7WW-3Kf_32rcRwWGPhVo)Xy!iR1b7D!}4QI5^?Yz># ztXZ9n!p2@D3c|S0X>M39UGOYVcJ&LEF6M1Iwv#@(TE3Oc(A}-jidxW_#YiZ>?;2JZ z=3y1lefW_Lr%-iwf=CO<-boi;d8p8xI~5FridHE{FWBk3Zq%Fy@Q+WddsFR%_>r<^ zkBZl@M$bLIa2&B{6X9)$k@=y=!cH|Wrcfwyu4WcPoub}Cm39Hj*vL` zHk>E{)$8^?xBh)%WxKLd*&+3p9WjB-Ufbp)k4z>r&Cv#OlBYhJSiXBEr^z>bIHSjo zf6%-Jg=tmR<3Go^ER#t1(wcfqZ($xFWz%b!Z`>qi^^h>y_pIP=k>H}Mz^^JbwNSezDOcLY!2-K$)%!gVc(RDA;)xm&CE+(13|6-*~mDgf$WYv+lCH+0MD-R()*kej)hw_yi9K(&tH~*t(6qzxe>m06&2zL-h zO7BmtQfX?cGD4o+!ZyFix_FBcf$MU?SiWa(h3Wc=bxUqkU+$)iRlKchV`WE_q(0j zcRA)drz@e~Wr8M~`a~*+Bh&A@h6NUc@ub|*rmiVW;*PRqm#XJ)c|*TwPPVJ`)&-(i zPkLjIhKaM{4Xrpz($I5wAm5US3?nM=H4+Nx&vzu+W4To_`4y718u8%tWIPk4qxlY@0IU z6&Vvz=_1`nQ{izz_4n>BzTY17rzKh7UhP`&R-~#~%MkUk?g=Ov@iM%N)DR=v3xR3iQ?{YuJzR@(TuNga*Tls#?@EOP z+i+fjf?B$6fsNYQ!XaQRkgXeB9}WhiSt;}9)E`WUOw71N?#1z$_|(oN%k=CNoLFDK z%B>yhBlJsc?^9Gs9v$rtWlT!1>LV;&dve!zZC=#Is`_SfxN&EvbME4eHKM@Ng9Pkp zI>rR&AeU7h@$J&VSPlQw6w>lrsOqvxm-%ngzp8E6nfLm{bd-+faA6%qxfa|Nb2?_r z`ep-eu}oqaVn36z2<>i@-|l3TV;%ak$WvYtGo0X_xE9a-9p0mv0HhPCAHjnrHxO1Tv z`4DGLBg5X0a@%ejgq@6K4}PM`3WPhMK|s22st6Dq{N2E>wC8kmbk{LzLRsL&2wm-a z&yK?tDo@X^t7R0>rTDuPjl6FrqL@t>@>HAX(#XpE&&waS6WomtCZw5l{3P*?o?>B@ zC&shceR-L68^+XNGS-!eifZXixGebH#>L?N12dy6%e^cANfd7jsKf2|KE>zF?gRm! zq&9E#S6*4HMP%;iiuYU-z|jI;wGmiQB`$5T@u;byY;jmLaorU{6Dnsn4GAxFi6Zr) zKaLu$)3(%kx;sDRNj)mCAi(zLqw0{PIeN*1viI#VX;*y6{)|j}qsFk9?JXq-v1M^x z-wyQPCk%@060>gyAUzJL_r%u^sUmAX7uGO_Ls79nx^8eUIJm!al|KIRQDFltG{5Gi zyTZamMW9j)Ulppu2M(69ee#ZI8;6TOo^sL>>~m|D?CZ-$eVLg*Nb%;@Ed%3^g5Id9 z=mluJ3*xEOdCz{RYfQUEHoTWAr~Jut>#ad+$0EwHnXgmSDznRX4Ev5N>UNdPtQqeI zGit+PTd=&SUHc5DngR7EAWFeL26gdC&n)eS_CMdu$#W{=Wwgl_<&J6i1=MpkTb{Tx zVPp&ml&k5nx)zMKJ>kw#qnu1DInDX_(-9xPn6sZWBVQ3S=8GAxA8Q0VwcEmeI3)@e z$lf*_35>VOy{3kWFazN)GmFceQIzYFrwAe@bH#C|8^8 zz2?tU8?hdh%M~7*%AM3m?Ak9$;MDS(V*`YLVaAO^$2ZY7Kt`JKNk5xdJ?Ay5ZB-<6 z)GCDEI)(FVHAsGP5mh$7QKAq|_A2ARm8?bLfTTh7L6FWRD8l^4fY3Bv;*o;u6a{I(uAC`Itf6>N~BqpKx^zOsjG`=FSO2%#T2PhlO zLzU{%f7Qy)Q` z!k|sC;+D61{z><+)goi>F2iw^j0S`~{*I3OPg*0m&PaqwJ>MpQ){^~|iC$nnyXSXm z)4?OA66Ik}@Q%%vS-yJ+o1C}7<96#K+wbE}wti=l>-#-{ub@uo`fywntnY+`4YeI? zJTV;+dio7_ljxv%QV`(>$vAFx@h??>o+R3qmeg-s?GL@e_6zrokbvvq z*cUYMVZ*ZqSL=N251>XkOi~MOFomN0;a5x@S<>|&4G;0!)rPjOp0~c>2cp};sZbPs zsFme&dnDIFEv#9pkB1mAXBMzJ2cBD@$Y9XFU07Dt%1LxTPGfg@_uzRc zq=$G|Q$O8N9()_kj*4Gzjp>RO8b$Joc3x@g+QLEA0iynKO4WLdI^aZqv?&jMr;$@I z{3jJT{;kdPgSDXGTan{#MS9g_yfMKrb!m$aw;6G~(aqr`C>e&)xRw@I@7^Ve+BBVl zVHb3M8N#%MQ{@<$H$x+;L>1$%ompg<8NA895-;6E@h{Ik}X(2T*l zZYckv#THaEu)QthU$}08SOvhxP6k1%p{n?&_@Vf0c%694xL^22>(9 zBmpD=BmpD=BmpD=BmpD=B!Pb!0aturqK%89YZ*=0#%M#1br%9UVl0_?9^UBe^$liki6A=hr!*3!32AO^4s|*==7c5BjfIP9zQXN-KI5r z9RBswji0&a>$n386Q~)?BJavSlt)?c`GGu*(kAmURjrgj# zV0C>I)|WaP!hEcQs}g~?&+Z0gd@sscoayLmd(-479K(1P4|6C-^-qtDja$qvzUs3j zC?GN0A2p1JX$KmVa=Z55*rOk*KU*rt@xTG*awI4y0+-bKSJ%W3H~mljh;&qV8nPZ~ z`~Rp{W6hgvjL=h2h+@hp6ndX$YiXL@TlL{&G(hmN=iuI1SpN-!tnjf%Pg&UMf8M#+ zamu39BQnrx1t}hdv5ixf>^->kRr;%uj*WvpgC$+>G-nE^1Vd7Kw`H7uAoCJ5JiBif-qJ#F6wnC$qhJcs16%T?VDc6_Yqs;^PhcP=SGo-LAan zy{gS$Sm!5~?Wcls9}8?~iW2+1rC@aau~qo2YKg_HxV7-$2*5M=!LL)g#i&Mo7;) zlFcj@-H*L|*x43Sf}Z_wQqZKuM`K)ZthBq!lG_NL#N;U)lF#}0cCHMrPFHKBc#>?J6?kKCVfOS1Uxj1DYo^c( z(pV^$I~I+i+ra-0H1vN3G;|;E{|Gd6ZM1)W-lAKh{u?5Ju8Q)z294^znI38%j@O#@ z#zJB#Y*-x3d^jV?aWo4*jU!3NC9!2x>oY&TLPtFR$H(&p0nhtq@n)ck_|5occrWmL zp%nP>&|>^es24u>zig;S3Put@5+;6Q%a@;ldM zaIiSRx+uSIwFL*0A8d>A$CgNNF#d=&=;*)S8g%dXZz`@?3VGNGye;{||U&;N0! zL3q?qAG{SPGrlYE7Jw96H_A36X3_k%}jL=r#}KoUR_KoUR_KoUR_KoUR_KoUR_Koa;j5W6!)HO(en*wf11n>?s2(f4D2gvN_T1(_Dgo29}R>A6wWCzlKZ__y0xg* zCMhvgVzh60nfA4gv88r^Zp1;@d9AWvvS(iLrTi+vmd7~fOBcmu4abMk@mQ&oJ##v0 z6Z-W<55}p#IS{>Olr=qdWe$V!`BT#dISTcCl)iO&c)0Mx(RO&WZ>E`xM*m9?RD*pk zf2Qt!q=MtVf`B3Sz2F;7m`sm&rIDPrkZPBhD-`!>0ria z+TE?Bcrd#^Col=(tT`=sd+(Tu-r3DujEkasOsMc(Opz$lY(e9lyC0eoSLhT-a&^7? zO28D8hon)TPy?kG?4rn2v7@u>S9i<}OHYemU~QT2>w+YFb+2%>#-?*Yr_ zA1Op%UsxsVB_8T0s1uyjQLfR^Ot;bor;oTaVr9A|HC6_MSfdnl>KB;`K1p9}GJQ9O zL6;0k`Mj&9xxDnO!^Q5gD}A?>sc@ditc*I2js)HK&n>jB(p!fO%rHpCt>ovHdbkfhAQd>T*bR`KDU-OPWp*oklk;XHJ&B3FY)MDvuCq? z`f{74#mTs#UE_@H;MC9+Kir{;=4&Ypkpm}@?jX9MbAq9AZ%tNfUa_Ssj$gJh(H;S5 zaD*W~6AUGsC-m#gLH`3Z2zr~fbf#)LOr!Y&xBs#jWucT^pn}$%MZ>ob8u}pl6~;HE zr1|uxTzjPCz5DHf`m>shzJpJTX&dZIzi;$SzX@#)W(8Z&7=Oa?b!7^B)E6ouSmB%h z%mS=Cm6>*5<$Y9C^MNOCh#n_gQ7V&4k5}fcx~o1Ias#hY?i!Ud?9*_IQzH%Ss3`Fl z@1;I81#$1J3$}n|#%yYy$8$XAu9Uzue~j5zP)UiRYgqU_avaZio;q)R$F0Q5v6@G=_0XOW_GI{O zcFt12Br#S)Ow=Kt$0yh(HN+?&saP5BBX*6D=>FY(MPUZa()aXiw+{j)-+ne$=b`J^ zV-fETpmaVVQvs^QI1Ax}cLMm&cP`*&iqZln<6KYdNqYdmQFpGZo7LBv&fM7 z3CyFSc`we&_jzBEuu2XwMQ8@^bu;t#jB`sCW_UN~y{B}F(IM+|@n-v;JSUCuC zX|4q=X#FZLn)lNDYLwWbu0ly8ubbX1oX%zjf?Y{Eag+Da0Dhr7NaUHHvDnUYPUTQ~ z>||`ZIKdK<*w^V*tmBsLIiTcm>Z87SlSjjP4nxn6Bd&>0FrvfOx>62H*wmAs5T!j2 z7SLVJ>Q6x#)$Wb{De}s5GL4E^3oBA2BI1(mY(hggN1@vyMdcxG@_5U&EQN3%cQUgy z%UygTae)^o_vV8(Nj)|6ZuVrCnD-7!d(K{JKk60U74j{T)m{(!D&?m}88#{N;u{u~ z{+r{T0WH_%lrfrhnnXU&(5t+GBlrt@M%`vgJW;`IUHK>#< zH7+~iF4)3O{|qrMt?UVA^)QM^QAA71Lo1L1C)Awc1RR4Or{78a+_3$502W%znqBnsA#P`9zX*76TW-Vlk_@1d!d0vIQvn- z{PS8eg^?sGGnKE{*E#V&0k zMlr(WDdUsf5H|1D#Gm64pYPX^)SQ&%bG)avN6|-Q0qWxWj#chASNJlh#^4UwEQ31y z{ks79?#wtt59Z-`ycz4bJ)W(;{o^@>pBtalKTDTDF+AF=t=f9Fnlz?E2WrmTr|Q97 zuUeGJlkkSYzQm*Qr;HcL?ml->rFAu@aZNqh+k{AuN*$}^(@1o;U)*tEE1xz?_6#={ z(Ag4xZzySWp~NdAu|q{D3G%2qmAW5`8O*E2n0okNRKzG%LpcJYUg8#YvF;xGCAr7f z&lyTn`{wtsUz)CL7X^Q|q=!=yn<&c;iYGT&*PsWmbW^q}ML!Kk!J{`UODqaFx5!mC z47^v&bn(?r(NFO{0e7{Q6j!cvmxVZ8H_d`yPW2tX4z)X$%CEtiUC8!?0?Fq4c;v$s;cgWqoApG1XqNFGxw8BY41JWAUjf3yko zUe7sKAF9~yc2NV>A+LiFyRipL46bBV1g=7tr|47wIIxTOK$WxEk!%S5P(CfSkd#Ut6;xauZVpdca zzjy+df(DOp@7tufiTMtfU-DZ|jg%Ru}2ea=9?8kU?WNb6QS3_Y+ck2sD7oy~vlIM1A|*G{V|uiI4E z4fbu(LzLdThFYy7(s`Hk{>$n0lwM7Y9kDjXNlrzTXDRV`lJ4lM4>OKmmlkPl-FroR z>k+<1=7)tE=0`bH6ER*KHtAHFljC%L+8YB2WY+wu%DvL~`Dy&Jk!h26eVK`^`B28t z%ZMr#U7pWD$cE8OV4XkECZyFo@>*+>>#VtNNTTDf~^D zJD#!Ji9@O8t-TUrXO%gVpYt(yEOVW_i*1swdx0-5-L}8aS z-j|8X!xglACZV@{Mj`)iZ7HWt5*j8Dr^ovmG_XmS21Gbmzj}NR?}uf)six3(?)QjmTi66nyP#8Ek!e@(xNPMIUEg=BRf3nWzoWu%4fUO(!f9ja zFA{fhq*=G0oUPXsy0c@Ia&Y!}OGGOaO3CU3(Z6;ko-@{btua%W#H}Y!rz+IiE0|7r z4}*9lhytEu^%IOHev9`t0#|BJwR<(JjuF;1=J0- z8V0v3TzXzG9Q~lc7GKGmQ#*0vUDsrXne^;3SlPmY9;B%li(1`2HAuaArDSb%zE>X9 ziGH-d`J*Un=96~s%6rkvwD&MkMKj1% z(X4&cdrq2Mr~BnMT|v)si{+r6VKd>Eb1EyHOxVN%jjZ?lKT5jU_h*iVRmDxZ5&ssU zi|6)@TgWFBlDCvu$&3Rz@@VTcAkmOUXxN4V@%R5RK#m~%RlHGL3!MAdX28Y2W~*_)fPnnqBS}EI#_vPGXq=fM*lh0}EgD!GQ#+=e`X}SmGO8F7_ zQL<8m#aq1eYWh~YUbHv=Of7~^YH|PbM1dC^S_+2#tx+Hv2z;}>SR-%@;&%%nLsDft8B&ZOFWfbO$L zmCO#|bSG4|ikm>ww{!YswK3e2r<3m~X}h%TJ-CgPQs}~6*0)poZmq(ne(kukfTt=+ z9`y$lEasL_LhMSwOQdnuEn~H5$nm~Og;??He64@^Gxl7JRp0Dqxz7%cJEvQ(a16Fq z7{ge-?{2}lBaEc%V9WBW_AzoE{@5G+K_Qhcfe}_ee!#X~SHd(r-emR<`m+0Yo=Q53 z9i98(j5F-|p8KLq}>Z7;?#a zRL__nP?XoT`3?zum9*IKTf2)(U_DH5=SQz^CG6C^4Eu}9*4~y?q;pol8EWJRpM2k9 zZrPJZUfde0*pM`m$8(Z>r8~shrju>jDyiIaTz<_>qN0pi!-^-C26*Tj2!^G!0hCdq<;SLl|?zIMyc6xbO!nwPcp*W(oHMQOv-7fLnDQDy8dv8C*@@GY z*Jd|%CP*zaJMb5IN&E5SKNaFMi}^Hv_c}H!jsNx?>KyGB|U}5R)Q;kr`Hw6k8rCuk2 zH(hjs8+d!H?blbX)RG3ab`*0$=a zSAhlT&Ku+y9(v~yWn1Oh>;5v*V!A|*_!9F|Qm7_8%}KEioxU>wzVhMICU?18epc`8 zq=0(#1FYW7Wu@p;p=(nY{{BGM#VvBs2;(2u`?%dfEdyF%)UOvLv>y z7L7)qi4d3n>Y8bhMjEY|3IIqc;~r*(wg*s=90)fuxt3F zOcF}VNfLLxVm|cFIbyWgqvMDO%e^#=4 z`?b{#vN2R0c0XEXvx{tmXHdyYZ+z?6G3e%<8C0soph1xg?e3fF_k3sqZIEcalzpgB zs;8-t0#)DUYP}w1C8Tio1Ut^iQrshk^e0CWN4%nYx&nu+8~ae-21}Y#I@jIT+%Ug8 z8XFnqb9<&Js5EV~mjNmoD(l8H-f%BMS^y-ri07=!E-g9fbn@y2yV(LqJPL-;z;bP~ zQj}dBhyLDjJ7Ez1=V4t-9Vn%h1GdOpK2SjMCra9+x!{zXZ8vLTg7Gh}?ZFG=3s3CV z#B8pI{eH{kF{dF-iPJwws-ol01Xz2i}r8d*p@ZFgFL_31-U4QjPS>Q(VK zZ85x#IEnmmh4S1@|E1O!STl58-#eNdsUZ_`BzaYkQq#Pyo1KhSe2+S$UhK8mTd71o zjxIKTL#?D`hd>7)!hjqEg*X* zU3lf8LU-;|FbpbMr5wFrr|-H^a~{AyKC$jiwG-k;%9=eYUc(wa_xQqb#G*}vw;@L6 zhaL+%)x4NOq5M_G7$XZv^~vHjep^HN{S-ncj2D;_1=lJenp1C9U82e-7OXG$naNFi zwqk}byk{sV0Tl~Xiz%O*gSb}=?+Dqk@Q%_Hh)^7ZX77M<{OuM0xhn7*HbI(feTWQ zo?`VIGy8I4M@_vG>NV);Lf;+~#haMd|N;^5nGq6Ad0+xy)5_lcG5%1&j6 z)L(YQ1TuSVn~yv)nangt8^}qX`e-|*p#9y|U)^BNSURauY!9OJS~BH>GG z>M^~Ad4QBnuVub*lbF>*!f4;iviBt$5aL<>@f$Am+1aQYm|3e2Y)(1!?;ti3G7ZvgDqATT!QP64X^*i@}jqN8* zzwa6rSP;gOaz~rG2AG9J*|JO3^S8XAUo5=xTdBP|z-H*Q8D#moMR9g4`+2l+4s!wL^V`eyQz!iYm#Yqurs5 zN$FL6gr#dw?)t9Hi`rOK-%JiS?(B5VUA(bI6nJ`&fIUsenBW}bvdSaAT{;-6;h&m9 zT7C;vT~_Hb|84qLwGBJ-UZ0qb($O3)tfMH`g1cf)$81^OY``s+Ni0L`XHpiS-EH#Q zos5zkyEL!?9)>vfs3$gztQIZ&pbDA+pP~ahTzS3~CEPi_A7ObDy4~8xGx5vcH2ET` zs9jTBZSJp3RZ)4f5;bh){B+cFmKoee@9@Ot@xC8-F4Q6);>>Ae*!xj#+iioeldoQ{s}Iz~+>3%nSit9|d;akxU|>G^fFi~_n8f0v?> z_sv8Uvk60NvRx-wBw zExies1;5+47~FqgW|U>QcjZ5c;%xzSxc%Oz_`KPjAmEeK=8gW!D~q*=%pG0vo@)X) zTEMF|0t>3dr7bodHC2=?4vQwPyFzF}PD zX|BjukWFh&oU53qe2BvD(?g3-1o+&OBL zlW8TVIUj#I;^P-{_LFAhD`LicG2``PjbNvCTi6e$M8N{t+lGVT;1kXXsF;7kncMBA z&59orQcWyu>vbKn3C&)~!CvjIcI`=xOT+I^X-op;YLmU!{F!Pa)}wN{!edjplNyO# z`y~mST3&N(fbcKOxN+$CCfWwbNK-!PXA`UEyhgRHiiD0@h45RaaDJ@@$xkk#%H}sp z6vD|~WgNJYwMZP0G^joZ(s^pDsO@cXCI68J5+H$Mkw#rWLswFutUAMd!y{ivE~4qQ zzd&{`ejVd(2*uya2USc5oX&KTWpn2ef$=u@kjcarCpEd z0Y`KSKOWxI$^%f0&TPB|P(K6okmOt)ztQ9#Ly)BDUWqR;MdqOEk5u+G@Omf!wKXbNdYr1oaoYJc(Oj(lKnAqyc2 zr4$Nx!u?}Eoc>wvtriPO3>dQcboTc!iF@iTauIXp=RPWJuLK9evrTF0VcqQZr%>2q zGzBax13wS%r<#M%`hC=#e2WX%qxQh!W}Z!=o$E1IJi+uSYtepuP0~P-nT_`~FI&~16U z;%*HVt0q;Oa6dVEY>KNpJd!KBv-e`Uswiw(ZZ)-aX_ZN_K7L<&*b^ojJyb7NMbS|O zcg2B#fYm=Z2^=hgLH~APSyd}1(fv4$-R0eb=cSMy;$cnwbVqscZ8SS7e!VrOD_Uq2 z$t&7oMwp6aCSqJouePPQmb>ROI-#HqQ^%f`V^Fj=L4|? z1jE#&Ek4|4JPNt>n)+F+LuY?c&^puXA%uxvQoJp3VLjGaz@Z;(zB|4#Knp(jNYr?F zLT+~+fRT|a(92lx5qC`J*6UYWo%ZIA3)p)zcGM|yheGv6>FhZ=j4V{Fd)2iWgnId? zQ}nlK+L=X$nZcXvpR&&Roe_^!3t|~o)MfO& z!!wEiH7v4(?84za!zAsSp`wk&JJ{E6@Kqna%8Y}*zWp{M0dkt@hsp8%m6@%(S+ca= z6u)cUJjYtlBHjGS`Ue-A1Lt=SL{?lWcrTcL7{;D_W?kWVmOGa|kAwe^WM>I|v%Bnz z@~o(`3@>w%)kKK1b<(Nl)m`s$?{HHH7RXpVf*THwHGHEdtg+O9_o!+8u23)@&Prod zJ^2J}1I4{WZqTgNccUFkobP@VKl1Rhq9U98R}Gr1g{;m{+Sp{kK2Wcp_PI0lU6M1j zJ9y^T9F62@G~x}+v@&;^BdDZ*{ zu5-A%mA;!{vToZ^sMeehuBk295dT_$=Cof8X0E)sG&?j?J9S@%4_$0M!A#ZT)Gl6T zSI9Zy8ogeXZ^n#{an6bH=3(tqkHr4eDAjPQva6~O9yh1CZ_bE29|?e6nE3do{=fF^ zswoaGNCPksf@^ShcXto&?k>Rsg9ZpTIAm}QPH=a39oz@E;O?%Q{SSM!^_`3J1D@*Y zI(^>i!mam)AAY@+DD>LCnXBTK>{s3k$5(=i_>AP!{Xv(_J{`Tv1POB$CT}NS$*4dL zLz@G>C64<&)Oacf95)(eQ)x3&5D(ZY&8dLH$?+ZBgb@=24xwHqVou{c8TS>8rrmy; z;fYI>%f_HRx^n%@uNdt8%$s+mYzRnDKztzXlpGM&6zD-+h$U3WB5TDx(D-1Cjqpm% zBuN2qT=9B;`z2O<-tif-$f?MtBwY-zIr2d44{V5dM}-?&)4rIX%0#uB=J^;^BK|+A zzf-2wYom{k=Am^7=AkqRVP41dYAuho4EGKf4Kv^d2z1|I#A}( z*V_)mjJwk9s!10^Gp7rb;E18*#;GVpvFfAcA-giIYUU{Py84M1W%TzzNPyHsrzZ^t z+I!{_H;r`#CsoifT2RLu(3jYI`7VKTQPFYlOtcVuL-k_m8c%2mj2@%V{5}}HAVR)r zDA+|Kj-B>>2z(o2BEA5RKIzFTT}~HK9y9;q>p48Q&Kt|DTu(@LtNxo1KG2ORI)ftT zxY;&_V6At_bkO+XO3}#IRU?$Qhs3h3xNBLSYZ++!E0hMS45` zK-&$k+`07+Vnhgvt*XXhRU8K$)hN8gky)48`|VSmTNzaK{*|u_B8o8&ZI$8=O!|7) zOV6<^({QU{Nhm2fMk>n(4d1WY+?%|2A+AIE*I*^4vQS%Ofl^FT0>}295J%;A(pZ6W zlISA|k$R~lT*_)0>~K=hzKKqh%yS)-hI zoENF$7j~TwGc$`T?m_S%gqb{CkzL!Q^4u@vNU%P*i(LA{BU);9hz+duY7esx7d9;t z0A&9(lPhJ(cb?2{MTTJqd8d3i%A)S;7F|L_b#byUJS~}#5qox> zqdPm*j$Zhs)FmEVcJ3i8J_`FUs~x*=rj$qX_Tm@7R zIjCQyZik6=&+ap)!V4kB8D$1Q%jIt-xt3ta8kb8JAKryE-rSkEp#14~mAV7)iBW9x z2ZgpP58(yEUu5UL9m)Nm3n7k$+eEA792b25AKck56llfim8V2wBON|Tl}I@rBgnk? zNqFa1Z23})G>I#ewbCK}>5wk;b`7nZ+%bERThHmH+m$Gc{uPb+Y=RFxZL+9bd=8|T zId~Q%jiDkwr?#q-3TjbJYuEFqTH12nD-i3 zA7eZR_&HBhro1Q+yrS$bIMA&zC2*u%s46G3ce#cNRc;(^Wk_dGftxV>@Bc~9>Qb+n zTHZ;P3$WscHS!>K_m75wLjo1X4=H-!7n}wkcbQ`19{S$gqH@;k8ASWZb-Ei6F(P6=b*?s^kJT*z{rL=pVBEE>8sS`H~rGl zi7imh8<;lN%WuOn{j>T0+Cn|6~ZBdWd#orcCWq zmi)|QyuHxIHkSMz@ab#veuFo~#9Xj7%yxuti$)(5S4hy%)473Qk7ixD3jJpyn_je|)fy(i9Wt#T*dEhf%q5mPFF5*FBC>IRo!wzas21VPCqT+qBn#VV zovvN+-Bfo2V%ehKpCTe{B}>g<(>P({ghL(d!)tou{L(wl`l8DNw(caj;nQJbJ{vZz z%T_B6N0pJr$_`6nc(!RC@&iBVOA>|@_OePNz#uqCqhuhTE&kkZ?v9$UOD?0 zn(b>cj$TC;NS>btGdIxj5y&s7bM7<8>JWT|%`^nQZQOT{^^cjf7aMdl8qF3kAT^`R zY7yc5VNG$lrZjeJiU5e>@Q}cmUA9Qh(^7#M+0RP@anp_tkinSDqP+{$+IlSSY?PpycrPDS{<5P(n>sf$K)^9-gw`0 z7m@qK0lI}8;;C=`I{Ll)31`@_V$RYsoGB_<-5;$7o2PTVC-dx>ahN#PMI4-h4k(3R z?j>$-gUxmv`~rs$--?uOcPkjmch*}%p^_02)f+py&|JUN zXCoJbqzl5HcxcGx8@ z4;9*A{T#lbc_RYqE8v}`Cs=n^;p|1WSQ9WmzQIcLF4*kq%5s6ohZfP57GxNpeK9Vt zWVec4WgEr5aJp6988y*OnQs{CZuEF|Fd)jMIpGP>0?d=vSIyPsyzbEDwdVDszjsd#n#$+PXK5fP(idMNj^c_rQ}$T6X-$e%OuN3Y zgt)IfMDV$BSe(y%MsFB%N-?fPj!_m#--59ErfT5o4Ifg)|BmPI`JfTB%e~eZ@!!~6 z*CIBI5fw3{9@{i$@?f|AwX_fZ&@0&s?NU0)r~SkM<-r}4*fM9GXNnXG!pf}S7(uF%{brX>3sZ4uP&N9sbLZ`YvJHQt@*oQ^?gI$ zafCmfYOZz*$pmoo^wh)uG!l1yzS4Bei3*`^YXAp4!z|9$bggYeZ7K`LkTRVWJ}twN zm^Vj&Uz8Uj)%AD=&P5*p?G0pVRdptj3;Oh>VROHX5{=(Ta1e*u zTjFsC)W1&V$*(ljm<8a1{jxx%d{>;qfS;daXUfE9e8c$mrHh9)5buWf) zWV$I{?~7!i+^3*K8S{-BcL}Df@+f$s2pD`nc={j58z7Zb^y8}8#QKxSxB5C^oK0Wp?A}ss^ z<*bn`h!G?o9=~4_O=->tVSQJ3&s=R!PNMTJ9ZJ5Lb<__%o-|01nb=^fcE%(R!J>VC zuOX<~e3wE!mNVdfc^kDoWKa~}X88;t{N*V3=&wjonJ#X~nHwcYw{NhQfMSrHW+upHSb=E+T!m}a74+7q>8m<5h&AH=MWNwuxR4UE{~ zz;n_TIBu!Lx?)klxOokmE*pp;X!4DX<{k8?1Wd*xM9vqa%FTJEQQe79nvaPobMg6g z4Iam`S}!a6rkz+5h+wch#|NHV0u>V>ExI>AW$c&v*YgQdBGxDwM#TZpEMA9ZH~qZ3 zWPT;_gC8GJQsG7i^N69KbAO0NzSK=;xMn8eG zdRoBk(p>!zPJ(5IvQ3_LsI2wTC?8Qlg6a_^j^k?>n8(Y18ceVJ(?#FGq=PrL9FIAo zyOI3qdx7(mT{}K@xl6et_1yvr;{Jh8bHIUc=W!bzkgo7dpize*W5p=pibUA{wkq1p z-i&KG*%sP?n#!Es?gV2rO<&Es*!ke4efK;#go515TC}?)NcGFQ@3Z0+lDKP=uK33D z8;mfY=rIymcFAa4N`Zc$OUSLG>9Pf|scN^rGe~oSYRvW?@7Z|cd>tmhCV0ce*inU0 zaTwpft5pY2xw`{j+e->vF@g!zk0Sg^;hwd!bDU}a@RbmL?U5Vf%{is55!Ag7DX6Dm zYkN6JL$9Y~a`KtibV_~E8K`re7BAsW)xnqG)iX zF&>$pse85UxY|6)PKtv_q6x&9L($@xB@Dw%P=R>81a-w5nWwA`ld2jSM8pjSOg<-K4$R>N zjOpgPq=9R03d0+*Ch8X$EgM?WPy$*Z@okDon<>2Jr{LE%xKfRAXvX^I4xM6AFY!N- z)6GBRqa3B7>HJ`;;VnA~S(VP$IF2#zq&o+)N_E`6i~?b6jovEk@+>~VT1zncJbLc* zrjB7go(*GC${;Y|;ZGgq4ZCK+A~0ZaAQ}nblYXqEZl(Mw31{f~j!iZ@^4<1&rarb9 zo2$MhM%BEc?%$Z3qxD%L(l~NafJecH$jC>egN5^(XJCSk4*^qvtHaq~v}1iC1j@el zBvbm2vdNusR+liW7D>FYz=MAs8Gyw$HqS3gywrG^UiuJ2fxoxKMOUy2i5i=MZ41ei zNs5)rfBM`QhkNwxKA>67LQ$G@p&k?t(ru0zVQk5lS6u#TpyT!Eo9LTr;({`8rXIjR zM+)z00~?~N3})Kq+UPZoAhCC*iyYidE#$)aW;vT_!@IiIC0G_TKGnYGVmOoxpDG~6 zWWHtrb(;OH!WiG&zBFRpUeACBjTlsvc{;4tXwg|kX9LUAxMNM4-ME-E3AXr(>X8{b zt($}5+d_eTRwu9kkbG=KtOm={R4Ms6SM&tocM~`N3jCMf*2zmOk0JV;SCsay^~00+ z=I6*vonzK0ge_YSffCdyX_;o*{Wu!8MmOvvP#OOYE8KH1gMQdX7)IBZ%loH5&>fvW zWf>FIs#@_u$>pGCLnB_U84G7T70vf;!a*Bt&0F$6n$rOrDE}1N%baVy5ecNoht!lo)0bd9#n@sijSy~6EW@xJ~xmoF4zx|~?OQaQx{1VXC zVMOMJ3&H}@UMq)X9*+XtZEz0SAb4fB7R*a#PEzSX1sJ3GKC9Dm5nr8*wB0OE%U|=` z^m>Hiki*r$`7aFERebPRo7_O(vStL zPguq`GbG({Y5?@8HB!Y~R89 zl9T|o@U};j@#!0G=wH3dICc{8mA=g2blmi@Y1--Z_?iMH&7f*lJ!ice%OJ`cFTaEe zg97GeZ%AW}d_}J@(4|N`M3QVJMC!C zUY~&&Mu%m9T;dgdQZtYi-I%Tz&Bi)?t0f?4{BgKZr2e5&bpImE!ZZn|fnB@TsypOq zyS|3rL+URDhv3(ilwWbLg>qDjYHH$HQ@`wb*Uc}m@zOH@-@kGK57my7ep%T3Ga69CGjlg|bRPaRC56~13-YKCC z9+(K4BJqC;BfUrSR>B1esish%QVg*Du0@_%ZwIpkFrx*`YU>zV{QFCrF572G#TIsZ zqTaIcls2n-Fg!Y;vIa4`%Py59I8bnT+I^Q(LqoQs)10hl&yJ#LHy-zGck@DkdF-4r zm*!4N<1RwmQg9%~=3oX?x=R}-`hd9buTAj)d9p;Ox{Ki>drOa*rB5>)4AS}M)jiC* zWWUR+#Eb*cDGyJd>Ckl)Wd8aO40KvDR(VhuSU%WdOTVdMT(`*YPG3bfX};kZosCI^ zBA)6?s{2eH8W#GX(9wDt`7J`h$*VaB-vPe!;Ycb-0e-fhMvg;9{){Zg`^3M?!CadVa#1Ee)OKs6_Sn)>1*U14rUmZ=;+%p z>Db1+e;>1(V9EI3{2vDS2nx09-}T@5PvAd+{{;RM_)p+Jf&T>l6ZlWyKY{-r0>K*S zh)_;I*bpTk?DOSHewg}+qTAbfz<5plOp+%L@*m%QSh)iam7M#5Ny`nYp<~MH8Yxvo z29TJR%R8ANP-`mKxQTNgLu1qS%~-$nNRj&(b>vl&g%S1B>PKghV7SGO2A{4E)j%?ZnhI)=t!nHLQ> z&9Cr@pHzjKc`-MtCYY@adWp%v#9IL__x)in?4Fg|a4gMF2u(Ak<7akZculxesF}y{ z($M4lYh&@#(II9@Ai|;23)Tt-3M^Z(I~f(SR7M;Q0b;E_H)T|1yN?(<)^P zp2@4g0MS?umx(JJPrS;lrmt3u;zUWyT-jc_%4bxj5St6?x3vYiVJhyfm{-=sFAf8< zavTNbCIQYm0S=K>dG1Q;w-R#|FlxE=H&qdVKUmvUa0~=WhbNJE7wV_Rb*(21U*~|* zE4jaMGvES?*YZ++2a?SjKQrmzL2qaA?A5fTx5;5B}@M!LSs_`|=d1UZ_w7>GVRD3LiDum3Ukd0z?< zLqmkp1EPd+17Q*FFfp4Odg<9BdV_WKn#JSD(X%BG`Ro&r0ga#%rT6<7oARQjL(9{N zF2-a2KawWxoxZYcD)LwngY^GE!cjvu=ApfB9hu&80*PIg#IYqays# zOl_EZc~B9##&~&yoh$-~pWg8j2f`(-)%Yz1SMju=I(C8=^mI~N`s0R46bkL2@8c2& z{K63#-G5EL!w>7dkqxpM9gVEmMs{gzTI`WktH8Vi;tI8&7a$6j2qLhp-pWL)@b(}} zTNE~`%Q6p;=}OSp5EbuA&-^`D+AC!#Ss1N;@)v}xI%wm;ROl1pABE5uE$?($uqFs3 znp)3jxLA>q{Cq-3UW`C4fC828uk@|bn!{K*dFzJH$yjAs*mqCM+j*-QP+^e`0o@0d zs0EMie$XR_zTcrWB>s3#9Mcu(-AYMuk4$#pxm%+{CEw4R_h=^Or7GZ%BQsF(F6$WH zcTT<^PeHcs$upaMji~WUWJ`PN8fJ8xhtvwpoe#zM{6hAa!4Y+xcdYXLQpI{TAguuh z`gqdL(2U|>UCmeS|D`#T;Tbw~VgGTVy>rpSNitncNk68uAlQ)MLd$*lSbNf_2+#1t z>+h@M97Lka5m!`+`{b? J(*@ Date: Thu, 29 Apr 2021 15:26:51 -0700 Subject: [PATCH 084/116] fix chacha crate version Signed-off-by: Andrew Whitehead --- askar-crypto/Cargo.toml | 2 +- askar-crypto/test-no-std/.cargo/config.toml | 8 -------- askar-crypto/test-no-std/Cargo.toml | 15 --------------- askar-crypto/test-no-std/src/main.rs | 14 -------------- 4 files changed, 1 insertion(+), 38 deletions(-) delete mode 100644 askar-crypto/test-no-std/.cargo/config.toml delete mode 100644 askar-crypto/test-no-std/Cargo.toml delete mode 100644 askar-crypto/test-no-std/src/main.rs diff --git a/askar-crypto/Cargo.toml b/askar-crypto/Cargo.toml index 24121e8c..2a9337a5 100644 --- a/askar-crypto/Cargo.toml +++ b/askar-crypto/Cargo.toml @@ -40,7 +40,7 @@ aes-gcm = { version = "0.8", default-features = false, features = ["aes"] } argon2 = { version = "0.1", default-features = false, features = ["password-hash"] } base64 = { version = "0.13", default-features = false } blake2 = { version = "0.9", default-features = false } -chacha20 = "*" # inherit from chacha20poly1305 +chacha20 = "0.6" # inherit from chacha20poly1305 chacha20poly1305 = { version = "0.7", default-features = false, features = ["chacha20"] } crypto_box = { version = "0.5", default-features = false, features = ["u64_backend"] } curve25519-dalek = { version = "3.0", default-features = false, features = ["u64_backend"] } diff --git a/askar-crypto/test-no-std/.cargo/config.toml b/askar-crypto/test-no-std/.cargo/config.toml deleted file mode 100644 index 62879354..00000000 --- a/askar-crypto/test-no-std/.cargo/config.toml +++ /dev/null @@ -1,8 +0,0 @@ -[target.'cfg(target_os = "linux")'] -rustflags = ["-C", "link-arg=-nostartfiles"] - -[target.'cfg(target_os = "windows")'] -rustflags = ["-C", "link-args=/ENTRY:_start /SUBSYSTEM:console"] - -[target.'cfg(target_os = "macos")'] -rustflags = ["-C", "link-args=-e __start -static -nostartfiles"] diff --git a/askar-crypto/test-no-std/Cargo.toml b/askar-crypto/test-no-std/Cargo.toml deleted file mode 100644 index fe298bfb..00000000 --- a/askar-crypto/test-no-std/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[workspace] - -[package] -name = "test-no-std" -version = "0.1.0" -edition = "2018" - -[dependencies] -askar-crypto = { path = ".." } - -[profile.dev] -panic = "abort" - -[profile.release] -panic = "abort" diff --git a/askar-crypto/test-no-std/src/main.rs b/askar-crypto/test-no-std/src/main.rs deleted file mode 100644 index 43f9387f..00000000 --- a/askar-crypto/test-no-std/src/main.rs +++ /dev/null @@ -1,14 +0,0 @@ -#![no_std] -#![no_main] - -use core::panic::PanicInfo; - -#[panic_handler] -fn panic(_info: &PanicInfo) -> ! { - loop {} -} - -#[no_mangle] -pub extern "C" fn _start() -> ! { - loop {} -} From 134de60be6319ee66a31ad4b363f42f4794bd9d9 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Thu, 29 Apr 2021 16:16:55 -0700 Subject: [PATCH 085/116] add fetch_all_keys to store instance Signed-off-by: Andrew Whitehead --- src/ffi/store.rs | 47 ++++++++++++++++++++++++ src/storage/entry.rs | 6 ++++ src/storage/store.rs | 48 +++++++++++++++++++++++++ wrappers/python/aries_askar/bindings.py | 37 ++++++++++++++----- wrappers/python/aries_askar/store.py | 20 ++++++++++- wrappers/python/demo/test.py | 15 ++++++-- 6 files changed, 161 insertions(+), 12 deletions(-) diff --git a/src/ffi/store.rs b/src/ffi/store.rs index b611e48d..ea9d2e8d 100644 --- a/src/ffi/store.rs +++ b/src/ffi/store.rs @@ -827,6 +827,53 @@ pub extern "C" fn askar_session_fetch_key( } } +#[no_mangle] +pub extern "C" fn askar_session_fetch_all_keys( + handle: SessionHandle, + alg: FfiStr<'_>, + thumbprint: FfiStr<'_>, + tag_filter: FfiStr<'_>, + limit: i64, + for_update: i8, + cb: Option, + cb_id: CallbackId, +) -> ErrorCode { + catch_err! { + trace!("Fetch all keys"); + let cb = cb.ok_or_else(|| err_msg!("No callback provided"))?; + let alg = alg.into_opt_string(); + let thumbprint = thumbprint.into_opt_string(); + let tag_filter = tag_filter.as_opt_str().map(TagFilter::from_str).transpose()?; + let limit = if limit < 0 { None } else {Some(limit)}; + + let cb = EnsureCallback::new(move |result| + match result { + Ok(entries) => { + let results = KeyEntryListHandle::create(FfiKeyEntryList::from(entries)); + cb(cb_id, ErrorCode::Success, results) + } + Err(err) => cb(cb_id, set_last_error(Some(err)), KeyEntryListHandle::invalid()), + } + ); + + spawn_ok(async move { + let result = async { + let mut session = handle.load().await?; + let key_entry = session.fetch_all_keys( + alg.as_ref().map(String::as_str), + thumbprint.as_ref().map(String::as_str), + tag_filter, + limit, + for_update != 0 + ).await?; + Ok(key_entry) + }.await; + cb.resolve(result); + }); + Ok(ErrorCode::Success) + } +} + #[no_mangle] pub extern "C" fn askar_session_update_key( handle: SessionHandle, diff --git a/src/storage/entry.rs b/src/storage/entry.rs index 85300feb..c5d4629d 100644 --- a/src/storage/entry.rs +++ b/src/storage/entry.rs @@ -454,6 +454,12 @@ impl TagFilter { } } +impl From for TagFilter { + fn from(query: wql::Query) -> Self { + Self { query } + } +} + impl FromStr for TagFilter { type Err = Error; diff --git a/src/storage/store.rs b/src/storage/store.rs index 73157788..7b405641 100644 --- a/src/storage/store.rs +++ b/src/storage/store.rs @@ -324,6 +324,54 @@ impl Session { ) } + /// Retrieve all keys matching the given filters. + pub async fn fetch_all_keys( + &mut self, + algorithm: Option<&str>, + thumbprint: Option<&str>, + tag_filter: Option, + limit: Option, + for_update: bool, + ) -> Result, Error> { + let mut query_parts = Vec::with_capacity(3); + if let Some(query) = tag_filter.map(|f| f.query) { + query_parts.push(TagFilter::from( + query + .map_names(|mut k| { + k.replace_range(0..0, "user:"); + Result::<_, ()>::Ok(k) + }) + .unwrap(), + )); + } + if let Some(algorithm) = algorithm { + query_parts.push(TagFilter::is_eq("alg", algorithm)); + } + if let Some(thumbprint) = thumbprint { + query_parts.push(TagFilter::is_eq("thumb", thumbprint)); + } + let tag_filter = if query_parts.is_empty() { + None + } else { + Some(TagFilter::all_of(query_parts)) + }; + let rows = self + .0 + .fetch_all( + EntryKind::Kms, + KmsCategory::CryptoKey.as_str(), + tag_filter, + limit, + for_update, + ) + .await?; + let mut entries = Vec::with_capacity(rows.len()); + for row in rows { + entries.push(KeyEntry::from_entry(row)?) + } + Ok(entries) + } + /// Remove an existing key from the store pub async fn remove_key(&mut self, name: &str) -> Result<(), Error> { self.0 diff --git a/wrappers/python/aries_askar/bindings.py b/wrappers/python/aries_askar/bindings.py index f3591213..bc6c574f 100644 --- a/wrappers/python/aries_askar/bindings.py +++ b/wrappers/python/aries_askar/bindings.py @@ -699,15 +699,13 @@ async def session_fetch_all( for_update: bool = False, ) -> EntryListHandle: """Fetch all matching rows in the Store.""" - category = encode_str(category) if isinstance(tag_filter, dict): tag_filter = json.dumps(tag_filter) - tag_filter = encode_str(tag_filter) return await do_call_async( "askar_session_fetch_all", handle, - category, - tag_filter, + encode_str(category), + encode_str(tag_filter), c_int64(limit if limit is not None else -1), c_int8(for_update), return_type=EntryListHandle, @@ -720,16 +718,14 @@ async def session_remove_all( tag_filter: Union[str, dict] = None, ) -> int: """Remove all matching rows in the Store.""" - category = encode_str(category) if isinstance(tag_filter, dict): tag_filter = json.dumps(tag_filter) - tag_filter = encode_str(tag_filter) return int( await do_call_async( "askar_session_remove_all", handle, - category, - tag_filter, + encode_str(category), + encode_str(tag_filter), return_type=c_int64, ) ) @@ -792,6 +788,31 @@ async def session_fetch_key( return KeyEntryListHandle(ptr) +async def session_fetch_all_keys( + handle: SessionHandle, + alg: Union[str, KeyAlg] = None, + thumbprint: str = None, + tag_filter: Union[str, dict] = None, + limit: int = None, + for_update: bool = False, +) -> EntryListHandle: + """Fetch all matching keys in the Store.""" + if isinstance(alg, KeyAlg): + alg = alg.value + if isinstance(tag_filter, dict): + tag_filter = json.dumps(tag_filter) + return await do_call_async( + "askar_session_fetch_all_keys", + handle, + encode_str(alg), + encode_str(thumbprint), + encode_str(tag_filter), + c_int64(limit if limit is not None else -1), + c_int8(for_update), + return_type=KeyEntryListHandle, + ) + + async def session_update_key( handle: SessionHandle, name: str, diff --git a/wrappers/python/aries_askar/store.py b/wrappers/python/aries_askar/store.py index 7013c8c3..884bfeff 100644 --- a/wrappers/python/aries_askar/store.py +++ b/wrappers/python/aries_askar/store.py @@ -17,7 +17,7 @@ ) from .error import AskarError, AskarErrorCode from .key import Key -from .types import EntryOperation +from .types import EntryOperation, KeyAlg class Entry: @@ -489,6 +489,24 @@ async def fetch_key( result_handle = await bindings.session_fetch_key(self._handle, name, for_update) return next(KeyEntryList(result_handle, 1)) if result_handle else None + async def fetch_all_keys( + self, + *, + alg: Union[str, KeyAlg] = None, + thumbprint: str = None, + tag_filter: Union[str, dict] = None, + limit: int = None, + for_update: bool = False, + ) -> KeyEntryList: + if not self._handle: + raise AskarError( + AskarErrorCode.WRAPPER, "Cannot fetch key from closed session" + ) + result_handle = await bindings.session_fetch_all_keys( + self._handle, alg, thumbprint, tag_filter, limit, for_update + ) + return KeyEntryList(result_handle) + async def update_key( self, name: str, diff --git a/wrappers/python/demo/test.py b/wrappers/python/demo/test.py index 6cb0071d..d95dba7a 100644 --- a/wrappers/python/demo/test.py +++ b/wrappers/python/demo/test.py @@ -136,12 +136,21 @@ async def store_test(): log("Inserted key") # Update keypair - await session.update_key(key_name, metadata="updated metadata") + await session.update_key(key_name, metadata="updated metadata", tags={"a": "b"}) log("Updated key") # Fetch keypair - key = await session.fetch_key(key_name) - log("Fetched key:", key) + fetch_key = await session.fetch_key(key_name) + log("Fetched key:", fetch_key) + thumbprint = keypair.get_jwk_thumbprint() + assert fetch_key.key.get_jwk_thumbprint() == thumbprint + + # Fetch with filters + keys = await session.fetch_all_keys( + alg=KeyAlg.ED25519, thumbprint=thumbprint, tag_filter={"a": "b"}, limit=1 + ) + log("Fetched keys:", keys) + assert len(keys) == 1 async with store as session: # Remove rows by category and (optional) tag filter From 294b79605b2ff3f3f8de5a9812e4ed6699692044 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Mon, 3 May 2021 13:16:47 -0700 Subject: [PATCH 086/116] make ArrayKey immutable Signed-off-by: Andrew Whitehead --- askar-crypto/src/alg/aesgcm.rs | 28 +++++------ askar-crypto/src/alg/chacha20.rs | 34 ++++++-------- askar-crypto/src/alg/ed25519.rs | 38 +++++++-------- askar-crypto/src/alg/k256.rs | 50 +++++++++++--------- askar-crypto/src/alg/p256.rs | 50 +++++++++++--------- askar-crypto/src/alg/x25519.rs | 48 +++++++++---------- askar-crypto/src/buffer/array.rs | 79 +++++++++++++++++++------------- src/protect/kdf/argon2.rs | 9 ++-- src/protect/kdf/mod.rs | 9 ++-- src/protect/profile_key.rs | 29 ++++++------ src/protect/store_key.rs | 21 ++++----- 11 files changed, 209 insertions(+), 186 deletions(-) diff --git a/askar-crypto/src/alg/aesgcm.rs b/askar-crypto/src/alg/aesgcm.rs index 51fcda16..e8d20b25 100644 --- a/askar-crypto/src/alg/aesgcm.rs +++ b/askar-crypto/src/alg/aesgcm.rs @@ -79,11 +79,6 @@ impl AesGcmKey { pub const NONCE_LENGTH: usize = NonceSize::::USIZE; /// The length of the AEAD encryption tag pub const TAG_LENGTH: usize = TagSize::::USIZE; - - #[inline] - pub(crate) fn uninit() -> Self { - Self(KeyType::::default()) - } } impl Clone for AesGcmKey { @@ -143,9 +138,9 @@ impl FromKeyDerivation for AesGcmKey { where Self: Sized, { - let mut key = KeyType::::default(); - derive.derive_key_bytes(key.as_mut())?; - Ok(Self(key)) + Ok(Self(KeyType::::try_new_with(|arr| { + derive.derive_key_bytes(arr) + })?)) } } @@ -230,15 +225,14 @@ where T: AesGcmType, { fn from_key_exchange(lhs: &Lhs, rhs: &Rhs) -> Result { - // NOTE: currently requires the exchange to produce a key of the same length, - // while it may be acceptable to just use the prefix if the output is longer? - let mut key = Self::uninit(); - let mut buf = Writer::from_slice(key.0.as_mut()); - lhs.write_key_exchange(rhs, &mut buf)?; - if buf.position() != Self::KEY_LENGTH { - return Err(err_msg!(Usage, "Invalid length for key exchange output")); - } - Ok(key) + Ok(Self(KeyType::::try_new_with(|arr| { + let mut buf = Writer::from_slice(arr); + lhs.write_key_exchange(rhs, &mut buf)?; + if buf.position() != Self::KEY_LENGTH { + return Err(err_msg!(Usage, "Invalid length for key exchange output")); + } + Ok(()) + })?)) } } diff --git a/askar-crypto/src/alg/chacha20.rs b/askar-crypto/src/alg/chacha20.rs index 7a0ec0ab..21b9db4a 100644 --- a/askar-crypto/src/alg/chacha20.rs +++ b/askar-crypto/src/alg/chacha20.rs @@ -81,18 +81,13 @@ impl Chacha20Key { /// The length of the AEAD encryption tag pub const TAG_LENGTH: usize = TagSize::::USIZE; - #[inline] - pub(crate) fn uninit() -> Self { - Self(KeyType::::default()) - } - /// Construct a new ChaCha20 key from a seed value // this is consistent with Indy's wallet wrapping key generation // FIXME - move to aries_askar, use from_key_secret_bytes pub fn from_seed(seed: &[u8]) -> Result { - let mut key = KeyType::::default(); - fill_random_deterministic(seed, key.as_mut())?; - Ok(Self(key)) + Ok(Self(KeyType::::try_new_with(|arr| { + fill_random_deterministic(seed, arr) + })?)) } } @@ -153,9 +148,9 @@ impl FromKeyDerivation for Chacha20Key { where Self: Sized, { - let mut key = KeyType::::default(); - derive.derive_key_bytes(key.as_mut())?; - Ok(Self(key)) + Ok(Self(KeyType::::try_new_with(|arr| { + derive.derive_key_bytes(arr) + })?)) } } @@ -240,15 +235,14 @@ where T: Chacha20Type, { fn from_key_exchange(lhs: &Lhs, rhs: &Rhs) -> Result { - // NOTE: currently requires the exchange to produce a key of the same length, - // while it may be acceptable to just use the prefix if the output is longer? - let mut key = Self::uninit(); - let mut buf = Writer::from_slice(key.0.as_mut()); - lhs.write_key_exchange(rhs, &mut buf)?; - if buf.position() != Self::KEY_LENGTH { - return Err(err_msg!(Usage, "Invalid length for key exchange output")); - } - Ok(key) + Ok(Self(KeyType::::try_new_with(|arr| { + let mut buf = Writer::from_slice(arr); + lhs.write_key_exchange(rhs, &mut buf)?; + if buf.position() != Self::KEY_LENGTH { + return Err(err_msg!(Usage, "Invalid length for key exchange output")); + } + Ok(()) + })?)) } } diff --git a/askar-crypto/src/alg/ed25519.rs b/askar-crypto/src/alg/ed25519.rs index 19f5211d..4d74910b 100644 --- a/askar-crypto/src/alg/ed25519.rs +++ b/askar-crypto/src/alg/ed25519.rs @@ -16,7 +16,6 @@ use crate::{ error::Error, generic_array::typenum::{U32, U64}, jwk::{FromJwk, JwkEncoder, JwkParts, ToJwk}, - random::fill_random, repr::{KeyGen, KeyMeta, KeyPublicBytes, KeySecretBytes, KeypairBytes, KeypairMeta}, sign::{KeySigVerify, KeySign, SignatureType}, }; @@ -128,8 +127,7 @@ impl Debug for Ed25519KeyPair { impl KeyGen for Ed25519KeyPair { fn generate() -> Result { - let mut sk = ArrayKey::::default(); - fill_random(sk.as_mut()); + let sk = ArrayKey::::random(); // NB: from_bytes is infallible if the slice is the right length Ok(Self::from_secret_key( SecretKey::from_bytes(sk.as_ref()).unwrap(), @@ -185,10 +183,11 @@ impl KeypairBytes for Ed25519KeyPair { fn with_keypair_bytes(&self, f: impl FnOnce(Option<&[u8]>) -> O) -> O { if let Some(secret) = self.secret.as_ref() { - let mut buf = ArrayKey::<::KeypairSize>::default(); - buf.as_mut()[..SECRET_KEY_LENGTH].copy_from_slice(secret.as_bytes()); - buf.as_mut()[SECRET_KEY_LENGTH..].copy_from_slice(self.public.as_bytes()); - f(Some(buf.as_ref())) + ArrayKey::<::KeypairSize>::temp(|arr| { + arr[..SECRET_KEY_LENGTH].copy_from_slice(secret.as_bytes()); + arr[SECRET_KEY_LENGTH..].copy_from_slice(self.public.as_bytes()); + f(Some(&*arr)) + }) } else { f(None) } @@ -269,18 +268,21 @@ impl ToJwk for Ed25519KeyPair { impl FromJwk for Ed25519KeyPair { fn from_jwk_parts(jwk: JwkParts<'_>) -> Result { - // SECURITY: ArrayKey zeroizes on drop - let mut pk = ArrayKey::::default(); - if jwk.x.decode_base64(pk.as_mut())? != pk.len() { - return Err(err_msg!(InvalidKeyData)); - } - let pk = PublicKey::from_bytes(&pk.as_ref()[..]).map_err(|_| err_msg!(InvalidKeyData))?; - let sk = if jwk.d.is_some() { - let mut sk = ArrayKey::::default(); - if jwk.d.decode_base64(sk.as_mut())? != sk.len() { - return Err(err_msg!(InvalidKeyData)); + let pk = ArrayKey::::temp(|arr| { + if jwk.x.decode_base64(arr)? != arr.len() { + Err(err_msg!(InvalidKeyData)) + } else { + PublicKey::from_bytes(&*arr).map_err(|_| err_msg!(InvalidKeyData)) } - Some(SecretKey::from_bytes(&sk.as_ref()[..]).map_err(|_| err_msg!(InvalidKeyData))?) + })?; + let sk = if jwk.d.is_some() { + Some(ArrayKey::::temp(|arr| { + if jwk.d.decode_base64(arr)? != arr.len() { + Err(err_msg!(InvalidKeyData)) + } else { + SecretKey::from_bytes(&*arr).map_err(|_| err_msg!(InvalidKeyData)) + } + })?) } else { None }; diff --git a/askar-crypto/src/alg/k256.rs b/askar-crypto/src/alg/k256.rs index 3f11cfe6..9c3aa20d 100644 --- a/askar-crypto/src/alg/k256.rs +++ b/askar-crypto/src/alg/k256.rs @@ -141,13 +141,14 @@ impl KeypairBytes for K256KeyPair { } fn with_keypair_bytes(&self, f: impl FnOnce(Option<&[u8]>) -> O) -> O { - if let Some(sk) = self.secret.as_ref() { - let mut buf = ArrayKey::<::KeypairSize>::default(); - let sk_b = k256::SecretBytes::from(sk.to_bytes()); - let pk_enc = EncodedPoint::encode(self.public, true); - buf.as_mut()[..SECRET_KEY_LENGTH].copy_from_slice(&sk_b[..]); - buf.as_mut()[SECRET_KEY_LENGTH..].copy_from_slice(pk_enc.as_ref()); - f(Some(buf.as_ref())) + if let Some(secret) = self.secret.as_ref() { + ArrayKey::<::KeypairSize>::temp(|arr| { + let sk_b = k256::SecretBytes::from(secret.to_bytes()); + let pk_enc = EncodedPoint::encode(self.public, true); + arr[..SECRET_KEY_LENGTH].copy_from_slice(&sk_b[..]); + arr[SECRET_KEY_LENGTH..].copy_from_slice(pk_enc.as_ref()); + f(Some(&*arr)) + }) } else { f(None) } @@ -241,24 +242,31 @@ impl ToJwk for K256KeyPair { impl FromJwk for K256KeyPair { fn from_jwk_parts(jwk: JwkParts<'_>) -> Result { - // SECURITY: ArrayKey zeroizes on drop - let mut pk_x = ArrayKey::::default(); - let mut pk_y = ArrayKey::::default(); - if jwk.x.decode_base64(pk_x.as_mut())? != pk_x.len() { - return Err(err_msg!(InvalidKeyData)); - } - if jwk.y.decode_base64(pk_y.as_mut())? != pk_y.len() { - return Err(err_msg!(InvalidKeyData)); - } + let pk_x = ArrayKey::::try_new_with(|arr| { + if jwk.x.decode_base64(arr)? != arr.len() { + Err(err_msg!(InvalidKeyData)) + } else { + Ok(()) + } + })?; + let pk_y = ArrayKey::::try_new_with(|arr| { + if jwk.y.decode_base64(arr)? != arr.len() { + Err(err_msg!(InvalidKeyData)) + } else { + Ok(()) + } + })?; let pk = EncodedPoint::from_affine_coordinates(pk_x.as_ref(), pk_y.as_ref(), false) .decode() .map_err(|_| err_msg!(InvalidKeyData))?; let sk = if jwk.d.is_some() { - let mut sk = ArrayKey::::default(); - if jwk.d.decode_base64(sk.as_mut())? != sk.len() { - return Err(err_msg!(InvalidKeyData)); - } - Some(SecretKey::from_bytes(sk.as_ref()).map_err(|_| err_msg!(InvalidKeyData))?) + Some(ArrayKey::::temp(|arr| { + if jwk.d.decode_base64(arr)? != arr.len() { + Err(err_msg!(InvalidKeyData)) + } else { + SecretKey::from_bytes(&*arr).map_err(|_| err_msg!(InvalidKeyData)) + } + })?) } else { None }; diff --git a/askar-crypto/src/alg/p256.rs b/askar-crypto/src/alg/p256.rs index 031e8c6c..ad4031f8 100644 --- a/askar-crypto/src/alg/p256.rs +++ b/askar-crypto/src/alg/p256.rs @@ -141,13 +141,14 @@ impl KeypairBytes for P256KeyPair { } fn with_keypair_bytes(&self, f: impl FnOnce(Option<&[u8]>) -> O) -> O { - if let Some(sk) = self.secret.as_ref() { - let mut buf = ArrayKey::<::KeypairSize>::default(); - let sk_b = p256::SecretBytes::from(sk.to_bytes()); - let pk_enc = EncodedPoint::encode(self.public, true); - buf.as_mut()[..SECRET_KEY_LENGTH].copy_from_slice(&sk_b[..]); - buf.as_mut()[SECRET_KEY_LENGTH..].copy_from_slice(pk_enc.as_ref()); - f(Some(buf.as_ref())) + if let Some(secret) = self.secret.as_ref() { + ArrayKey::<::KeypairSize>::temp(|arr| { + let sk_b = p256::SecretBytes::from(secret.to_bytes()); + let pk_enc = EncodedPoint::encode(self.public, true); + arr[..SECRET_KEY_LENGTH].copy_from_slice(&sk_b[..]); + arr[SECRET_KEY_LENGTH..].copy_from_slice(pk_enc.as_ref()); + f(Some(&*arr)) + }) } else { f(None) } @@ -241,24 +242,31 @@ impl ToJwk for P256KeyPair { impl FromJwk for P256KeyPair { fn from_jwk_parts(jwk: JwkParts<'_>) -> Result { - // SECURITY: ArrayKey zeroizes on drop - let mut pk_x = ArrayKey::::default(); - let mut pk_y = ArrayKey::::default(); - if jwk.x.decode_base64(pk_x.as_mut())? != pk_x.len() { - return Err(err_msg!(InvalidKeyData)); - } - if jwk.y.decode_base64(pk_y.as_mut())? != pk_y.len() { - return Err(err_msg!(InvalidKeyData)); - } + let pk_x = ArrayKey::::try_new_with(|arr| { + if jwk.x.decode_base64(arr)? != arr.len() { + Err(err_msg!(InvalidKeyData)) + } else { + Ok(()) + } + })?; + let pk_y = ArrayKey::::try_new_with(|arr| { + if jwk.y.decode_base64(arr)? != arr.len() { + Err(err_msg!(InvalidKeyData)) + } else { + Ok(()) + } + })?; let pk = EncodedPoint::from_affine_coordinates(pk_x.as_ref(), pk_y.as_ref(), false) .decode() .map_err(|_| err_msg!(InvalidKeyData))?; let sk = if jwk.d.is_some() { - let mut sk = ArrayKey::::default(); - if jwk.d.decode_base64(sk.as_mut())? != sk.len() { - return Err(err_msg!(InvalidKeyData)); - } - Some(SecretKey::from_bytes(sk.as_ref()).map_err(|_| err_msg!(InvalidKeyData))?) + Some(ArrayKey::::temp(|arr| { + if jwk.d.decode_base64(arr)? != arr.len() { + Err(err_msg!(InvalidKeyData)) + } else { + SecretKey::from_bytes(&*arr).map_err(|_| err_msg!(InvalidKeyData)) + } + })?) } else { None }; diff --git a/askar-crypto/src/alg/x25519.rs b/askar-crypto/src/alg/x25519.rs index 5ab52f17..702b94d9 100644 --- a/askar-crypto/src/alg/x25519.rs +++ b/askar-crypto/src/alg/x25519.rs @@ -15,7 +15,6 @@ use crate::{ generic_array::typenum::{U32, U64}, jwk::{FromJwk, JwkEncoder, JwkParts, ToJwk}, kdf::KeyExchange, - random::fill_random, repr::{KeyGen, KeyMeta, KeyPublicBytes, KeySecretBytes, KeypairBytes, KeypairMeta}, }; @@ -86,8 +85,7 @@ impl KeyMeta for X25519KeyPair { impl KeyGen for X25519KeyPair { fn generate() -> Result { - let mut sk = ArrayKey::::default(); - fill_random(sk.as_mut()); + let sk = ArrayKey::::random(); let sk = SecretKey::from( TryInto::<[u8; SECRET_KEY_LENGTH]>::try_into(&sk.as_ref()[..]).unwrap(), ); @@ -155,11 +153,12 @@ impl KeypairBytes for X25519KeyPair { fn with_keypair_bytes(&self, f: impl FnOnce(Option<&[u8]>) -> O) -> O { if let Some(secret) = self.secret.as_ref() { - let mut buf = ArrayKey::<::KeypairSize>::default(); - let b = Zeroizing::new(secret.to_bytes()); - buf.as_mut()[..SECRET_KEY_LENGTH].copy_from_slice(&b[..]); - buf.as_mut()[SECRET_KEY_LENGTH..].copy_from_slice(self.public.as_bytes()); - f(Some(buf.as_ref())) + ArrayKey::<::KeypairSize>::temp(|arr| { + let b = Zeroizing::new(secret.to_bytes()); + arr[..SECRET_KEY_LENGTH].copy_from_slice(&b[..]); + arr[SECRET_KEY_LENGTH..].copy_from_slice(self.public.as_bytes()); + f(Some(&*arr)) + }) } else { f(None) } @@ -202,22 +201,25 @@ impl ToJwk for X25519KeyPair { impl FromJwk for X25519KeyPair { fn from_jwk_parts(jwk: JwkParts<'_>) -> Result { - // SECURITY: ArrayKey zeroizes on drop - let mut pk = ArrayKey::::default(); - if jwk.x.decode_base64(pk.as_mut())? != pk.len() { - return Err(err_msg!(InvalidKeyData)); - } - let pk = PublicKey::from( - TryInto::<[u8; PUBLIC_KEY_LENGTH]>::try_into(&pk.as_ref()[..]).unwrap(), - ); - let sk = if jwk.d.is_some() { - let mut sk = ArrayKey::::default(); - if jwk.d.decode_base64(sk.as_mut())? != sk.len() { - return Err(err_msg!(InvalidKeyData)); + let pk = ArrayKey::::temp(|arr| { + if jwk.x.decode_base64(arr)? != arr.len() { + Err(err_msg!(InvalidKeyData)) + } else { + Ok(PublicKey::from( + TryInto::<[u8; PUBLIC_KEY_LENGTH]>::try_into(&*arr).unwrap(), + )) } - Some(SecretKey::from( - TryInto::<[u8; SECRET_KEY_LENGTH]>::try_into(&sk.as_ref()[..]).unwrap(), - )) + })?; + let sk = if jwk.d.is_some() { + Some(ArrayKey::::temp(|arr| { + if jwk.d.decode_base64(arr)? != arr.len() { + Err(err_msg!(InvalidKeyData)) + } else { + Ok(SecretKey::from( + TryInto::<[u8; SECRET_KEY_LENGTH]>::try_into(&*arr).unwrap(), + )) + } + })?) } else { None }; diff --git a/askar-crypto/src/buffer/array.rs b/askar-crypto/src/buffer/array.rs index 2ac1e2b8..4723c5d5 100644 --- a/askar-crypto/src/buffer/array.rs +++ b/askar-crypto/src/buffer/array.rs @@ -1,7 +1,7 @@ use core::{ - cmp::Ordering, fmt::{self, Debug, Formatter}, - marker::PhantomData, + marker::{PhantomData, PhantomPinned}, + ops::Deref, }; use crate::generic_array::{ArrayLength, GenericArray}; @@ -12,16 +12,35 @@ use crate::random::fill_random; /// A secure representation for fixed-length keys #[derive(Clone, Hash)] -pub struct ArrayKey>(GenericArray); +#[repr(transparent)] +pub struct ArrayKey>( + GenericArray, + // ensure that the type does not implement Unpin + PhantomPinned, +); impl> ArrayKey { /// The array length in bytes pub const SIZE: usize = L::USIZE; - /// Copy from a slice of the same length as this array - #[inline] - pub fn copy_from_slice>(&mut self, data: D) { - self.0[..].copy_from_slice(data.as_ref()); + /// Create a new buffer using an initializer for the data + pub fn new_with(f: impl FnOnce(&mut [u8])) -> Self { + let mut slf = Self::default(); + f(slf.0.as_mut()); + slf + } + + /// Create a new buffer using a fallible initializer for the data + pub fn try_new_with(f: impl FnOnce(&mut [u8]) -> Result<(), E>) -> Result { + let mut slf = Self::default(); + f(slf.0.as_mut())?; + Ok(slf) + } + + /// Temporarily allocate and use a key + pub fn temp(f: impl FnOnce(&mut [u8]) -> R) -> R { + let mut slf = Self::default(); + f(slf.0.as_mut()) } /// Convert this array to a non-zeroing GenericArray instance @@ -35,7 +54,7 @@ impl> ArrayKey { /// is incorrect. #[inline] pub fn from_slice(data: &[u8]) -> Self { - Self(GenericArray::from_slice(data).clone()) + Self::from(GenericArray::from_slice(data)) } /// Get the length of the array @@ -47,34 +66,44 @@ impl> ArrayKey { /// Create a new array of random bytes #[inline] pub fn random() -> Self { - let mut slf = GenericArray::default(); - fill_random(&mut slf); - Self(slf) + Self::new_with(fill_random) } } impl> AsRef> for ArrayKey { + #[inline(always)] fn as_ref(&self) -> &GenericArray { &self.0 } } -impl> AsMut> for ArrayKey { - fn as_mut(&mut self) -> &mut GenericArray { - &mut self.0 +impl> Deref for ArrayKey { + type Target = [u8]; + + #[inline(always)] + fn deref(&self) -> &[u8] { + self.0.as_ref() } } impl> Default for ArrayKey { - #[inline] + #[inline(always)] fn default() -> Self { - Self(GenericArray::default()) + Self(GenericArray::default(), PhantomPinned) + } +} + +impl> From<&GenericArray> for ArrayKey { + #[inline(always)] + fn from(key: &GenericArray) -> Self { + Self(key.clone(), PhantomPinned) } } impl> From> for ArrayKey { + #[inline(always)] fn from(key: GenericArray) -> Self { - Self(key) + Self(key, PhantomPinned) } } @@ -90,22 +119,12 @@ impl> Debug for ArrayKey { impl> PartialEq for ArrayKey { fn eq(&self, other: &Self) -> bool { + // FIXME implement constant-time equality? self.as_ref() == other.as_ref() } } impl> Eq for ArrayKey {} -impl> PartialOrd for ArrayKey { - fn partial_cmp(&self, other: &Self) -> Option { - self.0.partial_cmp(other.as_ref()) - } -} -impl> Ord for ArrayKey { - fn cmp(&self, other: &Self) -> Ordering { - self.0.cmp(other.as_ref()) - } -} - impl> Serialize for ArrayKey { fn serialize(&self, serializer: S) -> Result where @@ -154,8 +173,6 @@ impl<'de, L: ArrayLength> de::Visitor<'de> for KeyVisitor { if value.len() != L::USIZE { return Err(E::invalid_length(value.len(), &self)); } - let mut arr = ArrayKey::default(); - arr.as_mut().copy_from_slice(value); - Ok(arr) + Ok(ArrayKey::from_slice(value)) } } diff --git a/src/protect/kdf/argon2.rs b/src/protect/kdf/argon2.rs index 0afc0866..060271f6 100644 --- a/src/protect/kdf/argon2.rs +++ b/src/protect/kdf/argon2.rs @@ -56,10 +56,9 @@ impl Level { } pub fn derive_key(&self, password: &[u8], salt: &[u8]) -> Result { - let mut key = ArrayKey::<::KeySize>::default(); - Argon2::new(password, salt, *self.params())?.derive_key_bytes(key.as_mut())?; - Ok(StoreKey::from(StoreKeyType::from_secret_bytes( - key.as_ref(), - )?)) + ArrayKey::<::KeySize>::temp(|key| { + Argon2::new(password, salt, *self.params())?.derive_key_bytes(key)?; + Ok(StoreKey::from(StoreKeyType::from_secret_bytes(&*key)?)) + }) } } diff --git a/src/protect/kdf/mod.rs b/src/protect/kdf/mod.rs index 347e14e0..3bc4ed85 100644 --- a/src/protect/kdf/mod.rs +++ b/src/protect/kdf/mod.rs @@ -85,12 +85,9 @@ impl KdfMethod { fn parse_salt>(detail: &str) -> Result, Error> { let opts = Options::parse_uri(detail)?; if let Some(salt) = opts.query.get("salt") { - let mut salt_arr = ArrayKey::::default(); - if hex::decode_to_slice(salt, salt_arr.as_mut()).is_ok() { - Ok(salt_arr) - } else { - Err(err_msg!(Input, "Invalid salt")) - } + ArrayKey::::try_new_with(|arr| { + hex::decode_to_slice(salt, arr).map_err(|_| err_msg!(Input, "Invalid salt")) + }) } else { Err(err_msg!(Input, "Missing salt")) } diff --git a/src/protect/profile_key.rs b/src/protect/profile_key.rs index 199caa38..c73eb735 100644 --- a/src/protect/profile_key.rs +++ b/src/protect/profile_key.rs @@ -86,11 +86,12 @@ where enc_key: &Key, hmac_key: &HmacKey, ) -> Result, Error> { - let mut nonce = ArrayKey::::default(); - hmac_key.hmac_to(&[buffer.as_ref()], nonce.as_mut())?; - enc_key.encrypt_in_place(&mut buffer, nonce.as_ref(), &[])?; - buffer.buffer_insert(0, nonce.as_ref())?; - Ok(buffer.into_vec()) + ArrayKey::::temp(|nonce| { + hmac_key.hmac_to(&[buffer.as_ref()], nonce)?; + enc_key.encrypt_in_place(&mut buffer, nonce.as_ref(), &[])?; + buffer.buffer_insert(0, nonce.as_ref())?; + Ok(buffer.into_vec()) + }) } pub fn encrypt(mut buffer: SecretBytes, enc_key: &Key) -> Result, Error> { @@ -183,10 +184,11 @@ where name: &[u8], value: SecretBytes, ) -> Result, Error> { - let mut value_key = ArrayKey::::default(); - self.derive_value_key(category, name, value_key.as_mut())?; - let value_key = Key::from_secret_bytes(value_key.as_ref())?; - Self::encrypt(value, &value_key) + ArrayKey::::temp(|value_key| { + self.derive_value_key(category, name, value_key)?; + let value_key = Key::from_secret_bytes(&*value_key)?; + Self::encrypt(value, &value_key) + }) } fn decrypt_entry_category(&self, enc_category: Vec) -> Result { @@ -203,10 +205,11 @@ where name: &[u8], enc_value: Vec, ) -> Result { - let mut value_key = ArrayKey::::default(); - self.derive_value_key(category, name, value_key.as_mut())?; - let value_key = Key::from_secret_bytes(value_key.as_ref())?; - Self::decrypt(enc_value, &value_key) + ArrayKey::::temp(|value_key| { + self.derive_value_key(category, name, value_key)?; + let value_key = Key::from_secret_bytes(&*value_key)?; + Self::decrypt(enc_value, &value_key) + }) } fn encrypt_entry_tags(&self, tags: Vec) -> Result, Error> { diff --git a/src/protect/store_key.rs b/src/protect/store_key.rs index db5616a1..fdbe6389 100644 --- a/src/protect/store_key.rs +++ b/src/protect/store_key.rs @@ -30,17 +30,16 @@ pub fn generate_raw_store_key(seed: Option<&[u8]>) -> Result, E } pub fn parse_raw_store_key(raw_key: &str) -> Result { - let mut key = ArrayKey::<::KeySize>::default(); - let key_len = bs58::decode(raw_key) - .into(key.as_mut()) - .map_err(|_| err_msg!(Input, "Error parsing raw key as base58 value"))?; - if key_len != key.len() { - Err(err_msg!(Input, "Incorrect length for encoded raw key")) - } else { - Ok(StoreKey::from(StoreKeyType::from_secret_bytes( - key.as_ref(), - )?)) - } + ArrayKey::<::KeySize>::temp(|key| { + let key_len = bs58::decode(raw_key) + .into(&mut *key) + .map_err(|_| err_msg!(Input, "Error parsing raw key as base58 value"))?; + if key_len != key.len() { + Err(err_msg!(Input, "Incorrect length for encoded raw key")) + } else { + Ok(StoreKey::from(StoreKeyType::from_secret_bytes(&*key)?)) + } + }) } #[derive(Clone, Debug)] From 372a0d076986ff15e2048708cd96e546f2ba3d17 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Mon, 3 May 2021 17:07:54 -0700 Subject: [PATCH 087/116] implement BLS key representation Signed-off-by: Andrew Whitehead --- askar-crypto/Cargo.toml | 2 + askar-crypto/src/alg/bls.rs | 467 ++++++++++++++++++++++++++++++ askar-crypto/src/alg/mod.rs | 18 ++ askar-crypto/src/buffer/array.rs | 7 + askar-crypto/src/buffer/secret.rs | 7 +- src/protect/kdf/mod.rs | 7 +- 6 files changed, 502 insertions(+), 6 deletions(-) create mode 100644 askar-crypto/src/alg/bls.rs diff --git a/askar-crypto/Cargo.toml b/askar-crypto/Cargo.toml index 2a9337a5..5b8274ed 100644 --- a/askar-crypto/Cargo.toml +++ b/askar-crypto/Cargo.toml @@ -40,6 +40,7 @@ aes-gcm = { version = "0.8", default-features = false, features = ["aes"] } argon2 = { version = "0.1", default-features = false, features = ["password-hash"] } base64 = { version = "0.13", default-features = false } blake2 = { version = "0.9", default-features = false } +bls12_381 = { version = "0.4", default-features = false, features = ["groups"] } chacha20 = "0.6" # inherit from chacha20poly1305 chacha20poly1305 = { version = "0.7", default-features = false, features = ["chacha20"] } crypto_box = { version = "0.5", default-features = false, features = ["u64_backend"] } @@ -47,6 +48,7 @@ curve25519-dalek = { version = "3.0", default-features = false, features = ["u64 ed25519-dalek = { version = "1.0", default-features = false, features = ["u64_backend"] } digest = "0.9" group = "0.9" +hkdf = "0.11" k256 = { version = "0.7", default-features = false, features = ["arithmetic", "ecdsa", "ecdh", "sha256", "zeroize"] } p256 = { version = "0.7", default-features = false, features = ["arithmetic", "ecdsa", "ecdh", "zeroize"] } rand = { version = "0.7", default-features = false, features = ["getrandom"] } diff --git a/askar-crypto/src/alg/bls.rs b/askar-crypto/src/alg/bls.rs new file mode 100644 index 00000000..76803912 --- /dev/null +++ b/askar-crypto/src/alg/bls.rs @@ -0,0 +1,467 @@ +//! BLS12-381 key support + +use core::{ + convert::TryInto, + fmt::{self, Debug, Formatter}, + ops::Add, +}; + +use blake2::Digest; +use bls12_381::{G1Affine, G1Projective, G2Affine, G2Projective, Scalar}; +use group::GroupEncoding; +use sha2::Sha256; +use zeroize::Zeroizing; + +use crate::generic_array::{ + typenum::{self, Unsigned, U144, U32, U48, U96}, + ArrayLength, +}; + +use super::{BlsTypes, HasKeyAlg, KeyAlg}; +use crate::{ + buffer::ArrayKey, + error::Error, + jwk::{FromJwk, JwkEncoder, JwkParts, ToJwk}, + random::fill_random, + repr::{KeyGen, KeyMeta, KeyPublicBytes, KeySecretBytes, KeypairMeta}, +}; + +/// The 'kty' value of a BLS key JWK +pub const JWK_KEY_TYPE: &'static str = "EC"; + +/// A BLS12-381 key pair +#[derive(Clone)] +pub struct BlsKeyPair { + secret: Option, + public: Pk::Buffer, +} + +impl BlsKeyPair { + #[inline] + fn from_secret_key(sk: BlsSecretKey) -> Self { + let public = Pk::from_secret_scalar(&sk.0); + Self { + secret: Some(sk), + public, + } + } +} + +impl Debug for BlsKeyPair { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("BlsKeyPair") + .field("crv", &Pk::JWK_CURVE) + .field("secret", &self.secret) + .field("public", &self.public) + .finish() + } +} + +impl PartialEq for BlsKeyPair { + fn eq(&self, other: &Self) -> bool { + other.secret == self.secret && other.public == self.public + } +} + +impl Eq for BlsKeyPair {} + +impl HasKeyAlg for BlsKeyPair { + fn algorithm(&self) -> KeyAlg { + KeyAlg::Bls12_381(Pk::ALG_TYPE) + } +} + +impl KeyMeta for BlsKeyPair { + type KeySize = U32; +} + +impl KeypairMeta for BlsKeyPair +where + Pk: BlsPublicKeyType, + U32: Add, + >::Output: ArrayLength, +{ + type PublicKeySize = Pk::BufferSize; + type KeypairSize = typenum::Sum; +} + +impl KeyGen for BlsKeyPair { + fn generate() -> Result { + let secret = BlsSecretKey::generate()?; + Ok(Self::from_secret_key(secret)) + } +} + +impl KeySecretBytes for BlsKeyPair { + fn from_secret_bytes(key: &[u8]) -> Result + where + Self: Sized, + { + let sk = BlsSecretKey::from_bytes(key)?; + Ok(Self::from_secret_key(sk)) + } + + fn with_secret_bytes(&self, f: impl FnOnce(Option<&[u8]>) -> O) -> O { + if let Some(sk) = self.secret.as_ref() { + let mut skb = Zeroizing::new(sk.0.to_bytes()); + skb.reverse(); // into big-endian + f(Some(&*skb)) + } else { + f(None) + } + } +} + +impl KeyPublicBytes for BlsKeyPair { + fn from_public_bytes(key: &[u8]) -> Result { + Ok(Self { + secret: None, + public: Pk::from_public_bytes(key)?, + }) + } + + fn with_public_bytes(&self, f: impl FnOnce(&[u8]) -> O) -> O { + Pk::with_bytes(&self.public, f) + } +} + +impl ToJwk for BlsKeyPair { + fn encode_jwk(&self, enc: &mut JwkEncoder<'_>) -> Result<(), Error> { + enc.add_str("crv", Pk::JWK_CURVE)?; + enc.add_str("kty", JWK_KEY_TYPE)?; + self.with_public_bytes(|buf| enc.add_as_base64("x", buf))?; + if enc.is_secret() { + self.with_secret_bytes(|buf| { + if let Some(sk) = buf { + enc.add_as_base64("d", sk) + } else { + Ok(()) + } + })?; + } + Ok(()) + } +} + +impl FromJwk for BlsKeyPair { + fn from_jwk_parts(jwk: JwkParts<'_>) -> Result { + let pk = ArrayKey::::temp(|arr| { + if jwk.x.decode_base64(arr)? != arr.len() { + Err(err_msg!(InvalidKeyData)) + } else { + Pk::from_public_bytes(arr) + } + })?; + let sk = if jwk.d.is_some() { + Some(ArrayKey::::temp(|arr| { + if jwk.d.decode_base64(arr)? != arr.len() { + Err(err_msg!(InvalidKeyData)) + } else { + BlsSecretKey::from_bytes(arr) + } + })?) + } else { + None + }; + Ok(Self { + secret: sk, + public: pk, + }) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +#[repr(transparent)] +struct BlsSecretKey(Scalar); + +impl BlsSecretKey { + pub fn generate() -> Result { + let mut secret = Zeroizing::new([0u8; 64]); + fill_random(&mut secret[..]); + Ok(Self(Scalar::from_bytes_wide(&secret))) + } + + // FIXME - this is the draft 7 version, + // draft 10 hashes the salt and loops until a non-zero SK is found + pub fn from_seed(ikm: &[u8]) -> Result { + const SALT: &[u8] = b"BLS-SIG-KEYGEN-SALT-"; + + let mut salt = Sha256::digest(SALT); + Ok(Self(loop { + let mut okm = Zeroizing::new([0u8; 64]); + let mut extract = hkdf::HkdfExtract::::new(Some(salt.as_ref())); + extract.input_ikm(ikm); + extract.input_ikm(&[0u8]); + let (_, hkdf) = extract.finalize(); + hkdf.expand(&(48 as u16).to_be_bytes(), &mut okm[16..]) + .expect("HDKF extract failure"); + okm.reverse(); // into little endian + let scalar = Scalar::from_bytes_wide(&okm); + if scalar != Scalar::zero() { + break scalar; + } + salt = Sha256::digest(salt.as_ref()); + })) + } + + pub fn from_bytes(sk: &[u8]) -> Result { + if sk.len() != 32 { + return Err(err_msg!(InvalidKeyData)); + } + let mut skb = Zeroizing::new([0u8; 32]); + skb.copy_from_slice(sk); + skb.reverse(); // into little endian + let result: Option = Scalar::from_bytes(&skb).into(); + Ok(Self(result.ok_or_else(|| err_msg!(InvalidKeyData))?)) + } + + // pub fn to_bytes(&self) -> SecretBytes { + // let mut skb = self.0.to_bytes(); + // // turn into big-endian format + // skb.reverse(); + // let v = skb.to_vec(); + // skb.zeroize(); + // SecretBytes::from(v) + // } +} + +/// A common trait among supported ChaCha20 key types +pub trait BlsPublicKeyType: 'static { + // /// The AEAD implementation + // type Aead: NewAead + Aead + AeadInPlace; + + /// The concrete key representation + type Buffer: Clone + Debug + PartialEq + Sized; + + /// The size of the serialized public key + type BufferSize: ArrayLength; + + /// The associated algorithm type + const ALG_TYPE: BlsTypes; + /// The associated JWK curve name + const JWK_CURVE: &'static str; + + /// Initialize from the secret scalar + fn from_secret_scalar(secret: &Scalar) -> Self::Buffer; + + /// Initialize from the compressed bytes + fn from_public_bytes(key: &[u8]) -> Result; + + /// Access the bytes of the public key + fn with_bytes(buf: &Self::Buffer, f: impl FnOnce(&[u8]) -> O) -> O; +} + +/// G1 curve +#[derive(Debug)] +pub struct G1; + +impl BlsPublicKeyType for G1 { + type Buffer = G1Affine; + type BufferSize = U48; + + const ALG_TYPE: BlsTypes = BlsTypes::G1; + const JWK_CURVE: &'static str = "BLS12381_G1"; + + #[inline] + fn from_secret_scalar(secret: &Scalar) -> Self::Buffer { + G1Affine::from(G1Projective::generator() * secret) + } + + fn from_public_bytes(key: &[u8]) -> Result { + let buf: Option = G1Affine::from_compressed( + TryInto::<&[u8; 48]>::try_into(key).map_err(|_| err_msg!(InvalidKeyData))?, + ) + .into(); + buf.ok_or_else(|| err_msg!(InvalidKeyData)) + } + + fn with_bytes(buf: &Self::Buffer, f: impl FnOnce(&[u8]) -> O) -> O { + f(buf.to_bytes().as_ref()) + } +} + +/// G2 curve +#[derive(Debug)] +pub struct G2; + +impl BlsPublicKeyType for G2 { + type Buffer = G2Affine; + type BufferSize = U96; + + const ALG_TYPE: BlsTypes = BlsTypes::G2; + const JWK_CURVE: &'static str = "BLS12381_G2"; + + #[inline] + fn from_secret_scalar(secret: &Scalar) -> Self::Buffer { + G2Affine::from(G2Projective::generator() * secret) + } + + fn from_public_bytes(key: &[u8]) -> Result { + let buf: Option = G2Affine::from_compressed( + TryInto::<&[u8; 96]>::try_into(key).map_err(|_| err_msg!(InvalidKeyData))?, + ) + .into(); + buf.ok_or_else(|| err_msg!(InvalidKeyData)) + } + + fn with_bytes(buf: &Self::Buffer, f: impl FnOnce(&[u8]) -> O) -> O { + f(buf.to_bytes().as_ref()) + } +} + +/// G1 + G2 curves +#[derive(Debug)] +pub struct G1G2; + +impl BlsPublicKeyType for G1G2 { + type Buffer = (G1Affine, G2Affine); + type BufferSize = U144; + + const ALG_TYPE: BlsTypes = BlsTypes::G1G2; + const JWK_CURVE: &'static str = "BLS12381_G1G2"; + + #[inline] + fn from_secret_scalar(secret: &Scalar) -> Self::Buffer { + ( + G1Affine::from(G1Projective::generator() * secret), + G2Affine::from(G2Projective::generator() * secret), + ) + } + + fn from_public_bytes(key: &[u8]) -> Result { + if key.len() != Self::BufferSize::USIZE { + return Err(err_msg!(InvalidKeyData)); + } + let g1: Option = + G1Affine::from_compressed(TryInto::<&[u8; 48]>::try_into(&key[..48]).unwrap()).into(); + let g2: Option = + G2Affine::from_compressed(TryInto::<&[u8; 96]>::try_into(&key[48..]).unwrap()).into(); + if let (Some(g1), Some(g2)) = (g1, g2) { + Ok((g1, g2)) + } else { + Err(err_msg!(InvalidKeyData)) + } + } + + fn with_bytes(buf: &Self::Buffer, f: impl FnOnce(&[u8]) -> O) -> O { + ArrayKey::::temp(|arr| { + arr[0..48].copy_from_slice(buf.0.to_bytes().as_ref()); + arr[48..].copy_from_slice(buf.1.to_bytes().as_ref()); + f(&arr[..]) + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::string::ToString; + + // test against EIP-2333 (updated for signatures draft 4) + #[test] + fn key_gen_expected() { + let seed = &hex!( + "c55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e5349553 + 1f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04" + ); + let sk = BlsSecretKey::from_seed(&seed[..]).unwrap(); + let kp = BlsKeyPair::::from_secret_key(sk); + let sk = kp.to_secret_bytes().unwrap(); + assert_eq!( + sk.as_hex().to_string(), + "0d7359d57963ab8fbbde1852dcf553fedbc31f464d80ee7d40ae683122b45070" + ); + } + + #[test] + fn g1_key_expected() { + let sk = hex!("0d7359d57963ab8fbbde1852dcf553fedbc31f464d80ee7d40ae683122b45070"); + let kp = BlsKeyPair::::from_secret_bytes(&sk[..]).unwrap(); + let pk = kp.to_public_bytes().unwrap(); + assert_eq!( + pk.as_hex().to_string(), + "a2c975348667926acf12f3eecb005044e08a7a9b7d95f30bd281b55445107367a2e5d0558be7943c8bd13f9a1a7036fb" + ); + assert_eq!( + BlsKeyPair::::from_public_bytes(pk.as_ref()) + .unwrap() + .to_public_bytes() + .unwrap(), + pk + ); + } + + #[test] + fn g2_key_expected() { + let sk = hex!("0d7359d57963ab8fbbde1852dcf553fedbc31f464d80ee7d40ae683122b45070"); + let kp = BlsKeyPair::::from_secret_bytes(&sk[..]).unwrap(); + let pk = kp.to_public_bytes().unwrap(); + assert_eq!( + pk.as_hex().to_string(), + "a5e43d5ecb7b8c01ceb3b91f7413b628ef02c6859dc42a4354b21f9195531988a648655037faafd1bac2fd2d7d9466180baa3705a45a6c597853db51eaf431616057fd8049c6bee8764292f9a104200a45a63ceae9d3c368643ab9e5ff0f8810" + ); + assert_eq!( + BlsKeyPair::::from_public_bytes(pk.as_ref()) + .unwrap() + .to_public_bytes() + .unwrap(), + pk + ); + } + + #[test] + fn g1g2_key_expected() { + let sk = hex!("0d7359d57963ab8fbbde1852dcf553fedbc31f464d80ee7d40ae683122b45070"); + let kp = BlsKeyPair::::from_secret_bytes(&sk[..]).unwrap(); + let pk = kp.to_public_bytes().unwrap(); + assert_eq!( + pk.as_hex().to_string(), + "a2c975348667926acf12f3eecb005044e08a7a9b7d95f30bd281b55445107367a2e5d0558be7943c8bd13f9a1a7036fb\ + a5e43d5ecb7b8c01ceb3b91f7413b628ef02c6859dc42a4354b21f9195531988a648655037faafd1bac2fd2d7d9466180baa3705a45a6c597853db51eaf431616057fd8049c6bee8764292f9a104200a45a63ceae9d3c368643ab9e5ff0f8810" + ); + assert_eq!( + BlsKeyPair::::from_public_bytes(pk.as_ref()) + .unwrap() + .to_public_bytes() + .unwrap(), + pk + ); + } + + #[test] + fn jwk_expected() { + let test_pvt = &hex!("0d7359d57963ab8fbbde1852dcf553fedbc31f464d80ee7d40ae683122b45070"); + let test_pub_g1 = &hex!("a2c975348667926acf12f3eecb005044e08a7a9b7d95f30bd281b55445107367a2e5d0558be7943c8bd13f9a1a7036fb"); + let kp = BlsKeyPair::::from_secret_bytes(&test_pvt[..]).expect("Error creating key"); + + let jwk = kp.to_jwk_public().expect("Error converting key to JWK"); + let jwk = JwkParts::from_str(&jwk).expect("Error parsing JWK"); + assert_eq!(jwk.kty, "EC"); + assert_eq!(jwk.crv, G1::JWK_CURVE); + assert_eq!( + jwk.x, + base64::encode_config(test_pub_g1, base64::URL_SAFE_NO_PAD).as_str() + ); + assert_eq!(jwk.d, None); + let pk_load = BlsKeyPair::::from_jwk_parts(jwk).unwrap(); + assert_eq!(kp.to_public_bytes(), pk_load.to_public_bytes()); + + let jwk = kp.to_jwk_secret().expect("Error converting key to JWK"); + let jwk = JwkParts::from_slice(&jwk).expect("Error parsing JWK"); + assert_eq!(jwk.kty, "EC"); + assert_eq!(jwk.crv, G1::JWK_CURVE); + assert_eq!( + jwk.x, + base64::encode_config(test_pub_g1, base64::URL_SAFE_NO_PAD).as_str() + ); + assert_eq!( + jwk.d, + base64::encode_config(test_pvt, base64::URL_SAFE_NO_PAD).as_str() + ); + let _sk_load = BlsKeyPair::::from_jwk_parts(jwk).unwrap(); + // assert_eq!( + // kp.to_keypair_bytes().unwrap(), + // sk_load.to_keypair_bytes().unwrap() + // ); + } +} diff --git a/askar-crypto/src/alg/mod.rs b/askar-crypto/src/alg/mod.rs index 71b39815..107797ad 100644 --- a/askar-crypto/src/alg/mod.rs +++ b/askar-crypto/src/alg/mod.rs @@ -20,6 +20,8 @@ pub use any::{AnyKey, AnyKeyCreate}; pub mod aesgcm; +pub mod bls; + pub mod chacha20; pub mod ed25519; @@ -34,6 +36,8 @@ pub mod p256; pub enum KeyAlg { /// AES Aes(AesTypes), + /// BLS12-381 + Bls12_381(BlsTypes), /// (X)ChaCha20-Poly1305 Chacha20(Chacha20Types), /// Curve25519 signing key @@ -50,6 +54,9 @@ impl KeyAlg { match self { Self::Aes(AesTypes::A128GCM) => "a128gcm", Self::Aes(AesTypes::A256GCM) => "a256gcm", + Self::Bls12_381(BlsTypes::G1) => "bls12381g1", + Self::Bls12_381(BlsTypes::G2) => "bls12381g2", + Self::Bls12_381(BlsTypes::G1G2) => "bls12381g1g2", Self::Chacha20(Chacha20Types::C20P) => "c20p", Self::Chacha20(Chacha20Types::XC20P) => "xc20p", Self::Ed25519 => "ed25519", @@ -166,6 +173,17 @@ pub enum AesTypes { A256GCM, } +/// Supported public key types for Bls12_381 +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Zeroize)] +pub enum BlsTypes { + /// G1 curve + G1, + /// G2 curve + G2, + /// G1 + G2 curves + G1G2, +} + /// Supported algorithms for (X)ChaCha20-Poly1305 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Zeroize)] pub enum Chacha20Types { diff --git a/askar-crypto/src/buffer/array.rs b/askar-crypto/src/buffer/array.rs index 4723c5d5..eb3c6c60 100644 --- a/askar-crypto/src/buffer/array.rs +++ b/askar-crypto/src/buffer/array.rs @@ -10,6 +10,8 @@ use zeroize::Zeroize; use crate::random::fill_random; +use super::HexRepr; + /// A secure representation for fixed-length keys #[derive(Clone, Hash)] #[repr(transparent)] @@ -68,6 +70,11 @@ impl> ArrayKey { pub fn random() -> Self { Self::new_with(fill_random) } + + /// Get a hex formatter for the key data + pub fn as_hex(&self) -> HexRepr<&[u8]> { + HexRepr(self.0.as_ref()) + } } impl> AsRef> for ArrayKey { diff --git a/askar-crypto/src/buffer/secret.rs b/askar-crypto/src/buffer/secret.rs index 50832750..257e1d0d 100644 --- a/askar-crypto/src/buffer/secret.rs +++ b/askar-crypto/src/buffer/secret.rs @@ -8,7 +8,7 @@ use core::{ use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use zeroize::Zeroize; -use super::{string::MaybeStr, ResizeBuffer, WriteBuffer}; +use super::{string::MaybeStr, HexRepr, ResizeBuffer, WriteBuffer}; use crate::error::Error; /// A heap-allocated, zeroized byte buffer @@ -128,6 +128,11 @@ impl SecretBytes { self.0.splice(range, iter); Ok(()) } + + /// Get a hex formatter for the secret data + pub fn as_hex(&self) -> HexRepr<&[u8]> { + HexRepr(self.0.as_ref()) + } } impl Debug for SecretBytes { diff --git a/src/protect/kdf/mod.rs b/src/protect/kdf/mod.rs index 3bc4ed85..a04ff2ef 100644 --- a/src/protect/kdf/mod.rs +++ b/src/protect/kdf/mod.rs @@ -1,9 +1,6 @@ use super::store_key::{StoreKey, PREFIX_KDF}; use crate::{ - crypto::{ - buffer::{ArrayKey, HexRepr}, - generic_array::ArrayLength, - }, + crypto::{buffer::ArrayKey, generic_array::ArrayLength}, error::Error, storage::Options, }; @@ -65,7 +62,7 @@ impl KdfMethod { Self::Argon2i(level) => { let salt = level.generate_salt(); let key = level.derive_key(password.as_bytes(), salt.as_ref())?; - let detail = format!("?salt={}", HexRepr(salt.as_ref())); + let detail = format!("?salt={}", salt.as_hex()); Ok((key.into(), detail)) } } From 9c9117e70b28df3af7bab669a96aab5ee4e2b57f Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Mon, 3 May 2021 17:29:07 -0700 Subject: [PATCH 088/116] update rand, k256, p256 versions Signed-off-by: Andrew Whitehead --- askar-crypto/Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/askar-crypto/Cargo.toml b/askar-crypto/Cargo.toml index 5b8274ed..5de1e98b 100644 --- a/askar-crypto/Cargo.toml +++ b/askar-crypto/Cargo.toml @@ -49,9 +49,9 @@ ed25519-dalek = { version = "1.0", default-features = false, features = ["u64_ba digest = "0.9" group = "0.9" hkdf = "0.11" -k256 = { version = "0.7", default-features = false, features = ["arithmetic", "ecdsa", "ecdh", "sha256", "zeroize"] } -p256 = { version = "0.7", default-features = false, features = ["arithmetic", "ecdsa", "ecdh", "zeroize"] } -rand = { version = "0.7", default-features = false, features = ["getrandom"] } +k256 = { version = "0.8", default-features = false, features = ["arithmetic", "ecdsa", "ecdh", "sha256", "zeroize"] } +p256 = { version = "0.8", default-features = false, features = ["arithmetic", "ecdsa", "ecdh", "zeroize"] } +rand = { version = "0.8", default-features = false, features = ["getrandom"] } serde = { version = "1.0", default-features = false, features = ["derive"] } serde_json = { version = "1.0", default-features = false } sha2 = { version = "0.9", default-features = false } From c2d2320b6aa837ea4036e342314a58fbfa75cb09 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Mon, 3 May 2021 17:29:18 -0700 Subject: [PATCH 089/116] from_seed cleanups Signed-off-by: Andrew Whitehead --- askar-crypto/src/alg/aesgcm.rs | 8 ++++++++ askar-crypto/src/alg/bls.rs | 17 ++++++----------- askar-crypto/src/alg/chacha20.rs | 4 +--- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/askar-crypto/src/alg/aesgcm.rs b/askar-crypto/src/alg/aesgcm.rs index e8d20b25..92858658 100644 --- a/askar-crypto/src/alg/aesgcm.rs +++ b/askar-crypto/src/alg/aesgcm.rs @@ -15,6 +15,7 @@ use crate::{ generic_array::{typenum::Unsigned, GenericArray}, jwk::{JwkEncoder, ToJwk}, kdf::{FromKeyDerivation, FromKeyExchange, KeyDerivation, KeyExchange}, + random::fill_random_deterministic, repr::{KeyGen, KeyMeta, KeySecretBytes}, }; @@ -79,6 +80,13 @@ impl AesGcmKey { pub const NONCE_LENGTH: usize = NonceSize::::USIZE; /// The length of the AEAD encryption tag pub const TAG_LENGTH: usize = TagSize::::USIZE; + + /// Construct a new deterministic AES key from a seed value + pub fn from_seed(seed: &[u8]) -> Result { + Ok(Self(KeyType::::try_new_with(|arr| { + fill_random_deterministic(seed, arr) + })?)) + } } impl Clone for AesGcmKey { diff --git a/askar-crypto/src/alg/bls.rs b/askar-crypto/src/alg/bls.rs index 76803912..cd8751a8 100644 --- a/askar-crypto/src/alg/bls.rs +++ b/askar-crypto/src/alg/bls.rs @@ -37,6 +37,11 @@ pub struct BlsKeyPair { } impl BlsKeyPair { + /// Create a new deterministic BLS keypair according to the KeyGen algorithm + pub fn from_seed(ikm: &[u8]) -> Result { + Ok(Self::from_secret_key(BlsSecretKey::from_seed(ikm)?)) + } + #[inline] fn from_secret_key(sk: BlsSecretKey) -> Self { let public = Pk::from_secret_scalar(&sk.0); @@ -181,8 +186,7 @@ impl BlsSecretKey { Ok(Self(Scalar::from_bytes_wide(&secret))) } - // FIXME - this is the draft 7 version, - // draft 10 hashes the salt and loops until a non-zero SK is found + // bls-signatures draft 4 version (incompatible with earlier) pub fn from_seed(ikm: &[u8]) -> Result { const SALT: &[u8] = b"BLS-SIG-KEYGEN-SALT-"; @@ -214,15 +218,6 @@ impl BlsSecretKey { let result: Option = Scalar::from_bytes(&skb).into(); Ok(Self(result.ok_or_else(|| err_msg!(InvalidKeyData))?)) } - - // pub fn to_bytes(&self) -> SecretBytes { - // let mut skb = self.0.to_bytes(); - // // turn into big-endian format - // skb.reverse(); - // let v = skb.to_vec(); - // skb.zeroize(); - // SecretBytes::from(v) - // } } /// A common trait among supported ChaCha20 key types diff --git a/askar-crypto/src/alg/chacha20.rs b/askar-crypto/src/alg/chacha20.rs index 21b9db4a..eed87769 100644 --- a/askar-crypto/src/alg/chacha20.rs +++ b/askar-crypto/src/alg/chacha20.rs @@ -81,9 +81,7 @@ impl Chacha20Key { /// The length of the AEAD encryption tag pub const TAG_LENGTH: usize = TagSize::::USIZE; - /// Construct a new ChaCha20 key from a seed value - // this is consistent with Indy's wallet wrapping key generation - // FIXME - move to aries_askar, use from_key_secret_bytes + /// Construct a new deterministic ChaCha20 key from a seed value pub fn from_seed(seed: &[u8]) -> Result { Ok(Self(KeyType::::try_new_with(|arr| { fill_random_deterministic(seed, arr) From 00a5ed98264feab060f2a2f168fbb73a2cb4bca3 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Mon, 3 May 2021 17:39:37 -0700 Subject: [PATCH 090/116] create G1 or G2 key from G1G2 key Signed-off-by: Andrew Whitehead --- askar-crypto/src/alg/bls.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/askar-crypto/src/alg/bls.rs b/askar-crypto/src/alg/bls.rs index cd8751a8..40f4830e 100644 --- a/askar-crypto/src/alg/bls.rs +++ b/askar-crypto/src/alg/bls.rs @@ -347,6 +347,24 @@ impl BlsPublicKeyType for G1G2 { } } +impl From<&BlsKeyPair> for BlsKeyPair { + fn from(kp: &BlsKeyPair) -> Self { + BlsKeyPair { + secret: kp.secret.clone(), + public: kp.public.0.clone(), + } + } +} + +impl From<&BlsKeyPair> for BlsKeyPair { + fn from(kp: &BlsKeyPair) -> Self { + BlsKeyPair { + secret: kp.secret.clone(), + public: kp.public.1.clone(), + } + } +} + #[cfg(test)] mod tests { use super::*; From d522154ffd6f515ed16bc807bb940b867a1666b3 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Mon, 3 May 2021 18:06:29 -0700 Subject: [PATCH 091/116] add BLS key support to AnyKey and python wrapper Signed-off-by: Andrew Whitehead --- askar-crypto/src/alg/any.rs | 58 +++++++++++++++++++++++++++- askar-crypto/src/alg/mod.rs | 3 ++ wrappers/python/aries_askar/types.py | 3 ++ wrappers/python/demo/test.py | 3 ++ 4 files changed, 65 insertions(+), 2 deletions(-) diff --git a/askar-crypto/src/alg/any.rs b/askar-crypto/src/alg/any.rs index 2920b3c7..b213f08f 100644 --- a/askar-crypto/src/alg/any.rs +++ b/askar-crypto/src/alg/any.rs @@ -6,12 +6,13 @@ use core::{ }; use super::aesgcm::{AesGcmKey, A128GCM, A256GCM}; +use super::bls::{BlsKeyPair, BlsPublicKeyType, G1, G1G2, G2}; use super::chacha20::{Chacha20Key, C20P, XC20P}; use super::ed25519::{self, Ed25519KeyPair}; use super::k256::{self, K256KeyPair}; use super::p256::{self, P256KeyPair}; use super::x25519::{self, X25519KeyPair}; -use super::{AesTypes, Chacha20Types, EcCurves, HasKeyAlg, KeyAlg}; +use super::{AesTypes, BlsTypes, Chacha20Types, EcCurves, HasKeyAlg, KeyAlg}; use crate::{ buffer::{ResizeBuffer, SecretBytes, WriteBuffer}, encrypt::{KeyAeadInPlace, KeyAeadParams}, @@ -156,6 +157,9 @@ fn generate_any(alg: KeyAlg) -> Result { match alg { KeyAlg::Aes(AesTypes::A128GCM) => AesGcmKey::::generate().map(R::alloc_key), KeyAlg::Aes(AesTypes::A256GCM) => AesGcmKey::::generate().map(R::alloc_key), + KeyAlg::Bls12_381(BlsTypes::G1) => BlsKeyPair::::generate().map(R::alloc_key), + KeyAlg::Bls12_381(BlsTypes::G2) => BlsKeyPair::::generate().map(R::alloc_key), + KeyAlg::Bls12_381(BlsTypes::G1G2) => BlsKeyPair::::generate().map(R::alloc_key), KeyAlg::Chacha20(Chacha20Types::C20P) => Chacha20Key::::generate().map(R::alloc_key), KeyAlg::Chacha20(Chacha20Types::XC20P) => { Chacha20Key::::generate().map(R::alloc_key) @@ -177,6 +181,15 @@ fn generate_any(alg: KeyAlg) -> Result { #[inline] fn from_public_bytes_any(alg: KeyAlg, public: &[u8]) -> Result { match alg { + KeyAlg::Bls12_381(BlsTypes::G1) => { + BlsKeyPair::::from_public_bytes(public).map(R::alloc_key) + } + KeyAlg::Bls12_381(BlsTypes::G2) => { + BlsKeyPair::::from_public_bytes(public).map(R::alloc_key) + } + KeyAlg::Bls12_381(BlsTypes::G1G2) => { + BlsKeyPair::::from_public_bytes(public).map(R::alloc_key) + } KeyAlg::Ed25519 => Ed25519KeyPair::from_public_bytes(public).map(R::alloc_key), KeyAlg::X25519 => X25519KeyPair::from_public_bytes(public).map(R::alloc_key), KeyAlg::EcCurve(EcCurves::Secp256k1) => { @@ -204,6 +217,15 @@ fn from_secret_bytes_any(alg: KeyAlg, secret: &[u8]) -> Result { AesGcmKey::::from_secret_bytes(secret).map(R::alloc_key) } + KeyAlg::Bls12_381(BlsTypes::G1) => { + BlsKeyPair::::from_secret_bytes(secret).map(R::alloc_key) + } + KeyAlg::Bls12_381(BlsTypes::G2) => { + BlsKeyPair::::from_secret_bytes(secret).map(R::alloc_key) + } + KeyAlg::Bls12_381(BlsTypes::G1G2) => { + BlsKeyPair::::from_secret_bytes(secret).map(R::alloc_key) + } KeyAlg::Chacha20(Chacha20Types::C20P) => { Chacha20Key::::from_secret_bytes(secret).map(R::alloc_key) } @@ -289,6 +311,12 @@ fn from_key_derivation_any( #[inline] fn convert_key_any(key: &AnyKey, alg: KeyAlg) -> Result { match (key.algorithm(), alg) { + (KeyAlg::Bls12_381(BlsTypes::G1G2), KeyAlg::Bls12_381(BlsTypes::G1)) => Ok(R::alloc_key( + BlsKeyPair::::from(key.assume::>()), + )), + (KeyAlg::Bls12_381(BlsTypes::G1G2), KeyAlg::Bls12_381(BlsTypes::G2)) => Ok(R::alloc_key( + BlsKeyPair::::from(key.assume::>()), + )), (KeyAlg::Ed25519, KeyAlg::X25519) => Ok(>::try_from( key.assume::(), ) @@ -343,9 +371,14 @@ fn from_jwk_any(jwk: JwkParts<'_>) -> Result { ("OKP", c) if c == x25519::JWK_CURVE => { X25519KeyPair::from_jwk_parts(jwk).map(R::alloc_key) } + ("EC", c) if c == G1::JWK_CURVE => BlsKeyPair::::from_jwk_parts(jwk).map(R::alloc_key), + ("EC", c) if c == G2::JWK_CURVE => BlsKeyPair::::from_jwk_parts(jwk).map(R::alloc_key), + ("EC", c) if c == G1G2::JWK_CURVE => { + BlsKeyPair::::from_jwk_parts(jwk).map(R::alloc_key) + } ("EC", c) if c == k256::JWK_CURVE => K256KeyPair::from_jwk_parts(jwk).map(R::alloc_key), ("EC", c) if c == p256::JWK_CURVE => P256KeyPair::from_jwk_parts(jwk).map(R::alloc_key), - // "oct" + // FIXME implement symmetric keys _ => Err(err_msg!(Unsupported, "Unsupported JWK for key import")), } } @@ -353,6 +386,15 @@ fn from_jwk_any(jwk: JwkParts<'_>) -> Result { impl AnyKey { pub fn to_public_bytes(&self) -> Result { match self.key_type_id() { + s if s == TypeId::of::>() => { + Ok(self.assume::>().to_public_bytes()?) + } + s if s == TypeId::of::>() => { + Ok(self.assume::>().to_public_bytes()?) + } + s if s == TypeId::of::>() => { + Ok(self.assume::>().to_public_bytes()?) + } s if s == TypeId::of::() => { Ok(self.assume::().to_public_bytes()?) } @@ -378,6 +420,15 @@ impl AnyKey { s if s == TypeId::of::>() => { Ok(self.assume::>().to_secret_bytes()?) } + s if s == TypeId::of::>() => { + Ok(self.assume::>().to_secret_bytes()?) + } + s if s == TypeId::of::>() => { + Ok(self.assume::>().to_secret_bytes()?) + } + s if s == TypeId::of::>() => { + Ok(self.assume::>().to_secret_bytes()?) + } s if s == TypeId::of::>() => { Ok(self.assume::>().to_secret_bytes()?) } @@ -466,6 +517,9 @@ impl ToJwk for AnyKey { self, AesGcmKey, AesGcmKey, + BlsKeyPair, + BlsKeyPair, + BlsKeyPair, Chacha20Key, Chacha20Key, Ed25519KeyPair, diff --git a/askar-crypto/src/alg/mod.rs b/askar-crypto/src/alg/mod.rs index 107797ad..58b86f41 100644 --- a/askar-crypto/src/alg/mod.rs +++ b/askar-crypto/src/alg/mod.rs @@ -80,6 +80,9 @@ impl FromStr for KeyAlg { match normalize_alg(s)? { a if a == "a128gcm" || a == "aes128gcm" => Ok(Self::Aes(AesTypes::A128GCM)), a if a == "a256gcm" || a == "aes256gcm" => Ok(Self::Aes(AesTypes::A256GCM)), + a if a == "bls12381g1" => Ok(Self::Bls12_381(BlsTypes::G1)), + a if a == "bls12381g2" => Ok(Self::Bls12_381(BlsTypes::G2)), + a if a == "bls12381g1g2" => Ok(Self::Bls12_381(BlsTypes::G1G2)), a if a == "c20p" || a == "chacha20poly1305" => Ok(Self::Chacha20(Chacha20Types::C20P)), a if a == "xc20p" || a == "xchacha20poly1305" => { Ok(Self::Chacha20(Chacha20Types::XC20P)) diff --git a/wrappers/python/aries_askar/types.py b/wrappers/python/aries_askar/types.py index 59db182b..bdfff7bd 100644 --- a/wrappers/python/aries_askar/types.py +++ b/wrappers/python/aries_askar/types.py @@ -59,6 +59,9 @@ def __repr__(self) -> str: class KeyAlg(Enum): AES128GCM = "a128gcm" AES256GCM = "a256gcm" + BLS12_381_G1 = "bls12381g1" + BLS12_381_G2 = "bls12381g2" + BLS12_381_G1G2 = "bls12381g1g2" C20P = "c20p" XC20P = "xc20p" ED25519 = "ed25519" diff --git a/wrappers/python/demo/test.py b/wrappers/python/demo/test.py index d95dba7a..08d2f693 100644 --- a/wrappers/python/demo/test.py +++ b/wrappers/python/demo/test.py @@ -79,6 +79,9 @@ def keys_test(): derived = derive_key_ecdh_es("A256GCM", ephem, bob, "Alice", "Bob") log("Derived:", derived.get_jwk_thumbprint()) + key = Key.generate(KeyAlg.BLS12_381_G1G2) + log("Created key:", key.get_jwk_public()) + async def store_test(): if ENCRYPT: From 04e6207b03daf1fb570ef684f231824ddf6e08a8 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Mon, 3 May 2021 20:55:34 -0700 Subject: [PATCH 092/116] index G1G2 keys by both thumbprints, provide accessors for different JWK public keys and thumbprints Signed-off-by: Andrew Whitehead --- askar-crypto/src/alg/any.rs | 26 +++++----- askar-crypto/src/alg/bls.rs | 63 ++++++++++++++++++------- askar-crypto/src/alg/ed25519.rs | 2 +- askar-crypto/src/alg/k256.rs | 2 +- askar-crypto/src/alg/mod.rs | 16 +++---- askar-crypto/src/alg/p256.rs | 4 +- askar-crypto/src/alg/x25519.rs | 2 +- askar-crypto/src/jwk/encode.rs | 17 +++++-- askar-crypto/src/jwk/mod.rs | 14 +++--- src/ffi/key.rs | 10 ++-- src/kms/key.rs | 25 +++++++--- src/storage/store.rs | 4 +- wrappers/python/aries_askar/bindings.py | 14 ++++-- wrappers/python/aries_askar/key.py | 8 ++-- wrappers/python/demo/test.py | 4 +- 15 files changed, 137 insertions(+), 74 deletions(-) diff --git a/askar-crypto/src/alg/any.rs b/askar-crypto/src/alg/any.rs index b213f08f..4e955aa8 100644 --- a/askar-crypto/src/alg/any.rs +++ b/askar-crypto/src/alg/any.rs @@ -12,7 +12,7 @@ use super::ed25519::{self, Ed25519KeyPair}; use super::k256::{self, K256KeyPair}; use super::p256::{self, P256KeyPair}; use super::x25519::{self, X25519KeyPair}; -use super::{AesTypes, BlsTypes, Chacha20Types, EcCurves, HasKeyAlg, KeyAlg}; +use super::{AesTypes, BlsCurves, Chacha20Types, EcCurves, HasKeyAlg, KeyAlg}; use crate::{ buffer::{ResizeBuffer, SecretBytes, WriteBuffer}, encrypt::{KeyAeadInPlace, KeyAeadParams}, @@ -157,9 +157,9 @@ fn generate_any(alg: KeyAlg) -> Result { match alg { KeyAlg::Aes(AesTypes::A128GCM) => AesGcmKey::::generate().map(R::alloc_key), KeyAlg::Aes(AesTypes::A256GCM) => AesGcmKey::::generate().map(R::alloc_key), - KeyAlg::Bls12_381(BlsTypes::G1) => BlsKeyPair::::generate().map(R::alloc_key), - KeyAlg::Bls12_381(BlsTypes::G2) => BlsKeyPair::::generate().map(R::alloc_key), - KeyAlg::Bls12_381(BlsTypes::G1G2) => BlsKeyPair::::generate().map(R::alloc_key), + KeyAlg::Bls12_381(BlsCurves::G1) => BlsKeyPair::::generate().map(R::alloc_key), + KeyAlg::Bls12_381(BlsCurves::G2) => BlsKeyPair::::generate().map(R::alloc_key), + KeyAlg::Bls12_381(BlsCurves::G1G2) => BlsKeyPair::::generate().map(R::alloc_key), KeyAlg::Chacha20(Chacha20Types::C20P) => Chacha20Key::::generate().map(R::alloc_key), KeyAlg::Chacha20(Chacha20Types::XC20P) => { Chacha20Key::::generate().map(R::alloc_key) @@ -181,13 +181,13 @@ fn generate_any(alg: KeyAlg) -> Result { #[inline] fn from_public_bytes_any(alg: KeyAlg, public: &[u8]) -> Result { match alg { - KeyAlg::Bls12_381(BlsTypes::G1) => { + KeyAlg::Bls12_381(BlsCurves::G1) => { BlsKeyPair::::from_public_bytes(public).map(R::alloc_key) } - KeyAlg::Bls12_381(BlsTypes::G2) => { + KeyAlg::Bls12_381(BlsCurves::G2) => { BlsKeyPair::::from_public_bytes(public).map(R::alloc_key) } - KeyAlg::Bls12_381(BlsTypes::G1G2) => { + KeyAlg::Bls12_381(BlsCurves::G1G2) => { BlsKeyPair::::from_public_bytes(public).map(R::alloc_key) } KeyAlg::Ed25519 => Ed25519KeyPair::from_public_bytes(public).map(R::alloc_key), @@ -217,13 +217,13 @@ fn from_secret_bytes_any(alg: KeyAlg, secret: &[u8]) -> Result { AesGcmKey::::from_secret_bytes(secret).map(R::alloc_key) } - KeyAlg::Bls12_381(BlsTypes::G1) => { + KeyAlg::Bls12_381(BlsCurves::G1) => { BlsKeyPair::::from_secret_bytes(secret).map(R::alloc_key) } - KeyAlg::Bls12_381(BlsTypes::G2) => { + KeyAlg::Bls12_381(BlsCurves::G2) => { BlsKeyPair::::from_secret_bytes(secret).map(R::alloc_key) } - KeyAlg::Bls12_381(BlsTypes::G1G2) => { + KeyAlg::Bls12_381(BlsCurves::G1G2) => { BlsKeyPair::::from_secret_bytes(secret).map(R::alloc_key) } KeyAlg::Chacha20(Chacha20Types::C20P) => { @@ -311,10 +311,10 @@ fn from_key_derivation_any( #[inline] fn convert_key_any(key: &AnyKey, alg: KeyAlg) -> Result { match (key.algorithm(), alg) { - (KeyAlg::Bls12_381(BlsTypes::G1G2), KeyAlg::Bls12_381(BlsTypes::G1)) => Ok(R::alloc_key( + (KeyAlg::Bls12_381(BlsCurves::G1G2), KeyAlg::Bls12_381(BlsCurves::G1)) => Ok(R::alloc_key( BlsKeyPair::::from(key.assume::>()), )), - (KeyAlg::Bls12_381(BlsTypes::G1G2), KeyAlg::Bls12_381(BlsTypes::G2)) => Ok(R::alloc_key( + (KeyAlg::Bls12_381(BlsCurves::G1G2), KeyAlg::Bls12_381(BlsCurves::G2)) => Ok(R::alloc_key( BlsKeyPair::::from(key.assume::>()), )), (KeyAlg::Ed25519, KeyAlg::X25519) => Ok(>::try_from( @@ -609,7 +609,7 @@ mod tests { let key = Box::::generate(KeyAlg::Ed25519).unwrap(); assert_eq!(key.algorithm(), KeyAlg::Ed25519); assert_eq!(key.key_type_id(), TypeId::of::()); - let _ = key.to_jwk_public().unwrap(); + let _ = key.to_jwk_public(None).unwrap(); } #[test] diff --git a/askar-crypto/src/alg/bls.rs b/askar-crypto/src/alg/bls.rs index 40f4830e..91bdb09d 100644 --- a/askar-crypto/src/alg/bls.rs +++ b/askar-crypto/src/alg/bls.rs @@ -17,7 +17,7 @@ use crate::generic_array::{ ArrayLength, }; -use super::{BlsTypes, HasKeyAlg, KeyAlg}; +use super::{BlsCurves, HasKeyAlg, KeyAlg}; use crate::{ buffer::ArrayKey, error::Error, @@ -126,15 +126,15 @@ impl KeyPublicBytes for BlsKeyPair { } fn with_public_bytes(&self, f: impl FnOnce(&[u8]) -> O) -> O { - Pk::with_bytes(&self.public, f) + Pk::with_bytes(&self.public, None, f) } } impl ToJwk for BlsKeyPair { fn encode_jwk(&self, enc: &mut JwkEncoder<'_>) -> Result<(), Error> { - enc.add_str("crv", Pk::JWK_CURVE)?; + enc.add_str("crv", Pk::get_jwk_curve(enc.alg()))?; enc.add_str("kty", JWK_KEY_TYPE)?; - self.with_public_bytes(|buf| enc.add_as_base64("x", buf))?; + Pk::with_bytes(&self.public, enc.alg(), |buf| enc.add_as_base64("x", buf))?; if enc.is_secret() { self.with_secret_bytes(|buf| { if let Some(sk) = buf { @@ -232,10 +232,15 @@ pub trait BlsPublicKeyType: 'static { type BufferSize: ArrayLength; /// The associated algorithm type - const ALG_TYPE: BlsTypes; + const ALG_TYPE: BlsCurves; /// The associated JWK curve name const JWK_CURVE: &'static str; + /// Get the JWK curve for a specific key algorithm + fn get_jwk_curve(_alg: Option) -> &'static str { + Self::JWK_CURVE + } + /// Initialize from the secret scalar fn from_secret_scalar(secret: &Scalar) -> Self::Buffer; @@ -243,7 +248,7 @@ pub trait BlsPublicKeyType: 'static { fn from_public_bytes(key: &[u8]) -> Result; /// Access the bytes of the public key - fn with_bytes(buf: &Self::Buffer, f: impl FnOnce(&[u8]) -> O) -> O; + fn with_bytes(buf: &Self::Buffer, alg: Option, f: impl FnOnce(&[u8]) -> O) -> O; } /// G1 curve @@ -254,7 +259,7 @@ impl BlsPublicKeyType for G1 { type Buffer = G1Affine; type BufferSize = U48; - const ALG_TYPE: BlsTypes = BlsTypes::G1; + const ALG_TYPE: BlsCurves = BlsCurves::G1; const JWK_CURVE: &'static str = "BLS12381_G1"; #[inline] @@ -270,7 +275,7 @@ impl BlsPublicKeyType for G1 { buf.ok_or_else(|| err_msg!(InvalidKeyData)) } - fn with_bytes(buf: &Self::Buffer, f: impl FnOnce(&[u8]) -> O) -> O { + fn with_bytes(buf: &Self::Buffer, _alg: Option, f: impl FnOnce(&[u8]) -> O) -> O { f(buf.to_bytes().as_ref()) } } @@ -283,7 +288,7 @@ impl BlsPublicKeyType for G2 { type Buffer = G2Affine; type BufferSize = U96; - const ALG_TYPE: BlsTypes = BlsTypes::G2; + const ALG_TYPE: BlsCurves = BlsCurves::G2; const JWK_CURVE: &'static str = "BLS12381_G2"; #[inline] @@ -299,7 +304,7 @@ impl BlsPublicKeyType for G2 { buf.ok_or_else(|| err_msg!(InvalidKeyData)) } - fn with_bytes(buf: &Self::Buffer, f: impl FnOnce(&[u8]) -> O) -> O { + fn with_bytes(buf: &Self::Buffer, _alg: Option, f: impl FnOnce(&[u8]) -> O) -> O { f(buf.to_bytes().as_ref()) } } @@ -312,9 +317,19 @@ impl BlsPublicKeyType for G1G2 { type Buffer = (G1Affine, G2Affine); type BufferSize = U144; - const ALG_TYPE: BlsTypes = BlsTypes::G1G2; + const ALG_TYPE: BlsCurves = BlsCurves::G1G2; const JWK_CURVE: &'static str = "BLS12381_G1G2"; + fn get_jwk_curve(alg: Option) -> &'static str { + if alg == Some(KeyAlg::Bls12_381(BlsCurves::G1)) { + G1::JWK_CURVE + } else if alg == Some(KeyAlg::Bls12_381(BlsCurves::G2)) { + G2::JWK_CURVE + } else { + Self::JWK_CURVE + } + } + #[inline] fn from_secret_scalar(secret: &Scalar) -> Self::Buffer { ( @@ -338,12 +353,24 @@ impl BlsPublicKeyType for G1G2 { } } - fn with_bytes(buf: &Self::Buffer, f: impl FnOnce(&[u8]) -> O) -> O { - ArrayKey::::temp(|arr| { - arr[0..48].copy_from_slice(buf.0.to_bytes().as_ref()); - arr[48..].copy_from_slice(buf.1.to_bytes().as_ref()); - f(&arr[..]) - }) + fn with_bytes(buf: &Self::Buffer, alg: Option, f: impl FnOnce(&[u8]) -> O) -> O { + if alg == Some(KeyAlg::Bls12_381(BlsCurves::G1)) { + ArrayKey::::temp(|arr| { + arr.copy_from_slice(buf.0.to_bytes().as_ref()); + f(&arr[..]) + }) + } else if alg == Some(KeyAlg::Bls12_381(BlsCurves::G2)) { + ArrayKey::::temp(|arr| { + arr.copy_from_slice(buf.1.to_bytes().as_ref()); + f(&arr[..]) + }) + } else { + ArrayKey::::temp(|arr| { + arr[0..48].copy_from_slice(buf.0.to_bytes().as_ref()); + arr[48..].copy_from_slice(buf.1.to_bytes().as_ref()); + f(&arr[..]) + }) + } } } @@ -447,7 +474,7 @@ mod tests { let test_pub_g1 = &hex!("a2c975348667926acf12f3eecb005044e08a7a9b7d95f30bd281b55445107367a2e5d0558be7943c8bd13f9a1a7036fb"); let kp = BlsKeyPair::::from_secret_bytes(&test_pvt[..]).expect("Error creating key"); - let jwk = kp.to_jwk_public().expect("Error converting key to JWK"); + let jwk = kp.to_jwk_public(None).expect("Error converting key to JWK"); let jwk = JwkParts::from_str(&jwk).expect("Error parsing JWK"); assert_eq!(jwk.kty, "EC"); assert_eq!(jwk.crv, G1::JWK_CURVE); diff --git a/askar-crypto/src/alg/ed25519.rs b/askar-crypto/src/alg/ed25519.rs index 4d74910b..a9f934a0 100644 --- a/askar-crypto/src/alg/ed25519.rs +++ b/askar-crypto/src/alg/ed25519.rs @@ -361,7 +361,7 @@ mod tests { let test_pvt = base64::decode_config(test_pvt_b64, base64::URL_SAFE).unwrap(); let kp = Ed25519KeyPair::from_secret_bytes(&test_pvt).expect("Error creating signing key"); let jwk = kp - .to_jwk_public() + .to_jwk_public(None) .expect("Error converting public key to JWK"); let jwk = JwkParts::from_str(&jwk).expect("Error parsing JWK output"); assert_eq!(jwk.kty, JWK_KEY_TYPE); diff --git a/askar-crypto/src/alg/k256.rs b/askar-crypto/src/alg/k256.rs index 9c3aa20d..7c01de0d 100644 --- a/askar-crypto/src/alg/k256.rs +++ b/askar-crypto/src/alg/k256.rs @@ -313,7 +313,7 @@ mod tests { let test_pvt = base64::decode_config(test_pvt_b64, base64::URL_SAFE).unwrap(); let sk = K256KeyPair::from_secret_bytes(&test_pvt).expect("Error creating signing key"); - let jwk = sk.to_jwk_public().expect("Error converting key to JWK"); + let jwk = sk.to_jwk_public(None).expect("Error converting key to JWK"); let jwk = JwkParts::from_str(&jwk).expect("Error parsing JWK"); assert_eq!(jwk.kty, "EC"); assert_eq!(jwk.crv, JWK_CURVE); diff --git a/askar-crypto/src/alg/mod.rs b/askar-crypto/src/alg/mod.rs index 58b86f41..950f8f26 100644 --- a/askar-crypto/src/alg/mod.rs +++ b/askar-crypto/src/alg/mod.rs @@ -37,7 +37,7 @@ pub enum KeyAlg { /// AES Aes(AesTypes), /// BLS12-381 - Bls12_381(BlsTypes), + Bls12_381(BlsCurves), /// (X)ChaCha20-Poly1305 Chacha20(Chacha20Types), /// Curve25519 signing key @@ -54,9 +54,9 @@ impl KeyAlg { match self { Self::Aes(AesTypes::A128GCM) => "a128gcm", Self::Aes(AesTypes::A256GCM) => "a256gcm", - Self::Bls12_381(BlsTypes::G1) => "bls12381g1", - Self::Bls12_381(BlsTypes::G2) => "bls12381g2", - Self::Bls12_381(BlsTypes::G1G2) => "bls12381g1g2", + Self::Bls12_381(BlsCurves::G1) => "bls12381g1", + Self::Bls12_381(BlsCurves::G2) => "bls12381g2", + Self::Bls12_381(BlsCurves::G1G2) => "bls12381g1g2", Self::Chacha20(Chacha20Types::C20P) => "c20p", Self::Chacha20(Chacha20Types::XC20P) => "xc20p", Self::Ed25519 => "ed25519", @@ -80,9 +80,9 @@ impl FromStr for KeyAlg { match normalize_alg(s)? { a if a == "a128gcm" || a == "aes128gcm" => Ok(Self::Aes(AesTypes::A128GCM)), a if a == "a256gcm" || a == "aes256gcm" => Ok(Self::Aes(AesTypes::A256GCM)), - a if a == "bls12381g1" => Ok(Self::Bls12_381(BlsTypes::G1)), - a if a == "bls12381g2" => Ok(Self::Bls12_381(BlsTypes::G2)), - a if a == "bls12381g1g2" => Ok(Self::Bls12_381(BlsTypes::G1G2)), + a if a == "bls12381g1" => Ok(Self::Bls12_381(BlsCurves::G1)), + a if a == "bls12381g2" => Ok(Self::Bls12_381(BlsCurves::G2)), + a if a == "bls12381g1g2" => Ok(Self::Bls12_381(BlsCurves::G1G2)), a if a == "c20p" || a == "chacha20poly1305" => Ok(Self::Chacha20(Chacha20Types::C20P)), a if a == "xc20p" || a == "xchacha20poly1305" => { Ok(Self::Chacha20(Chacha20Types::XC20P)) @@ -178,7 +178,7 @@ pub enum AesTypes { /// Supported public key types for Bls12_381 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Zeroize)] -pub enum BlsTypes { +pub enum BlsCurves { /// G1 curve G1, /// G2 curve diff --git a/askar-crypto/src/alg/p256.rs b/askar-crypto/src/alg/p256.rs index ad4031f8..9bfc3e74 100644 --- a/askar-crypto/src/alg/p256.rs +++ b/askar-crypto/src/alg/p256.rs @@ -311,7 +311,7 @@ mod tests { let test_pvt = base64::decode_config(test_pvt_b64, base64::URL_SAFE).unwrap(); let sk = P256KeyPair::from_secret_bytes(&test_pvt).expect("Error creating signing key"); - let jwk = sk.to_jwk_public().expect("Error converting key to JWK"); + let jwk = sk.to_jwk_public(None).expect("Error converting key to JWK"); let jwk = JwkParts::from_str(&jwk).expect("Error parsing JWK"); assert_eq!(jwk.kty, "EC"); assert_eq!(jwk.crv, JWK_CURVE); @@ -347,7 +347,7 @@ mod tests { ) .unwrap(); assert_eq!( - pk.to_jwk_thumbprint().unwrap(), + pk.to_jwk_thumbprint(None).unwrap(), "8fm8079s3nu4FLV_7dVJoJ69A8XCXn7Za2mtaWCnxR4" ); } diff --git a/askar-crypto/src/alg/x25519.rs b/askar-crypto/src/alg/x25519.rs index 702b94d9..df17b136 100644 --- a/askar-crypto/src/alg/x25519.rs +++ b/askar-crypto/src/alg/x25519.rs @@ -269,7 +269,7 @@ mod tests { let kp = X25519KeyPair::from_secret_bytes(&test_pvt).expect("Error creating x25519 keypair"); let jwk = kp - .to_jwk_public() + .to_jwk_public(None) .expect("Error converting public key to JWK"); let jwk = JwkParts::from_str(&jwk).expect("Error parsing JWK output"); assert_eq!(jwk.kty, "OKP"); diff --git a/askar-crypto/src/jwk/encode.rs b/askar-crypto/src/jwk/encode.rs index c6ee6d9b..410dabc5 100644 --- a/askar-crypto/src/jwk/encode.rs +++ b/askar-crypto/src/jwk/encode.rs @@ -1,12 +1,12 @@ use core::fmt::Write; +use super::ops::KeyOpsSet; use crate::{ + alg::KeyAlg, buffer::{WriteBuffer, Writer}, error::Error, }; -use super::ops::KeyOpsSet; - /// Supported modes for JWK encoding #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum JwkEncoderMode { @@ -21,6 +21,7 @@ pub enum JwkEncoderMode { /// A helper structure which writes a JWK to a buffer #[derive(Debug)] pub struct JwkEncoder<'b> { + alg: Option, buffer: &'b mut dyn WriteBuffer, empty: bool, mode: JwkEncoderMode, @@ -28,8 +29,13 @@ pub struct JwkEncoder<'b> { impl<'b> JwkEncoder<'b> { /// Create a new instance - pub fn new(buffer: &'b mut B, mode: JwkEncoderMode) -> Result { + pub fn new( + alg: Option, + buffer: &'b mut B, + mode: JwkEncoderMode, + ) -> Result { Ok(Self { + alg, buffer, empty: true, mode, @@ -38,6 +44,11 @@ impl<'b> JwkEncoder<'b> { } impl JwkEncoder<'_> { + /// Get the requested algorithm for the JWK + pub fn alg(&self) -> Option { + self.alg + } + fn start_attr(&mut self, key: &str) -> Result<(), Error> { let buffer = &mut *self.buffer; if self.empty { diff --git a/askar-crypto/src/jwk/mod.rs b/askar-crypto/src/jwk/mod.rs index 0c18ac62..b57046a8 100644 --- a/askar-crypto/src/jwk/mod.rs +++ b/askar-crypto/src/jwk/mod.rs @@ -8,6 +8,7 @@ use sha2::Sha256; #[cfg(feature = "alloc")] use crate::buffer::SecretBytes; use crate::{ + alg::KeyAlg, buffer::{HashBuffer, ResizeBuffer}, error::Error, }; @@ -29,18 +30,18 @@ pub trait ToJwk { /// Create the JWK thumbprint of the key #[cfg(feature = "alloc")] #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] - fn to_jwk_thumbprint(&self) -> Result { + fn to_jwk_thumbprint(&self, alg: Option) -> Result { let mut v = Vec::with_capacity(43); - write_jwk_thumbprint(self, &mut v)?; + write_jwk_thumbprint(self, alg, &mut v)?; Ok(String::from_utf8(v).unwrap()) } /// Create a JWK of the public key #[cfg(feature = "alloc")] #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] - fn to_jwk_public(&self) -> Result { + fn to_jwk_public(&self, alg: Option) -> Result { let mut v = Vec::with_capacity(128); - let mut buf = JwkEncoder::new(&mut v, JwkEncoderMode::PublicKey)?; + let mut buf = JwkEncoder::new(alg, &mut v, JwkEncoderMode::PublicKey)?; self.encode_jwk(&mut buf)?; buf.finalize()?; Ok(String::from_utf8(v).unwrap()) @@ -51,7 +52,7 @@ pub trait ToJwk { #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] fn to_jwk_secret(&self) -> Result { let mut v = SecretBytes::with_capacity(128); - let mut buf = JwkEncoder::new(&mut v, JwkEncoderMode::SecretKey)?; + let mut buf = JwkEncoder::new(None, &mut v, JwkEncoderMode::SecretKey)?; self.encode_jwk(&mut buf)?; buf.finalize()?; Ok(v) @@ -61,10 +62,11 @@ pub trait ToJwk { /// Encode a key's JWK into a buffer pub fn write_jwk_thumbprint( key: &K, + alg: Option, output: &mut dyn ResizeBuffer, ) -> Result<(), Error> { let mut hasher = HashBuffer::::new(); - let mut buf = JwkEncoder::new(&mut hasher, JwkEncoderMode::Thumbprint)?; + let mut buf = JwkEncoder::new(alg, &mut hasher, JwkEncoderMode::Thumbprint)?; key.encode_jwk(&mut buf)?; buf.finalize()?; let hash = hasher.finalize(); diff --git a/src/ffi/key.rs b/src/ffi/key.rs index a3d653c1..f23db074 100644 --- a/src/ffi/key.rs +++ b/src/ffi/key.rs @@ -173,13 +173,15 @@ pub extern "C" fn askar_key_get_ephemeral(handle: LocalKeyHandle, out: *mut i8) #[no_mangle] pub extern "C" fn askar_key_get_jwk_public( handle: LocalKeyHandle, + alg: FfiStr<'_>, out: *mut *const c_char, ) -> ErrorCode { catch_err! { trace!("Get key JWK public: {}", handle); check_useful_c_ptr!(out); let key = handle.load()?; - let jwk = key.to_jwk_public()?; + let alg = alg.as_opt_str().map(KeyAlg::from_str).transpose()?; + let jwk = key.to_jwk_public(alg)?; unsafe { *out = rust_string_to_c(jwk) }; Ok(ErrorCode::Success) } @@ -203,20 +205,20 @@ pub extern "C" fn askar_key_get_jwk_secret( #[no_mangle] pub extern "C" fn askar_key_get_jwk_thumbprint( handle: LocalKeyHandle, + alg: FfiStr<'_>, out: *mut *const c_char, ) -> ErrorCode { catch_err! { trace!("Get key JWK thumbprint: {}", handle); check_useful_c_ptr!(out); let key = handle.load()?; - let thumb = key.to_jwk_thumbprint()?; + let alg = alg.as_opt_str().map(KeyAlg::from_str).transpose()?; + let thumb = key.to_jwk_thumbprint(alg)?; unsafe { *out = rust_string_to_c(thumb) }; Ok(ErrorCode::Success) } } -// key_aead_get_params (nonce len, tag len) - #[no_mangle] pub extern "C" fn askar_key_aead_random_nonce( handle: LocalKeyHandle, diff --git a/src/kms/key.rs b/src/kms/key.rs index f1c74595..d509d337 100644 --- a/src/kms/key.rs +++ b/src/kms/key.rs @@ -7,7 +7,7 @@ pub use crate::crypto::{ }; use crate::{ crypto::{ - alg::{AnyKey, AnyKeyCreate}, + alg::{AnyKey, AnyKeyCreate, BlsCurves}, encrypt::KeyAeadInPlace, jwk::{FromJwk, ToJwk}, kdf::{KeyDerivation, KeyExchange}, @@ -99,8 +99,8 @@ impl LocalKey { } /// Get the public JWK representation for this key or keypair - pub fn to_jwk_public(&self) -> Result { - Ok(self.inner.to_jwk_public()?) + pub fn to_jwk_public(&self, alg: Option) -> Result { + Ok(self.inner.to_jwk_public(alg)?) } /// Get the JWK representation for this private key or keypair @@ -109,9 +109,22 @@ impl LocalKey { } /// Get the JWK thumbprint for this key or keypair - pub fn to_jwk_thumbprint(&self) -> Result { - // FIXME add special case for BLS G1+G2 (two prints) - Ok(self.inner.to_jwk_thumbprint()?) + pub fn to_jwk_thumbprint(&self, alg: Option) -> Result { + Ok(self.inner.to_jwk_thumbprint(alg)?) + } + + /// Get the set of indexed JWK thumbprints for this key or keypair + pub fn to_jwk_thumbprints(&self) -> Result, Error> { + if self.inner.algorithm() == KeyAlg::Bls12_381(BlsCurves::G1G2) { + return Ok(vec![ + self.inner + .to_jwk_thumbprint(Some(KeyAlg::Bls12_381(BlsCurves::G1)))?, + self.inner + .to_jwk_thumbprint(Some(KeyAlg::Bls12_381(BlsCurves::G2)))?, + ]); + } else { + Ok(vec![self.inner.to_jwk_thumbprint(None)?]) + } } /// Map this key or keypair to its equivalent for another key algorithm diff --git a/src/storage/store.rs b/src/storage/store.rs index 7b405641..b7331cb3 100644 --- a/src/storage/store.rs +++ b/src/storage/store.rs @@ -274,8 +274,8 @@ impl Session { if !alg.is_empty() { ins_tags.push(EntryTag::Encrypted("alg".to_string(), alg.to_string())); } - let thumb = key.to_jwk_thumbprint()?; - if !thumb.is_empty() { + let thumbs = key.to_jwk_thumbprints()?; + for thumb in thumbs { ins_tags.push(EntryTag::Encrypted("thumb".to_string(), thumb)); } if let Some(tags) = tags { diff --git a/wrappers/python/aries_askar/bindings.py b/wrappers/python/aries_askar/bindings.py index bc6c574f..f9ad314d 100644 --- a/wrappers/python/aries_askar/bindings.py +++ b/wrappers/python/aries_askar/bindings.py @@ -975,9 +975,11 @@ def key_get_ephemeral(handle: LocalKeyHandle) -> bool: return eph.value != 0 -def key_get_jwk_public(handle: LocalKeyHandle) -> str: +def key_get_jwk_public(handle: LocalKeyHandle, alg: Union[str, KeyAlg] = None) -> str: jwk = StrBuffer() - do_call("askar_key_get_jwk_public", handle, byref(jwk)) + if isinstance(alg, KeyAlg): + alg = alg.value + do_call("askar_key_get_jwk_public", handle, encode_str(alg), byref(jwk)) return str(jwk) @@ -987,9 +989,13 @@ def key_get_jwk_secret(handle: LocalKeyHandle) -> ByteBuffer: return sec -def key_get_jwk_thumbprint(handle: LocalKeyHandle) -> str: +def key_get_jwk_thumbprint( + handle: LocalKeyHandle, alg: Union[str, KeyAlg] = None +) -> str: thumb = StrBuffer() - do_call("askar_key_get_jwk_thumbprint", handle, byref(thumb)) + if isinstance(alg, KeyAlg): + alg = alg.value + do_call("askar_key_get_jwk_thumbprint", handle, encode_str(alg), byref(thumb)) return str(thumb) diff --git a/wrappers/python/aries_askar/key.py b/wrappers/python/aries_askar/key.py index 0d6f12ef..6b9da568 100644 --- a/wrappers/python/aries_askar/key.py +++ b/wrappers/python/aries_askar/key.py @@ -51,14 +51,14 @@ def get_public_bytes(self) -> bytes: def get_secret_bytes(self) -> bytes: return bytes(bindings.key_get_secret_bytes(self._handle)) - def get_jwk_public(self) -> str: - return bindings.key_get_jwk_public(self._handle) + def get_jwk_public(self, alg: Union[str, KeyAlg] = None) -> str: + return bindings.key_get_jwk_public(self._handle, alg) def get_jwk_secret(self) -> str: return bindings.key_get_jwk_secret(self._handle) - def get_jwk_thumbprint(self) -> str: - return bindings.key_get_jwk_thumbprint(self._handle) + def get_jwk_thumbprint(self, alg: Union[str, KeyAlg] = None) -> str: + return bindings.key_get_jwk_thumbprint(self._handle, alg) def aead_params(self) -> bindings.AeadParams: return bindings.key_aead_get_params(self._handle) diff --git a/wrappers/python/demo/test.py b/wrappers/python/demo/test.py index 08d2f693..adc507ac 100644 --- a/wrappers/python/demo/test.py +++ b/wrappers/python/demo/test.py @@ -80,7 +80,9 @@ def keys_test(): log("Derived:", derived.get_jwk_thumbprint()) key = Key.generate(KeyAlg.BLS12_381_G1G2) - log("Created key:", key.get_jwk_public()) + log("BLS key G1:", key.get_jwk_public(KeyAlg.BLS12_381_G1)) + log("BLS key G2:", key.get_jwk_public(KeyAlg.BLS12_381_G2)) + log("BLS key G1G2:", key.get_jwk_public()) async def store_test(): From b4f403348f6c5bf0a0ae704b21ec440623e6511e Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Tue, 4 May 2021 08:59:20 -0700 Subject: [PATCH 093/116] implement KeyDerivation for HmacKey Signed-off-by: Andrew Whitehead --- askar-crypto/src/error.rs | 3 +- src/protect/hmac_key.rs | 92 ++++++++++++++++++++++++++++---------- src/protect/profile_key.rs | 25 ++++++----- 3 files changed, 85 insertions(+), 35 deletions(-) diff --git a/askar-crypto/src/error.rs b/askar-crypto/src/error.rs index 3df82d6b..0a57a6b2 100644 --- a/askar-crypto/src/error.rs +++ b/askar-crypto/src/error.rs @@ -69,7 +69,8 @@ pub struct Error { } impl Error { - pub(crate) fn from_msg(kind: ErrorKind, msg: &'static str) -> Self { + /// Create a new error instance with message text + pub fn from_msg(kind: ErrorKind, msg: &'static str) -> Self { Self { kind, #[cfg(feature = "std")] diff --git a/src/protect/hmac_key.rs b/src/protect/hmac_key.rs index ad4b73c4..aa0bc70a 100644 --- a/src/protect/hmac_key.rs +++ b/src/protect/hmac_key.rs @@ -1,5 +1,7 @@ -use std::fmt::{self, Debug, Formatter}; -use std::marker::PhantomData; +use std::{ + fmt::{self, Debug, Formatter}, + marker::PhantomData, +}; use hmac::{ digest::{BlockInput, FixedOutput, Reset, Update}, @@ -9,8 +11,10 @@ use serde::{Deserialize, Serialize}; use crate::{ crypto::{ + self, buffer::ArrayKey, generic_array::{typenum::Unsigned, ArrayLength, GenericArray}, + kdf::KeyDerivation, repr::KeyGen, }, error::Error, @@ -24,9 +28,9 @@ use crate::{ serialize = "ArrayKey: Serialize" ) )] -pub struct HmacKey, H>(ArrayKey, PhantomData); +pub struct HmacKey>(ArrayKey, PhantomData); -impl, H> HmacKey { +impl> HmacKey { #[allow(dead_code)] pub fn from_slice(key: &[u8]) -> Result { if key.len() != L::USIZE { @@ -36,13 +40,19 @@ impl, H> HmacKey { } } -impl, H> AsRef> for HmacKey { +impl> AsRef<[u8]> for HmacKey { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +impl> AsRef> for HmacKey { fn as_ref(&self) -> &GenericArray { self.0.as_ref() } } -impl, H> Debug for HmacKey { +impl> Debug for HmacKey { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { if cfg!(test) { f.debug_tuple("HmacKey").field(&*self).finish() @@ -52,39 +62,73 @@ impl, H> Debug for HmacKey { } } -impl, H> PartialEq for HmacKey { +impl> PartialEq for HmacKey { fn eq(&self, other: &Self) -> bool { - self.as_ref() == other.as_ref() + self.0.as_ref() == other.0.as_ref() } } -impl, H> Eq for HmacKey {} +impl> Eq for HmacKey {} -impl, H> KeyGen for HmacKey { +impl> KeyGen for HmacKey { fn generate() -> Result { Ok(Self(ArrayKey::random(), PhantomData)) } } -pub trait HmacOutput { - fn hmac_to(&self, messages: &[&[u8]], output: &mut [u8]) -> Result<(), Error>; +pub trait HmacDerive { + type Hash: BlockInput + Default + Reset + Update + Clone + FixedOutput; + type Key: AsRef<[u8]>; + + fn hmac_deriver<'d>(&'d self, inputs: &'d [&'d [u8]]) + -> HmacDeriver<'d, Self::Hash, Self::Key>; +} + +impl> HmacDerive for HmacKey +where + H: BlockInput + Default + Reset + Update + Clone + FixedOutput, +{ + type Hash = H; + type Key = Self; + + #[inline] + fn hmac_deriver<'d>( + &'d self, + inputs: &'d [&'d [u8]], + ) -> HmacDeriver<'d, Self::Hash, Self::Key> { + HmacDeriver { + key: self, + inputs, + _marker: PhantomData, + } + } +} + +pub struct HmacDeriver<'d, H, K: ?Sized> { + key: &'d K, + inputs: &'d [&'d [u8]], + _marker: PhantomData, } -impl HmacOutput for HmacKey +impl KeyDerivation for HmacDeriver<'_, H, K> where - L: ArrayLength, + K: AsRef<[u8]> + ?Sized, H: BlockInput + Default + Reset + Update + Clone + FixedOutput, { - fn hmac_to(&self, messages: &[&[u8]], output: &mut [u8]) -> Result<(), Error> { - if output.len() > H::OutputSize::USIZE { - return Err(err_msg!(Encryption, "invalid length for hmac output")); + fn derive_key_bytes(&mut self, key_output: &mut [u8]) -> Result<(), crypto::Error> { + if key_output.len() > H::OutputSize::USIZE { + return Err(crypto::Error::from_msg( + crypto::ErrorKind::Encryption, + "invalid length for hmac output", + )); } - let mut hmac = - Hmac::::new_varkey(self.0.as_ref()).map_err(|e| err_msg!(Encryption, "{}", e))?; - for msg in messages { + let mut hmac = Hmac::::new_varkey(self.key.as_ref()).map_err(|_| { + crypto::Error::from_msg(crypto::ErrorKind::Encryption, "invalid length for hmac key") + })?; + for msg in self.inputs { hmac.update(msg); } let hash = hmac.finalize().into_bytes(); - output.copy_from_slice(&hash[..output.len()]); + key_output.copy_from_slice(&hash[..key_output.len()]); Ok(()) } } @@ -97,12 +141,14 @@ mod tests { #[test] fn hmac_expected() { - let key = HmacKey::::from_slice(&hex!( + let key = HmacKey::::from_slice(&hex!( "c32ef97a2eed6316ae9b0d3129554358980ee6e0b21b81625229c191a3469f7e" )) .unwrap(); let mut output = [0u8; 12]; - key.hmac_to(&[b"test message"], &mut output).unwrap(); + key.hmac_deriver(&[b"test message"]) + .derive_key_bytes(&mut output) + .unwrap(); assert_eq!(output, &hex!("4cecfbf6be721395529be686")[..]); } } diff --git a/src/protect/profile_key.rs b/src/protect/profile_key.rs index c73eb735..8f356ebf 100644 --- a/src/protect/profile_key.rs +++ b/src/protect/profile_key.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Serialize}; use sha2::Sha256; -use super::hmac_key::HmacOutput; +use super::hmac_key::{HmacDerive, HmacKey}; use super::EntryEncryptor; use crate::{ crypto::{ @@ -9,13 +9,14 @@ use crate::{ buffer::{ArrayKey, ResizeBuffer, SecretBytes, WriteBuffer}, encrypt::{KeyAeadInPlace, KeyAeadMeta}, generic_array::typenum::{Unsigned, U32}, + kdf::KeyDerivation, repr::{KeyGen, KeyMeta, KeySecretBytes}, }, error::Error, storage::{EncEntryTag, EntryTag}, }; -pub type ProfileKey = ProfileKeyImpl, super::hmac_key::HmacKey>; +pub type ProfileKey = ProfileKeyImpl, HmacKey>; /// A record combining the keys required to encrypt and decrypt storage entries #[derive(Clone, Debug, Deserialize, Serialize)] @@ -74,7 +75,7 @@ where impl ProfileKeyImpl where Key: KeyGen + KeyMeta + KeyAeadInPlace + KeyAeadMeta + KeySecretBytes, - HmacKey: KeyGen + HmacOutput, + HmacKey: KeyGen + HmacDerive, { pub fn encrypted_size(len: usize) -> usize { len + Key::NonceSize::USIZE + Key::TagSize::USIZE @@ -87,7 +88,9 @@ where hmac_key: &HmacKey, ) -> Result, Error> { ArrayKey::::temp(|nonce| { - hmac_key.hmac_to(&[buffer.as_ref()], nonce)?; + hmac_key + .hmac_deriver(&[buffer.as_ref()]) + .derive_key_bytes(nonce)?; enc_key.encrypt_in_place(&mut buffer, nonce.as_ref(), &[])?; buffer.buffer_insert(0, nonce.as_ref())?; Ok(buffer.into_vec()) @@ -119,15 +122,15 @@ where name: &[u8], out: &mut [u8], ) -> Result<(), Error> { - self.item_hmac_key.hmac_to( - &[ + Ok(self + .item_hmac_key + .hmac_deriver(&[ &(category.len() as u32).to_be_bytes(), category, &(name.len() as u32).to_be_bytes(), name, - ], - out, - ) + ]) + .derive_key_bytes(out)?) } pub fn encrypt_tag_name(&self, name: SecretBytes) -> Result, Error> { @@ -162,7 +165,7 @@ impl Eq for ProfileKeyImpl {} impl EntryEncryptor for ProfileKeyImpl where Key: KeyGen + KeyMeta + KeyAeadInPlace + KeyAeadMeta + KeySecretBytes, - HmacKey: KeyGen + HmacOutput, + HmacKey: KeyGen + HmacDerive, { fn prepare_input(input: &[u8]) -> SecretBytes { let mut buf = SecretBytes::with_capacity(Self::encrypted_size(input.len())); @@ -309,7 +312,7 @@ mod tests { fn check_encrypt_searchable() { let input = SecretBytes::from(&b"hello"[..]); let key = Chacha20Key::::generate().unwrap(); - let hmac_key = crate::protect::hmac_key::HmacKey::::generate().unwrap(); + let hmac_key = HmacKey::generate().unwrap(); let enc1 = ProfileKey::encrypt_searchable(input.clone(), &key, &hmac_key).unwrap(); let enc2 = ProfileKey::encrypt_searchable(input.clone(), &key, &hmac_key).unwrap(); let enc3 = ProfileKey::encrypt(input.clone(), &key).unwrap(); From 8cca807e5207c36d87100565a7a5e67c045f7cbd Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Tue, 4 May 2021 09:20:19 -0700 Subject: [PATCH 094/116] create ArrayKey from key derivation Signed-off-by: Andrew Whitehead --- askar-crypto/src/buffer/array.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/askar-crypto/src/buffer/array.rs b/askar-crypto/src/buffer/array.rs index eb3c6c60..da57ac21 100644 --- a/askar-crypto/src/buffer/array.rs +++ b/askar-crypto/src/buffer/array.rs @@ -8,9 +8,12 @@ use crate::generic_array::{ArrayLength, GenericArray}; use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use zeroize::Zeroize; -use crate::random::fill_random; - use super::HexRepr; +use crate::{ + error::Error, + kdf::{FromKeyDerivation, KeyDerivation}, + random::fill_random, +}; /// A secure representation for fixed-length keys #[derive(Clone, Hash)] @@ -183,3 +186,12 @@ impl<'de, L: ArrayLength> de::Visitor<'de> for KeyVisitor { Ok(ArrayKey::from_slice(value)) } } + +impl> FromKeyDerivation for ArrayKey { + fn from_key_derivation(mut derive: D) -> Result + where + Self: Sized, + { + Self::try_new_with(|buf| derive.derive_key_bytes(buf)) + } +} From 3c5a69738f05f317ac4b4b12ec4c1af24be09462 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Tue, 4 May 2021 09:20:52 -0700 Subject: [PATCH 095/116] simplify hmac operations a little Signed-off-by: Andrew Whitehead --- src/protect/profile_key.rs | 73 ++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 43 deletions(-) diff --git a/src/protect/profile_key.rs b/src/protect/profile_key.rs index 8f356ebf..23ab6479 100644 --- a/src/protect/profile_key.rs +++ b/src/protect/profile_key.rs @@ -9,8 +9,8 @@ use crate::{ buffer::{ArrayKey, ResizeBuffer, SecretBytes, WriteBuffer}, encrypt::{KeyAeadInPlace, KeyAeadMeta}, generic_array::typenum::{Unsigned, U32}, - kdf::KeyDerivation, - repr::{KeyGen, KeyMeta, KeySecretBytes}, + kdf::FromKeyDerivation, + repr::KeyGen, }, error::Error, storage::{EncEntryTag, EntryTag}, @@ -74,37 +74,35 @@ where impl ProfileKeyImpl where - Key: KeyGen + KeyMeta + KeyAeadInPlace + KeyAeadMeta + KeySecretBytes, - HmacKey: KeyGen + HmacDerive, + Key: KeyAeadInPlace + KeyAeadMeta + FromKeyDerivation, + HmacKey: HmacDerive, { - pub fn encrypted_size(len: usize) -> usize { + fn encrypted_size(len: usize) -> usize { len + Key::NonceSize::USIZE + Key::TagSize::USIZE } /// Encrypt a value with a predictable nonce, making it searchable - pub fn encrypt_searchable( + fn encrypt_searchable( mut buffer: SecretBytes, enc_key: &Key, hmac_key: &HmacKey, ) -> Result, Error> { - ArrayKey::::temp(|nonce| { - hmac_key - .hmac_deriver(&[buffer.as_ref()]) - .derive_key_bytes(nonce)?; - enc_key.encrypt_in_place(&mut buffer, nonce.as_ref(), &[])?; - buffer.buffer_insert(0, nonce.as_ref())?; - Ok(buffer.into_vec()) - }) + let nonce = ArrayKey::::from_key_derivation( + hmac_key.hmac_deriver(&[buffer.as_ref()]), + )?; + enc_key.encrypt_in_place(&mut buffer, nonce.as_ref(), &[])?; + buffer.buffer_insert(0, nonce.as_ref())?; + Ok(buffer.into_vec()) } - pub fn encrypt(mut buffer: SecretBytes, enc_key: &Key) -> Result, Error> { + fn encrypt(mut buffer: SecretBytes, enc_key: &Key) -> Result, Error> { let nonce = ArrayKey::::random(); enc_key.encrypt_in_place(&mut buffer, nonce.as_ref(), &[])?; buffer.buffer_insert(0, nonce.as_ref())?; Ok(buffer.into_vec()) } - pub fn decrypt(ciphertext: Vec, enc_key: &Key) -> Result { + fn decrypt(ciphertext: Vec, enc_key: &Key) -> Result { let nonce_len = Key::NonceSize::USIZE; if ciphertext.len() < nonce_len { return Err(err_msg!(Encryption, "invalid encrypted value")); @@ -116,36 +114,31 @@ where Ok(buffer) } - pub fn derive_value_key( - &self, - category: &[u8], - name: &[u8], - out: &mut [u8], - ) -> Result<(), Error> { - Ok(self - .item_hmac_key - .hmac_deriver(&[ + #[inline] + fn derive_value_key(&self, category: &[u8], name: &[u8]) -> Result { + Ok(Key::from_key_derivation(self.item_hmac_key.hmac_deriver( + &[ &(category.len() as u32).to_be_bytes(), category, &(name.len() as u32).to_be_bytes(), name, - ]) - .derive_key_bytes(out)?) + ], + ))?) } - pub fn encrypt_tag_name(&self, name: SecretBytes) -> Result, Error> { + fn encrypt_tag_name(&self, name: SecretBytes) -> Result, Error> { Self::encrypt_searchable(name, &self.tag_name_key, &self.tags_hmac_key) } - pub fn encrypt_tag_value(&self, value: SecretBytes) -> Result, Error> { + fn encrypt_tag_value(&self, value: SecretBytes) -> Result, Error> { Self::encrypt_searchable(value, &self.tag_value_key, &self.tags_hmac_key) } - pub fn decrypt_tag_name(&self, enc_tag_name: Vec) -> Result { + fn decrypt_tag_name(&self, enc_tag_name: Vec) -> Result { Self::decrypt(enc_tag_name, &self.tag_name_key) } - pub fn decrypt_tag_value(&self, enc_tag_value: Vec) -> Result { + fn decrypt_tag_value(&self, enc_tag_value: Vec) -> Result { Self::decrypt(enc_tag_value, &self.tag_value_key) } } @@ -164,8 +157,8 @@ impl Eq for ProfileKeyImpl {} impl EntryEncryptor for ProfileKeyImpl where - Key: KeyGen + KeyMeta + KeyAeadInPlace + KeyAeadMeta + KeySecretBytes, - HmacKey: KeyGen + HmacDerive, + Key: KeyAeadInPlace + KeyAeadMeta + FromKeyDerivation, + HmacKey: HmacDerive, { fn prepare_input(input: &[u8]) -> SecretBytes { let mut buf = SecretBytes::with_capacity(Self::encrypted_size(input.len())); @@ -187,11 +180,8 @@ where name: &[u8], value: SecretBytes, ) -> Result, Error> { - ArrayKey::::temp(|value_key| { - self.derive_value_key(category, name, value_key)?; - let value_key = Key::from_secret_bytes(&*value_key)?; - Self::encrypt(value, &value_key) - }) + let value_key = self.derive_value_key(category, name)?; + Self::encrypt(value, &value_key) } fn decrypt_entry_category(&self, enc_category: Vec) -> Result { @@ -208,11 +198,8 @@ where name: &[u8], enc_value: Vec, ) -> Result { - ArrayKey::::temp(|value_key| { - self.derive_value_key(category, name, value_key)?; - let value_key = Key::from_secret_bytes(&*value_key)?; - Self::decrypt(enc_value, &value_key) - }) + let value_key = self.derive_value_key(category, name)?; + Self::decrypt(enc_value, &value_key) } fn encrypt_entry_tags(&self, tags: Vec) -> Result, Error> { From 9534a0ce378c2e440123efdd7fdf22cb9c5b1a35 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Tue, 4 May 2021 09:57:31 -0700 Subject: [PATCH 096/116] add version tag to profile key Signed-off-by: Andrew Whitehead --- src/protect/profile_key.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/protect/profile_key.rs b/src/protect/profile_key.rs index 23ab6479..ec19a61d 100644 --- a/src/protect/profile_key.rs +++ b/src/protect/profile_key.rs @@ -24,6 +24,7 @@ pub type ProfileKey = ProfileKeyImpl, HmacKey>; deserialize = "Key: for<'a> Deserialize<'a>, HmacKey: for<'a> Deserialize<'a>", serialize = "Key: Serialize, HmacKey: Serialize" ))] +#[serde(tag = "ver", rename = "1")] pub struct ProfileKeyImpl { #[serde(rename = "ick")] pub category_key: Key, @@ -126,19 +127,19 @@ where ))?) } - fn encrypt_tag_name(&self, name: SecretBytes) -> Result, Error> { + pub fn encrypt_tag_name(&self, name: SecretBytes) -> Result, Error> { Self::encrypt_searchable(name, &self.tag_name_key, &self.tags_hmac_key) } - fn encrypt_tag_value(&self, value: SecretBytes) -> Result, Error> { + pub fn encrypt_tag_value(&self, value: SecretBytes) -> Result, Error> { Self::encrypt_searchable(value, &self.tag_value_key, &self.tags_hmac_key) } - fn decrypt_tag_name(&self, enc_tag_name: Vec) -> Result { + pub fn decrypt_tag_name(&self, enc_tag_name: Vec) -> Result { Self::decrypt(enc_tag_name, &self.tag_name_key) } - fn decrypt_tag_value(&self, enc_tag_value: Vec) -> Result { + pub fn decrypt_tag_value(&self, enc_tag_value: Vec) -> Result { Self::decrypt(enc_tag_value, &self.tag_value_key) } } From 66e42393de987ef34966a61fe56f059948120432 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Tue, 4 May 2021 15:08:49 -0700 Subject: [PATCH 097/116] any key and trait cleanups Signed-off-by: Andrew Whitehead --- askar-crypto/src/alg/aesgcm.rs | 1 + askar-crypto/src/alg/any.rs | 270 +++++++++++++------------ askar-crypto/src/alg/bls.rs | 1 + askar-crypto/src/alg/chacha20.rs | 1 + askar-crypto/src/alg/ed25519.rs | 1 + askar-crypto/src/alg/k256.rs | 1 + askar-crypto/src/alg/p256.rs | 1 + askar-crypto/src/alg/x25519.rs | 1 + askar-crypto/src/encrypt/crypto_box.rs | 2 +- askar-crypto/src/repr.rs | 48 +++-- src/kms/key.rs | 1 + 11 files changed, 183 insertions(+), 145 deletions(-) diff --git a/askar-crypto/src/alg/aesgcm.rs b/askar-crypto/src/alg/aesgcm.rs index 92858658..d7064087 100644 --- a/askar-crypto/src/alg/aesgcm.rs +++ b/askar-crypto/src/alg/aesgcm.rs @@ -248,6 +248,7 @@ where mod tests { use super::*; use crate::buffer::SecretBytes; + use crate::repr::ToSecretBytes; #[test] fn encrypt_round_trip() { diff --git a/askar-crypto/src/alg/any.rs b/askar-crypto/src/alg/any.rs index 4e955aa8..d61ae90e 100644 --- a/askar-crypto/src/alg/any.rs +++ b/askar-crypto/src/alg/any.rs @@ -14,12 +14,12 @@ use super::p256::{self, P256KeyPair}; use super::x25519::{self, X25519KeyPair}; use super::{AesTypes, BlsCurves, Chacha20Types, EcCurves, HasKeyAlg, KeyAlg}; use crate::{ - buffer::{ResizeBuffer, SecretBytes, WriteBuffer}, + buffer::{ResizeBuffer, WriteBuffer}, encrypt::{KeyAeadInPlace, KeyAeadParams}, error::Error, jwk::{FromJwk, JwkEncoder, JwkParts, ToJwk}, kdf::{FromKeyDerivation, FromKeyExchange, KeyDerivation, KeyExchange}, - repr::{KeyGen, KeyPublicBytes, KeySecretBytes}, + repr::{KeyGen, KeyPublicBytes, KeySecretBytes, ToPublicBytes, ToSecretBytes}, sign::{KeySigVerify, KeySign, SignatureType}, }; @@ -331,25 +331,6 @@ fn convert_key_any(key: &AnyKey, alg: KeyAlg) -> Result { } } -impl KeyExchange for AnyKey { - fn write_key_exchange(&self, other: &AnyKey, out: &mut dyn WriteBuffer) -> Result<(), Error> { - match self.key_type_id() { - s if s != other.key_type_id() => Err(err_msg!(Unsupported, "Unsupported key exchange")), - s if s == TypeId::of::() => Ok(self - .assume::() - .write_key_exchange(other.assume::(), out)?), - s if s == TypeId::of::() => Ok(self - .assume::() - .write_key_exchange(other.assume::(), out)?), - s if s == TypeId::of::() => Ok(self - .assume::() - .write_key_exchange(other.assume::(), out)?), - #[allow(unreachable_patterns)] - _ => return Err(err_msg!(Unsupported, "Unsupported key exchange")), - } - } -} - impl FromJwk for Box { fn from_jwk_parts(jwk: JwkParts<'_>) -> Result { from_jwk_any(jwk) @@ -378,108 +359,138 @@ fn from_jwk_any(jwk: JwkParts<'_>) -> Result { } ("EC", c) if c == k256::JWK_CURVE => K256KeyPair::from_jwk_parts(jwk).map(R::alloc_key), ("EC", c) if c == p256::JWK_CURVE => P256KeyPair::from_jwk_parts(jwk).map(R::alloc_key), - // FIXME implement symmetric keys + // FIXME implement symmetric keys? _ => Err(err_msg!(Unsupported, "Unsupported JWK for key import")), } } -impl AnyKey { - pub fn to_public_bytes(&self) -> Result { - match self.key_type_id() { - s if s == TypeId::of::>() => { - Ok(self.assume::>().to_public_bytes()?) - } - s if s == TypeId::of::>() => { - Ok(self.assume::>().to_public_bytes()?) - } - s if s == TypeId::of::>() => { - Ok(self.assume::>().to_public_bytes()?) - } - s if s == TypeId::of::() => { - Ok(self.assume::().to_public_bytes()?) - } - s if s == TypeId::of::() => { - Ok(self.assume::().to_public_bytes()?) - } - s if s == TypeId::of::() => { - Ok(self.assume::().to_public_bytes()?) - } - s if s == TypeId::of::() => { - Ok(self.assume::().to_public_bytes()?) - } - #[allow(unreachable_patterns)] - _ => return Err(err_msg!(Unsupported, "Unsupported key exchange")), - } +macro_rules! match_key_alg { + ($slf:expr, $ty:ty, $($kty:ident),+ $(,$errmsg:literal)?) => {{ + fn matcher(key: &AnyKey) -> Result<$ty, Error> { + let alg = key.algorithm(); + match_key_alg!(@ $($kty)+ ; key, alg); + return Err(err_msg!(Unsupported $(,$errmsg)?)) + } + matcher($slf) + }}; + (@ ; $key:ident, $alg:ident) => {()}; + (@ Aes $($rest:ident)*; $key:ident, $alg:ident) => {{ + if $alg == KeyAlg::Aes(AesTypes::A128GCM) { + return Ok($key.assume::>()); + } + if $alg == KeyAlg::Aes(AesTypes::A256GCM) { + return Ok($key.assume::>()); + } + match_key_alg!(@ $($rest)*; $key, $alg) + }}; + (@ Bls $($rest:ident)*; $key:ident, $alg:ident) => {{ + if $alg == KeyAlg::Bls12_381(BlsCurves::G1) { + return Ok($key.assume::>()); + } + if $alg == KeyAlg::Bls12_381(BlsCurves::G2) { + return Ok($key.assume::>()); + } + if $alg == KeyAlg::Bls12_381(BlsCurves::G1G2) { + return Ok($key.assume::>()); + } + match_key_alg!(@ $($rest)*; $key, $alg) + }}; + (@ Chacha $($rest:ident)*; $key:ident, $alg:ident) => {{ + if $alg == KeyAlg::Chacha20(Chacha20Types::C20P) { + return Ok($key.assume::>()); + } + match_key_alg!(@ $($rest)*; $key, $alg) + }}; + (@ Ed25519 $($rest:ident)*; $key:ident, $alg:ident) => {{ + if $alg == KeyAlg::Ed25519 { + return Ok($key.assume::()) + } + match_key_alg!(@ $($rest)*; $key, $alg) + }}; + (@ X25519 $($rest:ident)*; $key:ident, $alg:ident) => {{ + if $alg == KeyAlg::X25519 { + return Ok($key.assume::()) + } + match_key_alg!(@ $($rest)*; $key, $alg) + }}; + (@ K256 $($rest:ident)*; $key:ident, $alg:ident) => {{ + if $alg == KeyAlg::EcCurve(EcCurves::Secp256k1) { + return Ok($key.assume::()) + } + match_key_alg!(@ $($rest)*; $key, $alg) + }}; + (@ P256 $($rest:ident)*; $key:ident, $alg:ident) => {{ + if $alg == KeyAlg::EcCurve(EcCurves::Secp256r1) { + return Ok($key.assume::()) + } + match_key_alg!(@ $($rest)*; $key, $alg) + }}; +} + +impl ToPublicBytes for AnyKey { + fn write_public_bytes(&self, out: &mut dyn WriteBuffer) -> Result<(), Error> { + let key = match_key_alg! { + self, + &dyn ToPublicBytes, + Bls, + Ed25519, + K256, + P256, + X25519, + "Public key export is not supported for this key type" + }?; + key.write_public_bytes(out) } +} - pub fn to_secret_bytes(&self) -> Result { - match self.key_type_id() { - s if s == TypeId::of::>() => { - Ok(self.assume::>().to_secret_bytes()?) - } - s if s == TypeId::of::>() => { - Ok(self.assume::>().to_secret_bytes()?) - } - s if s == TypeId::of::>() => { - Ok(self.assume::>().to_secret_bytes()?) - } - s if s == TypeId::of::>() => { - Ok(self.assume::>().to_secret_bytes()?) - } - s if s == TypeId::of::>() => { - Ok(self.assume::>().to_secret_bytes()?) - } - s if s == TypeId::of::>() => { - Ok(self.assume::>().to_secret_bytes()?) - } - s if s == TypeId::of::>() => { - Ok(self.assume::>().to_secret_bytes()?) - } - s if s == TypeId::of::() => { - Ok(self.assume::().to_secret_bytes()?) - } - s if s == TypeId::of::() => { - Ok(self.assume::().to_secret_bytes()?) - } - s if s == TypeId::of::() => { - Ok(self.assume::().to_secret_bytes()?) - } - s if s == TypeId::of::() => { - Ok(self.assume::().to_secret_bytes()?) - } - #[allow(unreachable_patterns)] - _ => return Err(err_msg!(Unsupported, "Unsupported key exchange")), - } +impl ToSecretBytes for AnyKey { + fn write_secret_bytes(&self, out: &mut dyn WriteBuffer) -> Result<(), Error> { + let key = match_key_alg! { + self, + &dyn ToSecretBytes, + Aes, + Bls, + Chacha, + Ed25519, + K256, + P256, + X25519, + "Secret key export is not supported for this key type" + }?; + key.write_secret_bytes(out) } } -macro_rules! match_key_types { - ($slf:expr, $( $t:ty ),+; $errmsg:expr) => { - match $slf.key_type_id() { - $( - t if t == TypeId::of::<$t>() => $slf.assume::<$t>(), - )+ +impl KeyExchange for AnyKey { + fn write_key_exchange(&self, other: &AnyKey, out: &mut dyn WriteBuffer) -> Result<(), Error> { + if self.key_type_id() != other.key_type_id() { + return Err(err_msg!(Unsupported, "Unsupported key exchange")); + } + match self.algorithm() { + KeyAlg::X25519 => Ok(self + .assume::() + .write_key_exchange(other.assume::(), out)?), + KeyAlg::EcCurve(EcCurves::Secp256k1) => Ok(self + .assume::() + .write_key_exchange(other.assume::(), out)?), + KeyAlg::EcCurve(EcCurves::Secp256r1) => Ok(self + .assume::() + .write_key_exchange(other.assume::(), out)?), #[allow(unreachable_patterns)] - _ => { - return Err(err_msg!( - Unsupported, - $errmsg - )) - } - } - }; + _ => return Err(err_msg!(Unsupported, "Unsupported key exchange")), + } + } } impl AnyKey { fn key_as_aead(&self) -> Result<&dyn KeyAeadInPlace, Error> { - Ok(match_key_types! { + match_key_alg! { self, - AesGcmKey, - AesGcmKey, - Chacha20Key, - Chacha20Key; + &dyn KeyAeadInPlace, + Aes, + Chacha, "AEAD is not supported for this key type" - }) + } } } @@ -513,21 +524,18 @@ impl KeyAeadInPlace for AnyKey { impl ToJwk for AnyKey { fn encode_jwk(&self, enc: &mut JwkEncoder<'_>) -> Result<(), Error> { - let key: &dyn ToJwk = match_key_types! { + let key = match_key_alg! { self, - AesGcmKey, - AesGcmKey, - BlsKeyPair, - BlsKeyPair, - BlsKeyPair, - Chacha20Key, - Chacha20Key, - Ed25519KeyPair, - X25519KeyPair, - K256KeyPair, - P256KeyPair; + &dyn ToJwk, + Aes, + Bls, + Chacha, + Ed25519, + K256, + P256, + X25519, "JWK export is not supported for this key type" - }; + }?; key.encode_jwk(enc) } } @@ -539,13 +547,14 @@ impl KeySign for AnyKey { sig_type: Option, out: &mut dyn WriteBuffer, ) -> Result<(), Error> { - let key: &dyn KeySign = match_key_types! { + let key = match_key_alg! { self, - Ed25519KeyPair, - K256KeyPair, - P256KeyPair; + &dyn KeySign, + Ed25519, + K256, + P256, "Signing is not supported for this key type" - }; + }?; key.write_signature(message, sig_type, out) } } @@ -557,13 +566,14 @@ impl KeySigVerify for AnyKey { signature: &[u8], sig_type: Option, ) -> Result { - let key: &dyn KeySigVerify = match_key_types! { + let key = match_key_alg! { self, - Ed25519KeyPair, - K256KeyPair, - P256KeyPair; + &dyn KeySigVerify, + Ed25519, + K256, + P256, "Signature verification is not supported for this key type" - }; + }?; key.verify_signature(message, signature, sig_type) } } diff --git a/askar-crypto/src/alg/bls.rs b/askar-crypto/src/alg/bls.rs index 91bdb09d..17971e3f 100644 --- a/askar-crypto/src/alg/bls.rs +++ b/askar-crypto/src/alg/bls.rs @@ -395,6 +395,7 @@ impl From<&BlsKeyPair> for BlsKeyPair { #[cfg(test)] mod tests { use super::*; + use crate::repr::{ToPublicBytes, ToSecretBytes}; use std::string::ToString; // test against EIP-2333 (updated for signatures draft 4) diff --git a/askar-crypto/src/alg/chacha20.rs b/askar-crypto/src/alg/chacha20.rs index eed87769..97e1955b 100644 --- a/askar-crypto/src/alg/chacha20.rs +++ b/askar-crypto/src/alg/chacha20.rs @@ -248,6 +248,7 @@ where mod tests { use super::*; use crate::buffer::SecretBytes; + use crate::repr::ToSecretBytes; #[test] fn encrypt_round_trip() { diff --git a/askar-crypto/src/alg/ed25519.rs b/askar-crypto/src/alg/ed25519.rs index a9f934a0..e9c97a22 100644 --- a/askar-crypto/src/alg/ed25519.rs +++ b/askar-crypto/src/alg/ed25519.rs @@ -316,6 +316,7 @@ impl Debug for Ed25519SigningKey<'_> { #[cfg(test)] mod tests { use super::*; + use crate::repr::{ToPublicBytes, ToSecretBytes}; #[test] fn expand_keypair() { diff --git a/askar-crypto/src/alg/k256.rs b/askar-crypto/src/alg/k256.rs index 7c01de0d..57e1e9e4 100644 --- a/askar-crypto/src/alg/k256.rs +++ b/askar-crypto/src/alg/k256.rs @@ -293,6 +293,7 @@ impl KeyExchange for K256KeyPair { #[cfg(test)] mod tests { use super::*; + use crate::repr::ToPublicBytes; #[test] fn jwk_expected() { diff --git a/askar-crypto/src/alg/p256.rs b/askar-crypto/src/alg/p256.rs index 9bfc3e74..cdc9e108 100644 --- a/askar-crypto/src/alg/p256.rs +++ b/askar-crypto/src/alg/p256.rs @@ -293,6 +293,7 @@ impl KeyExchange for P256KeyPair { #[cfg(test)] mod tests { use super::*; + use crate::repr::ToPublicBytes; #[test] fn jwk_expected() { diff --git a/askar-crypto/src/alg/x25519.rs b/askar-crypto/src/alg/x25519.rs index df17b136..a3c78dc1 100644 --- a/askar-crypto/src/alg/x25519.rs +++ b/askar-crypto/src/alg/x25519.rs @@ -254,6 +254,7 @@ impl TryFrom<&Ed25519KeyPair> for X25519KeyPair { #[cfg(test)] mod tests { use super::*; + use crate::repr::ToPublicBytes; #[test] fn jwk_expected() { diff --git a/askar-crypto/src/encrypt/crypto_box.rs b/askar-crypto/src/encrypt/crypto_box.rs index 4e8c1547..22e66488 100644 --- a/askar-crypto/src/encrypt/crypto_box.rs +++ b/askar-crypto/src/encrypt/crypto_box.rs @@ -128,7 +128,7 @@ pub fn crypto_box_seal_open( mod tests { use super::*; use crate::buffer::SecretBytes; - use crate::repr::KeySecretBytes; + use crate::repr::{KeySecretBytes, ToPublicBytes}; #[test] fn crypto_box_round_trip_expected() { diff --git a/askar-crypto/src/repr.rs b/askar-crypto/src/repr.rs index 35920534..8c800ab5 100644 --- a/askar-crypto/src/repr.rs +++ b/askar-crypto/src/repr.rs @@ -21,28 +21,38 @@ pub trait KeySecretBytes { /// Access a temporary slice of the key secret bytes, if any. fn with_secret_bytes(&self, f: impl FnOnce(Option<&[u8]>) -> O) -> O; +} +/// Object-safe trait for exporting key secret bytes +pub trait ToSecretBytes { /// Write the key secret bytes to a buffer. - fn to_secret_bytes_buffer(&self, out: &mut B) -> Result<(), Error> { - self.with_secret_bytes(|buf| { - if let Some(buf) = buf { - out.buffer_write(buf) - } else { - Err(err_msg!(MissingSecretKey)) - } - }) - } + fn write_secret_bytes(&self, out: &mut dyn WriteBuffer) -> Result<(), Error>; #[cfg(feature = "alloc")] #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] /// Write the key secret bytes to a new allocated buffer. fn to_secret_bytes(&self) -> Result { let mut buf = SecretBytes::with_capacity(128); - self.to_secret_bytes_buffer(&mut buf)?; + self.write_secret_bytes(&mut buf)?; Ok(buf) } } +impl ToSecretBytes for K +where + K: KeySecretBytes, +{ + fn write_secret_bytes(&self, out: &mut dyn WriteBuffer) -> Result<(), Error> { + self.with_secret_bytes(|buf| { + if let Some(buf) = buf { + out.buffer_write(buf) + } else { + Err(err_msg!(MissingSecretKey)) + } + }) + } +} + /// Convert between key instance and key public bytes. pub trait KeyPublicBytes { /// Create a new key instance from a slice of public key bytes. @@ -52,22 +62,32 @@ pub trait KeyPublicBytes { /// Access a temporary slice of the key public bytes. fn with_public_bytes(&self, f: impl FnOnce(&[u8]) -> O) -> O; +} +/// Object-safe trait for exporting key public bytes +pub trait ToPublicBytes { /// Write the key public bytes to a buffer. - fn to_public_bytes_buffer(&self, out: &mut B) -> Result<(), Error> { - self.with_public_bytes(|buf| out.buffer_write(buf)) - } + fn write_public_bytes(&self, out: &mut dyn WriteBuffer) -> Result<(), Error>; #[cfg(feature = "alloc")] #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] /// Write the key public bytes to a new allocated buffer. fn to_public_bytes(&self) -> Result { let mut buf = SecretBytes::with_capacity(128); - self.to_public_bytes_buffer(&mut buf)?; + self.write_public_bytes(&mut buf)?; Ok(buf) } } +impl ToPublicBytes for K +where + K: KeyPublicBytes, +{ + fn write_public_bytes(&self, out: &mut dyn WriteBuffer) -> Result<(), Error> { + self.with_public_bytes(|buf| out.buffer_write(buf)) + } +} + /// Convert between keypair instance and keypair (secret and public) bytes pub trait KeypairBytes { /// Create a new key instance from a slice of keypair bytes. diff --git a/src/kms/key.rs b/src/kms/key.rs index d509d337..6884b079 100644 --- a/src/kms/key.rs +++ b/src/kms/key.rs @@ -12,6 +12,7 @@ use crate::{ jwk::{FromJwk, ToJwk}, kdf::{KeyDerivation, KeyExchange}, random::fill_random, + repr::{ToPublicBytes, ToSecretBytes}, sign::{KeySigVerify, KeySign, SignatureType}, Error as CryptoError, }, From 73214052f5e42d0fe22f735a4e4aaee6cd445684 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Tue, 4 May 2021 16:01:14 -0700 Subject: [PATCH 098/116] add feature independence for key types Signed-off-by: Andrew Whitehead --- askar-crypto/Cargo.toml | 33 +++-- askar-crypto/src/alg/aesgcm.rs | 4 +- askar-crypto/src/alg/any.rs | 160 ++++++++++++++++++++++--- askar-crypto/src/alg/mod.rs | 7 ++ askar-crypto/src/buffer/secret.rs | 1 + askar-crypto/src/encrypt/crypto_box.rs | 2 +- askar-crypto/src/encrypt/mod.rs | 4 +- askar-crypto/src/kdf/ecdh_1pu.rs | 2 + askar-crypto/src/kdf/ecdh_es.rs | 2 + askar-crypto/src/random.rs | 5 + 10 files changed, 184 insertions(+), 36 deletions(-) diff --git a/askar-crypto/Cargo.toml b/askar-crypto/Cargo.toml index 5de1e98b..e6544f2b 100644 --- a/askar-crypto/Cargo.toml +++ b/askar-crypto/Cargo.toml @@ -15,10 +15,17 @@ features = ["std"] rustdoc-args = ["--cfg", "docsrs"] [features] -default = ["alloc", "any_key"] +default = ["alloc", "any_key", "all_keys", "crypto_box"] alloc = [] -any_key = ["alloc"] std = ["alloc"] +all_keys = ["aes", "bls", "chacha", "ec_curves", "ed25519"] +any_key = ["alloc"] +aes = ["aes-gcm"] +bls = ["bls12_381", "hkdf"] +chacha = ["chacha20", "chacha20poly1305"] +crypto_box = ["alloc", "crypto_box_rs", "ed25519"] +ec_curves = ["k256", "p256"] +ed25519 = ["curve25519-dalek", "ed25519-dalek", "x25519-dalek"] [dev-dependencies] base64 = { version = "*", default-features = false, features = ["alloc"] } @@ -36,24 +43,24 @@ harness = false [dependencies] aead = "0.3" -aes-gcm = { version = "0.8", default-features = false, features = ["aes"] } +aes-gcm = { version = "0.8", default-features = false, features = ["aes"], optional = true } argon2 = { version = "0.1", default-features = false, features = ["password-hash"] } base64 = { version = "0.13", default-features = false } blake2 = { version = "0.9", default-features = false } -bls12_381 = { version = "0.4", default-features = false, features = ["groups"] } -chacha20 = "0.6" # inherit from chacha20poly1305 -chacha20poly1305 = { version = "0.7", default-features = false, features = ["chacha20"] } -crypto_box = { version = "0.5", default-features = false, features = ["u64_backend"] } -curve25519-dalek = { version = "3.0", default-features = false, features = ["u64_backend"] } -ed25519-dalek = { version = "1.0", default-features = false, features = ["u64_backend"] } +bls12_381 = { version = "0.4", default-features = false, features = ["groups"], optional = true } +chacha20 = { version = "0.6", optional = true } # should match chacha20poly1305 +chacha20poly1305 = { version = "0.7", default-features = false, features = ["chacha20", "xchacha20poly1305"], optional = true } +crypto_box_rs = { package = "crypto_box", version = "0.5", default-features = false, features = ["u64_backend"], optional = true } +curve25519-dalek = { version = "3.0", default-features = false, features = ["u64_backend"], optional = true } +ed25519-dalek = { version = "1.0", default-features = false, features = ["u64_backend"], optional = true } digest = "0.9" group = "0.9" -hkdf = "0.11" -k256 = { version = "0.8", default-features = false, features = ["arithmetic", "ecdsa", "ecdh", "sha256", "zeroize"] } -p256 = { version = "0.8", default-features = false, features = ["arithmetic", "ecdsa", "ecdh", "zeroize"] } +hkdf = { version = "0.11", optional = true } +k256 = { version = "0.8", default-features = false, features = ["arithmetic", "ecdsa", "ecdh", "sha256", "zeroize"], optional = true } +p256 = { version = "0.8", default-features = false, features = ["arithmetic", "ecdsa", "ecdh", "zeroize"], optional = true } rand = { version = "0.8", default-features = false, features = ["getrandom"] } serde = { version = "1.0", default-features = false, features = ["derive"] } serde_json = { version = "1.0", default-features = false } sha2 = { version = "0.9", default-features = false } -x25519-dalek = { version = "1.1", default-features = false, features = ["u64_backend"] } +x25519-dalek = { version = "1.1", default-features = false, features = ["u64_backend"], optional = true } zeroize = { version = "1.1.0", features = ["zeroize_derive"] } diff --git a/askar-crypto/src/alg/aesgcm.rs b/askar-crypto/src/alg/aesgcm.rs index d7064087..10ee7bee 100644 --- a/askar-crypto/src/alg/aesgcm.rs +++ b/askar-crypto/src/alg/aesgcm.rs @@ -8,6 +8,8 @@ use serde::{Deserialize, Serialize}; use zeroize::Zeroize; use super::{AesTypes, HasKeyAlg, KeyAlg}; +#[cfg(feature = "chacha")] +use crate::random::fill_random_deterministic; use crate::{ buffer::{ArrayKey, ResizeBuffer, Writer}, encrypt::{KeyAeadInPlace, KeyAeadMeta, KeyAeadParams}, @@ -15,7 +17,6 @@ use crate::{ generic_array::{typenum::Unsigned, GenericArray}, jwk::{JwkEncoder, ToJwk}, kdf::{FromKeyDerivation, FromKeyExchange, KeyDerivation, KeyExchange}, - random::fill_random_deterministic, repr::{KeyGen, KeyMeta, KeySecretBytes}, }; @@ -81,6 +82,7 @@ impl AesGcmKey { /// The length of the AEAD encryption tag pub const TAG_LENGTH: usize = TagSize::::USIZE; + #[cfg(feature = "chacha")] /// Construct a new deterministic AES key from a seed value pub fn from_seed(seed: &[u8]) -> Result { Ok(Self(KeyType::::try_new_with(|arr| { diff --git a/askar-crypto/src/alg/any.rs b/askar-crypto/src/alg/any.rs index d61ae90e..f3dc0248 100644 --- a/askar-crypto/src/alg/any.rs +++ b/askar-crypto/src/alg/any.rs @@ -1,28 +1,57 @@ use alloc::{boxed::Box, sync::Arc}; +#[cfg(feature = "ed25519")] +use core::convert::TryFrom; use core::{ any::{Any, TypeId}, - convert::TryFrom, fmt::Debug, }; -use super::aesgcm::{AesGcmKey, A128GCM, A256GCM}; -use super::bls::{BlsKeyPair, BlsPublicKeyType, G1, G1G2, G2}; -use super::chacha20::{Chacha20Key, C20P, XC20P}; +#[cfg(feature = "aes")] +use super::{ + aesgcm::{AesGcmKey, A128GCM, A256GCM}, + AesTypes, +}; + +#[cfg(feature = "bls")] +use super::{ + bls::{BlsKeyPair, BlsPublicKeyType, G1, G1G2, G2}, + BlsCurves, +}; + +#[cfg(feature = "chacha")] +use super::{ + chacha20::{Chacha20Key, C20P, XC20P}, + Chacha20Types, +}; + +#[cfg(feature = "ed25519")] use super::ed25519::{self, Ed25519KeyPair}; +#[cfg(feature = "ed25519")] +use super::x25519::{self, X25519KeyPair}; + +#[cfg(feature = "k256")] use super::k256::{self, K256KeyPair}; + +#[cfg(feature = "p256")] use super::p256::{self, P256KeyPair}; -use super::x25519::{self, X25519KeyPair}; -use super::{AesTypes, BlsCurves, Chacha20Types, EcCurves, HasKeyAlg, KeyAlg}; + +use super::{HasKeyAlg, KeyAlg}; use crate::{ buffer::{ResizeBuffer, WriteBuffer}, encrypt::{KeyAeadInPlace, KeyAeadParams}, error::Error, jwk::{FromJwk, JwkEncoder, JwkParts, ToJwk}, - kdf::{FromKeyDerivation, FromKeyExchange, KeyDerivation, KeyExchange}, + kdf::{KeyDerivation, KeyExchange}, repr::{KeyGen, KeyPublicBytes, KeySecretBytes, ToPublicBytes, ToSecretBytes}, sign::{KeySigVerify, KeySign, SignatureType}, }; +#[cfg(any(feature = "k256", feature = "p256"))] +use super::EcCurves; + +#[cfg(any(feature = "aes", feature = "chacha"))] +use crate::kdf::{FromKeyDerivation, FromKeyExchange}; + #[derive(Debug)] pub struct KeyT(T); @@ -155,18 +184,29 @@ impl AnyKeyCreate for Arc { #[inline] fn generate_any(alg: KeyAlg) -> Result { match alg { + #[cfg(feature = "aes")] KeyAlg::Aes(AesTypes::A128GCM) => AesGcmKey::::generate().map(R::alloc_key), + #[cfg(feature = "aes")] KeyAlg::Aes(AesTypes::A256GCM) => AesGcmKey::::generate().map(R::alloc_key), + #[cfg(feature = "bls")] KeyAlg::Bls12_381(BlsCurves::G1) => BlsKeyPair::::generate().map(R::alloc_key), + #[cfg(feature = "bls")] KeyAlg::Bls12_381(BlsCurves::G2) => BlsKeyPair::::generate().map(R::alloc_key), + #[cfg(feature = "bls")] KeyAlg::Bls12_381(BlsCurves::G1G2) => BlsKeyPair::::generate().map(R::alloc_key), + #[cfg(feature = "chacha")] KeyAlg::Chacha20(Chacha20Types::C20P) => Chacha20Key::::generate().map(R::alloc_key), + #[cfg(feature = "chacha")] KeyAlg::Chacha20(Chacha20Types::XC20P) => { Chacha20Key::::generate().map(R::alloc_key) } + #[cfg(feature = "ed25519")] KeyAlg::Ed25519 => Ed25519KeyPair::generate().map(R::alloc_key), + #[cfg(feature = "ed25519")] KeyAlg::X25519 => X25519KeyPair::generate().map(R::alloc_key), + #[cfg(feature = "k256")] KeyAlg::EcCurve(EcCurves::Secp256k1) => K256KeyPair::generate().map(R::alloc_key), + #[cfg(feature = "p256")] KeyAlg::EcCurve(EcCurves::Secp256r1) => P256KeyPair::generate().map(R::alloc_key), #[allow(unreachable_patterns)] _ => { @@ -181,20 +221,27 @@ fn generate_any(alg: KeyAlg) -> Result { #[inline] fn from_public_bytes_any(alg: KeyAlg, public: &[u8]) -> Result { match alg { + #[cfg(feature = "bls")] KeyAlg::Bls12_381(BlsCurves::G1) => { BlsKeyPair::::from_public_bytes(public).map(R::alloc_key) } + #[cfg(feature = "bls")] KeyAlg::Bls12_381(BlsCurves::G2) => { BlsKeyPair::::from_public_bytes(public).map(R::alloc_key) } + #[cfg(feature = "bls")] KeyAlg::Bls12_381(BlsCurves::G1G2) => { BlsKeyPair::::from_public_bytes(public).map(R::alloc_key) } + #[cfg(feature = "ed25519")] KeyAlg::Ed25519 => Ed25519KeyPair::from_public_bytes(public).map(R::alloc_key), + #[cfg(feature = "ed25519")] KeyAlg::X25519 => X25519KeyPair::from_public_bytes(public).map(R::alloc_key), + #[cfg(feature = "k256")] KeyAlg::EcCurve(EcCurves::Secp256k1) => { K256KeyPair::from_public_bytes(public).map(R::alloc_key) } + #[cfg(feature = "p256")] KeyAlg::EcCurve(EcCurves::Secp256r1) => { P256KeyPair::from_public_bytes(public).map(R::alloc_key) } @@ -202,7 +249,7 @@ fn from_public_bytes_any(alg: KeyAlg, public: &[u8]) -> Result { return Err(err_msg!( Unsupported, - "Unsupported algorithm for key import" + "Unsupported algorithm for public key import" )) } } @@ -211,32 +258,43 @@ fn from_public_bytes_any(alg: KeyAlg, public: &[u8]) -> Result(alg: KeyAlg, secret: &[u8]) -> Result { match alg { + #[cfg(feature = "aes")] KeyAlg::Aes(AesTypes::A128GCM) => { AesGcmKey::::from_secret_bytes(secret).map(R::alloc_key) } + #[cfg(feature = "aes")] KeyAlg::Aes(AesTypes::A256GCM) => { AesGcmKey::::from_secret_bytes(secret).map(R::alloc_key) } + #[cfg(feature = "bls")] KeyAlg::Bls12_381(BlsCurves::G1) => { BlsKeyPair::::from_secret_bytes(secret).map(R::alloc_key) } + #[cfg(feature = "bls")] KeyAlg::Bls12_381(BlsCurves::G2) => { BlsKeyPair::::from_secret_bytes(secret).map(R::alloc_key) } + #[cfg(feature = "bls")] KeyAlg::Bls12_381(BlsCurves::G1G2) => { BlsKeyPair::::from_secret_bytes(secret).map(R::alloc_key) } + #[cfg(feature = "chacha")] KeyAlg::Chacha20(Chacha20Types::C20P) => { Chacha20Key::::from_secret_bytes(secret).map(R::alloc_key) } + #[cfg(feature = "chacha")] KeyAlg::Chacha20(Chacha20Types::XC20P) => { Chacha20Key::::from_secret_bytes(secret).map(R::alloc_key) } + #[cfg(feature = "ed25519")] KeyAlg::Ed25519 => Ed25519KeyPair::from_secret_bytes(secret).map(R::alloc_key), + #[cfg(feature = "ed25519")] KeyAlg::X25519 => X25519KeyPair::from_secret_bytes(secret).map(R::alloc_key), + #[cfg(feature = "k256")] KeyAlg::EcCurve(EcCurves::Secp256k1) => { K256KeyPair::from_secret_bytes(secret).map(R::alloc_key) } + #[cfg(feature = "p256")] KeyAlg::EcCurve(EcCurves::Secp256r1) => { P256KeyPair::from_secret_bytes(secret).map(R::alloc_key) } @@ -244,12 +302,13 @@ fn from_secret_bytes_any(alg: KeyAlg, secret: &[u8]) -> Result { return Err(err_msg!( Unsupported, - "Unsupported algorithm for key import" + "Unsupported algorithm for secret key import" )) } } } +#[cfg(any(feature = "aes", feature = "chacha"))] #[inline] fn from_key_exchange_any(alg: KeyAlg, secret: &Sk, public: &Pk) -> Result where @@ -258,15 +317,19 @@ where Pk: ?Sized, { match alg { + #[cfg(feature = "aes")] KeyAlg::Aes(AesTypes::A128GCM) => { AesGcmKey::::from_key_exchange(secret, public).map(R::alloc_key) } + #[cfg(feature = "aes")] KeyAlg::Aes(AesTypes::A256GCM) => { AesGcmKey::::from_key_exchange(secret, public).map(R::alloc_key) } + #[cfg(feature = "chacha")] KeyAlg::Chacha20(Chacha20Types::C20P) => { Chacha20Key::::from_key_exchange(secret, public).map(R::alloc_key) } + #[cfg(feature = "chacha")] KeyAlg::Chacha20(Chacha20Types::XC20P) => { Chacha20Key::::from_key_exchange(secret, public).map(R::alloc_key) } @@ -274,27 +337,45 @@ where _ => { return Err(err_msg!( Unsupported, - "Unsupported algorithm for key import" - )) + "Unsupported algorithm for key exchange" + )); } } } +#[cfg(not(any(feature = "aes", feature = "chacha")))] +#[inline] +fn from_key_exchange_any( + _alg: KeyAlg, + _secret: &Sk, + _public: &Pk, +) -> Result { + return Err(err_msg!( + Unsupported, + "Unsupported algorithm for key exchange" + )); +} + +#[cfg(any(feature = "aes", feature = "chacha"))] #[inline] fn from_key_derivation_any( alg: KeyAlg, derive: impl KeyDerivation, ) -> Result { match alg { + #[cfg(feature = "aes")] KeyAlg::Aes(AesTypes::A128GCM) => { AesGcmKey::::from_key_derivation(derive).map(R::alloc_key) } + #[cfg(feature = "aes")] KeyAlg::Aes(AesTypes::A256GCM) => { AesGcmKey::::from_key_derivation(derive).map(R::alloc_key) } + #[cfg(feature = "chacha")] KeyAlg::Chacha20(Chacha20Types::C20P) => { Chacha20Key::::from_key_derivation(derive).map(R::alloc_key) } + #[cfg(feature = "chacha")] KeyAlg::Chacha20(Chacha20Types::XC20P) => { Chacha20Key::::from_key_derivation(derive).map(R::alloc_key) } @@ -302,21 +383,35 @@ fn from_key_derivation_any( _ => { return Err(err_msg!( Unsupported, - "Unsupported algorithm for key import" - )) + "Unsupported algorithm for key derivation" + )); } } } +#[cfg(not(any(feature = "aes", feature = "chacha")))] +fn from_key_derivation_any( + _alg: KeyAlg, + _derive: impl KeyDerivation, +) -> Result { + return Err(err_msg!( + Unsupported, + "Unsupported algorithm for key derivation" + )); +} + #[inline] fn convert_key_any(key: &AnyKey, alg: KeyAlg) -> Result { match (key.algorithm(), alg) { + #[cfg(feature = "bls")] (KeyAlg::Bls12_381(BlsCurves::G1G2), KeyAlg::Bls12_381(BlsCurves::G1)) => Ok(R::alloc_key( BlsKeyPair::::from(key.assume::>()), )), + #[cfg(feature = "bls")] (KeyAlg::Bls12_381(BlsCurves::G1G2), KeyAlg::Bls12_381(BlsCurves::G2)) => Ok(R::alloc_key( BlsKeyPair::::from(key.assume::>()), )), + #[cfg(feature = "ed25519")] (KeyAlg::Ed25519, KeyAlg::X25519) => Ok(>::try_from( key.assume::(), ) @@ -346,18 +441,25 @@ impl FromJwk for Arc { #[inline] fn from_jwk_any(jwk: JwkParts<'_>) -> Result { match (jwk.kty, jwk.crv.as_ref()) { + #[cfg(feature = "ed25519")] ("OKP", c) if c == ed25519::JWK_CURVE => { Ed25519KeyPair::from_jwk_parts(jwk).map(R::alloc_key) } + #[cfg(feature = "ed25519")] ("OKP", c) if c == x25519::JWK_CURVE => { X25519KeyPair::from_jwk_parts(jwk).map(R::alloc_key) } + #[cfg(feature = "bls")] ("EC", c) if c == G1::JWK_CURVE => BlsKeyPair::::from_jwk_parts(jwk).map(R::alloc_key), + #[cfg(feature = "bls")] ("EC", c) if c == G2::JWK_CURVE => BlsKeyPair::::from_jwk_parts(jwk).map(R::alloc_key), + #[cfg(feature = "bls")] ("EC", c) if c == G1G2::JWK_CURVE => { BlsKeyPair::::from_jwk_parts(jwk).map(R::alloc_key) } + #[cfg(feature = "k256")] ("EC", c) if c == k256::JWK_CURVE => K256KeyPair::from_jwk_parts(jwk).map(R::alloc_key), + #[cfg(feature = "p256")] ("EC", c) if c == p256::JWK_CURVE => P256KeyPair::from_jwk_parts(jwk).map(R::alloc_key), // FIXME implement symmetric keys? _ => Err(err_msg!(Unsupported, "Unsupported JWK for key import")), @@ -367,6 +469,7 @@ fn from_jwk_any(jwk: JwkParts<'_>) -> Result { macro_rules! match_key_alg { ($slf:expr, $ty:ty, $($kty:ident),+ $(,$errmsg:literal)?) => {{ fn matcher(key: &AnyKey) -> Result<$ty, Error> { + #[allow(unused_variables)] let alg = key.algorithm(); match_key_alg!(@ $($kty)+ ; key, alg); return Err(err_msg!(Unsupported $(,$errmsg)?)) @@ -375,51 +478,60 @@ macro_rules! match_key_alg { }}; (@ ; $key:ident, $alg:ident) => {()}; (@ Aes $($rest:ident)*; $key:ident, $alg:ident) => {{ - if $alg == KeyAlg::Aes(AesTypes::A128GCM) { + #[cfg(feature = "aes")] if $alg == KeyAlg::Aes(AesTypes::A128GCM) { return Ok($key.assume::>()); } - if $alg == KeyAlg::Aes(AesTypes::A256GCM) { + #[cfg(feature = "aes")] if $alg == KeyAlg::Aes(AesTypes::A256GCM) { return Ok($key.assume::>()); } match_key_alg!(@ $($rest)*; $key, $alg) }}; (@ Bls $($rest:ident)*; $key:ident, $alg:ident) => {{ - if $alg == KeyAlg::Bls12_381(BlsCurves::G1) { + #[cfg(feature = "bls")] if $alg == KeyAlg::Bls12_381(BlsCurves::G1) { return Ok($key.assume::>()); } - if $alg == KeyAlg::Bls12_381(BlsCurves::G2) { + #[cfg(feature = "bls")] if $alg == KeyAlg::Bls12_381(BlsCurves::G2) { return Ok($key.assume::>()); } - if $alg == KeyAlg::Bls12_381(BlsCurves::G1G2) { + #[cfg(feature = "bls")] if $alg == KeyAlg::Bls12_381(BlsCurves::G1G2) { return Ok($key.assume::>()); } match_key_alg!(@ $($rest)*; $key, $alg) }}; (@ Chacha $($rest:ident)*; $key:ident, $alg:ident) => {{ + #[cfg(feature = "chacha")] if $alg == KeyAlg::Chacha20(Chacha20Types::C20P) { return Ok($key.assume::>()); } + #[cfg(feature = "chacha")] + if $alg == KeyAlg::Chacha20(Chacha20Types::XC20P) { + return Ok($key.assume::>()); + } match_key_alg!(@ $($rest)*; $key, $alg) }}; (@ Ed25519 $($rest:ident)*; $key:ident, $alg:ident) => {{ + #[cfg(feature = "ed25519")] if $alg == KeyAlg::Ed25519 { return Ok($key.assume::()) } match_key_alg!(@ $($rest)*; $key, $alg) }}; (@ X25519 $($rest:ident)*; $key:ident, $alg:ident) => {{ + #[cfg(feature = "ed25519")] if $alg == KeyAlg::X25519 { return Ok($key.assume::()) } match_key_alg!(@ $($rest)*; $key, $alg) }}; (@ K256 $($rest:ident)*; $key:ident, $alg:ident) => {{ + #[cfg(feature = "k256")] if $alg == KeyAlg::EcCurve(EcCurves::Secp256k1) { return Ok($key.assume::()) } match_key_alg!(@ $($rest)*; $key, $alg) }}; (@ P256 $($rest:ident)*; $key:ident, $alg:ident) => {{ + #[cfg(feature = "p256")] if $alg == KeyAlg::EcCurve(EcCurves::Secp256r1) { return Ok($key.assume::()) } @@ -467,17 +579,23 @@ impl KeyExchange for AnyKey { return Err(err_msg!(Unsupported, "Unsupported key exchange")); } match self.algorithm() { + #[cfg(feature = "ed25519")] KeyAlg::X25519 => Ok(self .assume::() .write_key_exchange(other.assume::(), out)?), + #[cfg(feature = "k256")] KeyAlg::EcCurve(EcCurves::Secp256k1) => Ok(self .assume::() .write_key_exchange(other.assume::(), out)?), + #[cfg(feature = "p256")] KeyAlg::EcCurve(EcCurves::Secp256r1) => Ok(self .assume::() .write_key_exchange(other.assume::(), out)?), #[allow(unreachable_patterns)] - _ => return Err(err_msg!(Unsupported, "Unsupported key exchange")), + _ => { + let _ = out; + return Err(err_msg!(Unsupported, "Unsupported key exchange")); + } } } } @@ -610,10 +728,12 @@ impl AnyKeyAlg for K { #[cfg(test)] mod tests { + #[allow(unused_imports)] use super::*; // FIXME - add a custom key type for testing, to allow feature independence + #[cfg(feature = "ed25519")] #[test] fn ed25519_as_any() { let key = Box::::generate(KeyAlg::Ed25519).unwrap(); @@ -622,6 +742,7 @@ mod tests { let _ = key.to_jwk_public(None).unwrap(); } + #[cfg(feature = "aes")] #[test] fn key_exchange_any() { let alice = Box::::generate(KeyAlg::X25519).unwrap(); @@ -635,6 +756,7 @@ mod tests { .unwrap(); } + #[cfg(feature = "chacha")] #[test] fn key_encrypt_any() { use crate::buffer::SecretBytes; diff --git a/askar-crypto/src/alg/mod.rs b/askar-crypto/src/alg/mod.rs index 950f8f26..fd3b5123 100644 --- a/askar-crypto/src/alg/mod.rs +++ b/askar-crypto/src/alg/mod.rs @@ -18,17 +18,24 @@ mod any; #[cfg_attr(docsrs, doc(cfg(feature = "any_key")))] pub use any::{AnyKey, AnyKeyCreate}; +#[cfg(feature = "aes")] pub mod aesgcm; +#[cfg(feature = "bls")] pub mod bls; +#[cfg(feature = "chacha")] pub mod chacha20; +#[cfg(feature = "ed25519")] pub mod ed25519; +#[cfg(feature = "ed25519")] pub mod x25519; +#[cfg(feature = "k256")] pub mod k256; +#[cfg(feature = "p256")] pub mod p256; /// Supported key algorithms diff --git a/askar-crypto/src/buffer/secret.rs b/askar-crypto/src/buffer/secret.rs index 257e1d0d..d4a8f63a 100644 --- a/askar-crypto/src/buffer/secret.rs +++ b/askar-crypto/src/buffer/secret.rs @@ -110,6 +110,7 @@ impl SecretBytes { v } + #[cfg(feature = "crypto_box")] pub(crate) fn as_vec_mut(&mut self) -> &mut Vec { &mut self.0 } diff --git a/askar-crypto/src/encrypt/crypto_box.rs b/askar-crypto/src/encrypt/crypto_box.rs index 22e66488..ee70678d 100644 --- a/askar-crypto/src/encrypt/crypto_box.rs +++ b/askar-crypto/src/encrypt/crypto_box.rs @@ -6,7 +6,7 @@ use crate::{ }; use aead::AeadInPlace; use blake2::{digest::Update, digest::VariableOutput, VarBlake2b}; -use crypto_box::{self as cbox, SalsaBox}; +use crypto_box_rs::{self as cbox, SalsaBox}; use crate::{ alg::x25519::X25519KeyPair, diff --git a/askar-crypto/src/encrypt/mod.rs b/askar-crypto/src/encrypt/mod.rs index 2f046f8b..e58e24f5 100644 --- a/askar-crypto/src/encrypt/mod.rs +++ b/askar-crypto/src/encrypt/mod.rs @@ -7,8 +7,8 @@ use crate::{ random::fill_random, }; -#[cfg(feature = "alloc")] // FIXME - support non-alloc? -#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] +#[cfg(feature = "crypto_box")] // FIXME - support non-alloc? +#[cfg_attr(docsrs, doc(cfg(feature = "crypto_box")))] pub mod crypto_box; /// Trait for key types which perform AEAD encryption diff --git a/askar-crypto/src/kdf/ecdh_1pu.rs b/askar-crypto/src/kdf/ecdh_1pu.rs index 04111290..ddf45236 100644 --- a/askar-crypto/src/kdf/ecdh_1pu.rs +++ b/askar-crypto/src/kdf/ecdh_1pu.rs @@ -72,8 +72,10 @@ impl KeyDerivation for Ecdh1PU<'_, Key> { #[cfg(test)] mod tests { + #[allow(unused_imports)] use super::*; + #[cfg(feature = "p256")] #[test] // from RFC: https://tools.ietf.org/html/draft-madden-jose-ecdh-1pu-03#appendix-A fn expected_1pu_direct_output() { diff --git a/askar-crypto/src/kdf/ecdh_es.rs b/askar-crypto/src/kdf/ecdh_es.rs index d632c8e6..3d269f67 100644 --- a/askar-crypto/src/kdf/ecdh_es.rs +++ b/askar-crypto/src/kdf/ecdh_es.rs @@ -71,8 +71,10 @@ impl KeyDerivation for EcdhEs<'_, Key> { #[cfg(test)] mod tests { + #[allow(unused_imports)] use super::*; + #[cfg(feature = "ed25519")] #[test] // based on RFC sample keys // https://tools.ietf.org/html/rfc8037#appendix-A.6 diff --git a/askar-crypto/src/random.rs b/askar-crypto/src/random.rs index f311e753..6287bdac 100644 --- a/askar-crypto/src/random.rs +++ b/askar-crypto/src/random.rs @@ -1,6 +1,8 @@ //! Support for random number generation +#[cfg(feature = "chacha")] use aead::generic_array::{typenum::Unsigned, GenericArray}; +#[cfg(feature = "chacha")] use chacha20::{ cipher::{NewStreamCipher, SyncStreamCipher}, ChaCha20, @@ -9,8 +11,10 @@ use rand::{CryptoRng, RngCore}; #[cfg(feature = "alloc")] use crate::buffer::SecretBytes; +#[cfg(feature = "chacha")] use crate::error::Error; +#[cfg(feature = "chacha")] /// The expected length of a seed for `fill_random_deterministic` pub const DETERMINISTIC_SEED_LENGTH: usize = ::KeySize::USIZE; @@ -34,6 +38,7 @@ pub fn fill_random(value: &mut [u8]) { with_rng(|rng| rng.fill_bytes(value)); } +#[cfg(feature = "chacha")] /// Written to be compatible with randombytes_deterministic in libsodium, /// used to generate a deterministic symmetric encryption key pub fn fill_random_deterministic(seed: &[u8], output: &mut [u8]) -> Result<(), Error> { From 4235434db20c428104f78e583f14b70ee6b5b9ee Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Tue, 4 May 2021 16:49:13 -0700 Subject: [PATCH 099/116] make argon2 feature optional Signed-off-by: Andrew Whitehead --- askar-crypto/Cargo.toml | 4 ++-- askar-crypto/src/kdf/mod.rs | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/askar-crypto/Cargo.toml b/askar-crypto/Cargo.toml index e6544f2b..296bf869 100644 --- a/askar-crypto/Cargo.toml +++ b/askar-crypto/Cargo.toml @@ -11,7 +11,7 @@ categories = ["cryptography", "database"] keywords = ["hyperledger", "aries", "ssi", "verifiable", "credentials"] [package.metadata.docs.rs] -features = ["std"] +features = ["argon2", "std"] rustdoc-args = ["--cfg", "docsrs"] [features] @@ -44,7 +44,7 @@ harness = false [dependencies] aead = "0.3" aes-gcm = { version = "0.8", default-features = false, features = ["aes"], optional = true } -argon2 = { version = "0.1", default-features = false, features = ["password-hash"] } +argon2 = { version = "0.1", default-features = false, features = ["password-hash"], optional = true } base64 = { version = "0.13", default-features = false } blake2 = { version = "0.9", default-features = false } bls12_381 = { version = "0.4", default-features = false, features = ["groups"], optional = true } diff --git a/askar-crypto/src/kdf/mod.rs b/askar-crypto/src/kdf/mod.rs index 31cd8488..6450a288 100644 --- a/askar-crypto/src/kdf/mod.rs +++ b/askar-crypto/src/kdf/mod.rs @@ -4,6 +4,8 @@ use crate::buffer::SecretBytes; use crate::{buffer::WriteBuffer, error::Error}; +#[cfg(feature = "argon2")] +#[cfg_attr(docsrs, doc(cfg(feature = "argon2")))] pub mod argon2; pub mod concat; From 63d9c621cae57a041d8751b1650408592c35ed1b Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Tue, 4 May 2021 16:49:38 -0700 Subject: [PATCH 100/116] show feature flags for alg modules Signed-off-by: Andrew Whitehead --- Cargo.toml | 2 +- askar-crypto/src/alg/mod.rs | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 7a77786a..e24fe1bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,7 @@ pg_test = ["postgres"] hex-literal = "0.3" [dependencies] -askar-crypto = { version = "0.2", path = "./askar-crypto", features = ["std"] } +askar-crypto = { version = "0.2", path = "./askar-crypto", features = ["argon2", "std"] } async-mutex = "1.4" async-stream = "0.3" bs58 = "0.4" diff --git a/askar-crypto/src/alg/mod.rs b/askar-crypto/src/alg/mod.rs index fd3b5123..241d2492 100644 --- a/askar-crypto/src/alg/mod.rs +++ b/askar-crypto/src/alg/mod.rs @@ -19,23 +19,30 @@ mod any; pub use any::{AnyKey, AnyKeyCreate}; #[cfg(feature = "aes")] +#[cfg_attr(docsrs, doc(cfg(feature = "aes")))] pub mod aesgcm; #[cfg(feature = "bls")] +#[cfg_attr(docsrs, doc(cfg(feature = "bls")))] pub mod bls; #[cfg(feature = "chacha")] +#[cfg_attr(docsrs, doc(cfg(feature = "chacha")))] pub mod chacha20; #[cfg(feature = "ed25519")] +#[cfg_attr(docsrs, doc(cfg(feature = "ed25519")))] pub mod ed25519; #[cfg(feature = "ed25519")] +#[cfg_attr(docsrs, doc(cfg(feature = "ed25519")))] pub mod x25519; #[cfg(feature = "k256")] +#[cfg_attr(docsrs, doc(cfg(feature = "k256")))] pub mod k256; #[cfg(feature = "p256")] +#[cfg_attr(docsrs, doc(cfg(feature = "p256")))] pub mod p256; /// Supported key algorithms From 46223c6819cde0d27f35f86e33213ac530c69f9c Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Wed, 5 May 2021 17:42:14 -0700 Subject: [PATCH 101/116] move key generation from a seed to KeyGen trait; add to FFI and python wrapper Signed-off-by: Andrew Whitehead --- askar-crypto/Cargo.toml | 6 ++-- askar-crypto/src/alg/aesgcm.rs | 28 +++++++++------ askar-crypto/src/alg/any.rs | 46 +++++++++++++++++++++++-- askar-crypto/src/alg/bls.rs | 24 +++++++++---- askar-crypto/src/alg/chacha20.rs | 24 ++++++++----- askar-crypto/src/random.rs | 23 ++++++++++--- askar-crypto/src/repr.rs | 36 +++++++++++++++++++ src/ffi/key.rs | 17 +++++++++ src/kms/key.rs | 10 ++++++ src/kms/mod.rs | 1 + src/protect/store_key.rs | 2 +- wrappers/python/aries_askar/bindings.py | 16 +++++++++ wrappers/python/aries_askar/key.py | 6 ++++ wrappers/python/demo/test.py | 2 +- 14 files changed, 204 insertions(+), 37 deletions(-) diff --git a/askar-crypto/Cargo.toml b/askar-crypto/Cargo.toml index 296bf869..25c1a589 100644 --- a/askar-crypto/Cargo.toml +++ b/askar-crypto/Cargo.toml @@ -22,7 +22,7 @@ all_keys = ["aes", "bls", "chacha", "ec_curves", "ed25519"] any_key = ["alloc"] aes = ["aes-gcm"] bls = ["bls12_381", "hkdf"] -chacha = ["chacha20", "chacha20poly1305"] +chacha = ["chacha20poly1305"] crypto_box = ["alloc", "crypto_box_rs", "ed25519"] ec_curves = ["k256", "p256"] ed25519 = ["curve25519-dalek", "ed25519-dalek", "x25519-dalek"] @@ -48,10 +48,10 @@ argon2 = { version = "0.1", default-features = false, features = ["password-hash base64 = { version = "0.13", default-features = false } blake2 = { version = "0.9", default-features = false } bls12_381 = { version = "0.4", default-features = false, features = ["groups"], optional = true } -chacha20 = { version = "0.6", optional = true } # should match chacha20poly1305 +chacha20 = { version = "0.6" } # should match chacha20poly1305 chacha20poly1305 = { version = "0.7", default-features = false, features = ["chacha20", "xchacha20poly1305"], optional = true } crypto_box_rs = { package = "crypto_box", version = "0.5", default-features = false, features = ["u64_backend"], optional = true } -curve25519-dalek = { version = "3.0", default-features = false, features = ["u64_backend"], optional = true } +curve25519-dalek = { version = "3.1", default-features = false, features = ["u64_backend"], optional = true } ed25519-dalek = { version = "1.0", default-features = false, features = ["u64_backend"], optional = true } digest = "0.9" group = "0.9" diff --git a/askar-crypto/src/alg/aesgcm.rs b/askar-crypto/src/alg/aesgcm.rs index 10ee7bee..7846197a 100644 --- a/askar-crypto/src/alg/aesgcm.rs +++ b/askar-crypto/src/alg/aesgcm.rs @@ -8,8 +8,6 @@ use serde::{Deserialize, Serialize}; use zeroize::Zeroize; use super::{AesTypes, HasKeyAlg, KeyAlg}; -#[cfg(feature = "chacha")] -use crate::random::fill_random_deterministic; use crate::{ buffer::{ArrayKey, ResizeBuffer, Writer}, encrypt::{KeyAeadInPlace, KeyAeadMeta, KeyAeadParams}, @@ -17,7 +15,8 @@ use crate::{ generic_array::{typenum::Unsigned, GenericArray}, jwk::{JwkEncoder, ToJwk}, kdf::{FromKeyDerivation, FromKeyExchange, KeyDerivation, KeyExchange}, - repr::{KeyGen, KeyMeta, KeySecretBytes}, + random::fill_random_deterministic, + repr::{KeyGen, KeyMeta, KeySecretBytes, Seed, SeedMethod}, }; /// The 'kty' value of a symmetric key JWK @@ -81,14 +80,6 @@ impl AesGcmKey { pub const NONCE_LENGTH: usize = NonceSize::::USIZE; /// The length of the AEAD encryption tag pub const TAG_LENGTH: usize = TagSize::::USIZE; - - #[cfg(feature = "chacha")] - /// Construct a new deterministic AES key from a seed value - pub fn from_seed(seed: &[u8]) -> Result { - Ok(Self(KeyType::::try_new_with(|arr| { - fill_random_deterministic(seed, arr) - })?)) - } } impl Clone for AesGcmKey { @@ -128,6 +119,21 @@ impl KeyGen for AesGcmKey { fn generate() -> Result { Ok(AesGcmKey(KeyType::::random())) } + + fn from_seed(seed: Seed<'_>) -> Result + where + Self: Sized, + { + match seed { + Seed::Bytes(ikm, SeedMethod::Preferred) | Seed::Bytes(ikm, SeedMethod::RandomDet) => { + Ok(Self(KeyType::::try_new_with(|arr| { + fill_random_deterministic(ikm, arr) + })?)) + } + #[allow(unreachable_patterns)] + _ => Err(err_msg!(Unsupported)), + } + } } impl KeySecretBytes for AesGcmKey { diff --git a/askar-crypto/src/alg/any.rs b/askar-crypto/src/alg/any.rs index f3dc0248..fc983c90 100644 --- a/askar-crypto/src/alg/any.rs +++ b/askar-crypto/src/alg/any.rs @@ -42,7 +42,7 @@ use crate::{ error::Error, jwk::{FromJwk, JwkEncoder, JwkParts, ToJwk}, kdf::{KeyDerivation, KeyExchange}, - repr::{KeyGen, KeyPublicBytes, KeySecretBytes, ToPublicBytes, ToSecretBytes}, + repr::{KeyGen, KeyPublicBytes, KeySecretBytes, Seed, ToPublicBytes, ToSecretBytes}, sign::{KeySigVerify, KeySign, SignatureType}, }; @@ -86,9 +86,12 @@ impl std::panic::RefUnwindSafe for AnyKey {} /// Create `AnyKey` instances from various sources pub trait AnyKeyCreate: Sized { - /// Generate a new key for the given key algorithm. + /// Generate a new random key for the given key algorithm. fn generate(alg: KeyAlg) -> Result; + /// Generate a new deterministic key for the given key algorithm. + fn from_seed(alg: KeyAlg, seed: Seed<'_>) -> Result; + /// Load a public key from its byte representation fn from_public_bytes(alg: KeyAlg, public: &[u8]) -> Result; @@ -116,6 +119,10 @@ impl AnyKeyCreate for Box { generate_any(alg) } + fn from_seed(alg: KeyAlg, seed: Seed<'_>) -> Result { + from_seed_any(alg, seed) + } + fn from_public_bytes(alg: KeyAlg, public: &[u8]) -> Result { from_public_bytes_any(alg, public) } @@ -151,6 +158,10 @@ impl AnyKeyCreate for Arc { generate_any(alg) } + fn from_seed(alg: KeyAlg, seed: Seed<'_>) -> Result { + from_seed_any(alg, seed) + } + fn from_public_bytes(alg: KeyAlg, public: &[u8]) -> Result { from_public_bytes_any(alg, public) } @@ -218,6 +229,37 @@ fn generate_any(alg: KeyAlg) -> Result { } } +#[inline] +fn from_seed_any(alg: KeyAlg, seed: Seed<'_>) -> Result { + match alg { + #[cfg(feature = "aes")] + KeyAlg::Aes(AesTypes::A128GCM) => AesGcmKey::::from_seed(seed).map(R::alloc_key), + #[cfg(feature = "aes")] + KeyAlg::Aes(AesTypes::A256GCM) => AesGcmKey::::from_seed(seed).map(R::alloc_key), + #[cfg(feature = "bls")] + KeyAlg::Bls12_381(BlsCurves::G1) => BlsKeyPair::::from_seed(seed).map(R::alloc_key), + #[cfg(feature = "bls")] + KeyAlg::Bls12_381(BlsCurves::G2) => BlsKeyPair::::from_seed(seed).map(R::alloc_key), + #[cfg(feature = "bls")] + KeyAlg::Bls12_381(BlsCurves::G1G2) => BlsKeyPair::::from_seed(seed).map(R::alloc_key), + #[cfg(feature = "chacha")] + KeyAlg::Chacha20(Chacha20Types::C20P) => { + Chacha20Key::::from_seed(seed).map(R::alloc_key) + } + #[cfg(feature = "chacha")] + KeyAlg::Chacha20(Chacha20Types::XC20P) => { + Chacha20Key::::from_seed(seed).map(R::alloc_key) + } + #[allow(unreachable_patterns)] + _ => { + return Err(err_msg!( + Unsupported, + "Unsupported algorithm for public key import" + )) + } + } +} + #[inline] fn from_public_bytes_any(alg: KeyAlg, public: &[u8]) -> Result { match alg { diff --git a/askar-crypto/src/alg/bls.rs b/askar-crypto/src/alg/bls.rs index 17971e3f..dd529fa8 100644 --- a/askar-crypto/src/alg/bls.rs +++ b/askar-crypto/src/alg/bls.rs @@ -23,7 +23,7 @@ use crate::{ error::Error, jwk::{FromJwk, JwkEncoder, JwkParts, ToJwk}, random::fill_random, - repr::{KeyGen, KeyMeta, KeyPublicBytes, KeySecretBytes, KeypairMeta}, + repr::{KeyGen, KeyMeta, KeyPublicBytes, KeySecretBytes, KeypairMeta, Seed, SeedMethod}, }; /// The 'kty' value of a BLS key JWK @@ -37,11 +37,6 @@ pub struct BlsKeyPair { } impl BlsKeyPair { - /// Create a new deterministic BLS keypair according to the KeyGen algorithm - pub fn from_seed(ikm: &[u8]) -> Result { - Ok(Self::from_secret_key(BlsSecretKey::from_seed(ikm)?)) - } - #[inline] fn from_secret_key(sk: BlsSecretKey) -> Self { let public = Pk::from_secret_scalar(&sk.0); @@ -95,6 +90,20 @@ impl KeyGen for BlsKeyPair { let secret = BlsSecretKey::generate()?; Ok(Self::from_secret_key(secret)) } + + fn from_seed(seed: Seed<'_>) -> Result + where + Self: Sized, + { + match seed { + Seed::Bytes(ikm, SeedMethod::Preferred) + | Seed::Bytes(ikm, SeedMethod::BlsKeyGenDraft4) => { + Ok(Self::from_secret_key(BlsSecretKey::from_seed(ikm)?)) + } + #[allow(unreachable_patterns)] + _ => Err(err_msg!(Unsupported, "Unsupported seed method for BLS key")), + } + } } impl KeySecretBytes for BlsKeyPair { @@ -189,6 +198,9 @@ impl BlsSecretKey { // bls-signatures draft 4 version (incompatible with earlier) pub fn from_seed(ikm: &[u8]) -> Result { const SALT: &[u8] = b"BLS-SIG-KEYGEN-SALT-"; + if ikm.len() < 32 { + return Err(err_msg!(Usage, "Insufficient length for seed")); + } let mut salt = Sha256::digest(SALT); Ok(Self(loop { diff --git a/askar-crypto/src/alg/chacha20.rs b/askar-crypto/src/alg/chacha20.rs index 97e1955b..997d25e5 100644 --- a/askar-crypto/src/alg/chacha20.rs +++ b/askar-crypto/src/alg/chacha20.rs @@ -16,7 +16,7 @@ use crate::{ jwk::{JwkEncoder, ToJwk}, kdf::{FromKeyDerivation, FromKeyExchange, KeyDerivation, KeyExchange}, random::fill_random_deterministic, - repr::{KeyGen, KeyMeta, KeySecretBytes}, + repr::{KeyGen, KeyMeta, KeySecretBytes, Seed, SeedMethod}, }; /// The 'kty' value of a symmetric key JWK @@ -80,13 +80,6 @@ impl Chacha20Key { pub const NONCE_LENGTH: usize = NonceSize::::USIZE; /// The length of the AEAD encryption tag pub const TAG_LENGTH: usize = TagSize::::USIZE; - - /// Construct a new deterministic ChaCha20 key from a seed value - pub fn from_seed(seed: &[u8]) -> Result { - Ok(Self(KeyType::::try_new_with(|arr| { - fill_random_deterministic(seed, arr) - })?)) - } } impl Clone for Chacha20Key { @@ -126,6 +119,21 @@ impl KeyGen for Chacha20Key { fn generate() -> Result { Ok(Chacha20Key(KeyType::::random())) } + + fn from_seed(seed: Seed<'_>) -> Result + where + Self: Sized, + { + match seed { + Seed::Bytes(ikm, SeedMethod::Preferred) | Seed::Bytes(ikm, SeedMethod::RandomDet) => { + Ok(Self(KeyType::::try_new_with(|arr| { + fill_random_deterministic(ikm, arr) + })?)) + } + #[allow(unreachable_patterns)] + _ => Err(err_msg!(Unsupported)), + } + } } impl KeySecretBytes for Chacha20Key { diff --git a/askar-crypto/src/random.rs b/askar-crypto/src/random.rs index 6287bdac..efa68b8b 100644 --- a/askar-crypto/src/random.rs +++ b/askar-crypto/src/random.rs @@ -1,8 +1,6 @@ //! Support for random number generation -#[cfg(feature = "chacha")] use aead::generic_array::{typenum::Unsigned, GenericArray}; -#[cfg(feature = "chacha")] use chacha20::{ cipher::{NewStreamCipher, SyncStreamCipher}, ChaCha20, @@ -11,10 +9,8 @@ use rand::{CryptoRng, RngCore}; #[cfg(feature = "alloc")] use crate::buffer::SecretBytes; -#[cfg(feature = "chacha")] use crate::error::Error; -#[cfg(feature = "chacha")] /// The expected length of a seed for `fill_random_deterministic` pub const DETERMINISTIC_SEED_LENGTH: usize = ::KeySize::USIZE; @@ -38,7 +34,6 @@ pub fn fill_random(value: &mut [u8]) { with_rng(|rng| rng.fill_bytes(value)); } -#[cfg(feature = "chacha")] /// Written to be compatible with randombytes_deterministic in libsodium, /// used to generate a deterministic symmetric encryption key pub fn fill_random_deterministic(seed: &[u8], output: &mut [u8]) -> Result<(), Error> { @@ -60,3 +55,21 @@ pub fn fill_random_deterministic(seed: &[u8], output: &mut [u8]) -> Result<(), E pub fn random_secret(len: usize) -> SecretBytes { SecretBytes::new_with(len, fill_random) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::buffer::HexRepr; + use std::string::ToString; + + #[test] + fn fill_random_det_expected() { + let seed = b"testseed000000000000000000000001"; + let mut output = [0u8; 32]; + fill_random_deterministic(seed, &mut output).unwrap(); + assert_eq!( + HexRepr(output).to_string(), + "b1923a011cd1adbe89552db9862470c29512a8f51d184dfd778bfe7f845390d1" + ); + } +} diff --git a/askar-crypto/src/repr.rs b/askar-crypto/src/repr.rs index 8c800ab5..ead44287 100644 --- a/askar-crypto/src/repr.rs +++ b/askar-crypto/src/repr.rs @@ -4,12 +4,48 @@ use crate::buffer::SecretBytes; use crate::{buffer::WriteBuffer, error::Error, generic_array::ArrayLength}; +/// A seed used in key generation +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Seed<'d> { + /// A seed byte string with a selected generation method + Bytes(&'d [u8], SeedMethod), +} + +impl<'d> From<&'d [u8]> for Seed<'d> { + fn from(seed: &'d [u8]) -> Self { + Self::Bytes(seed, SeedMethod::Preferred) + } +} + +/// Supported deterministic key methods +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum SeedMethod { + /// Use the preferred method for the current key algorithm + Preferred, + /// Generate a BLS key according to bls-signatures-draft-04 + BlsKeyGenDraft4, + /// Random bytes compatible with libsodium's randombytes_buf_deterministic. + /// The seed must be 32 bytes in length + RandomDet, +} + /// Key generation operations pub trait KeyGen { /// Generate a new random key. fn generate() -> Result where Self: Sized; + + /// Generate a new deterministic key. + fn from_seed(_seed: Seed<'_>) -> Result + where + Self: Sized, + { + return Err(err_msg!( + Unsupported, + "Key generation from seed not supported" + )); + } } /// Convert between key instance and key secret bytes diff --git a/src/ffi/key.rs b/src/ffi/key.rs index f23db074..702548e0 100644 --- a/src/ffi/key.rs +++ b/src/ffi/key.rs @@ -32,6 +32,23 @@ pub extern "C" fn askar_key_generate( } } +#[no_mangle] +pub extern "C" fn askar_key_from_seed( + alg: FfiStr<'_>, + seed: ByteBuffer, + method: FfiStr<'_>, + out: *mut LocalKeyHandle, +) -> ErrorCode { + catch_err! { + trace!("Create key from seed: {}", alg.as_str()); + check_useful_c_ptr!(out); + let alg = KeyAlg::from_str(alg.as_str())?; + let key = LocalKey::from_seed(alg, seed.as_slice(), method.as_opt_str())?; + unsafe { *out = LocalKeyHandle::create(key) }; + Ok(ErrorCode::Success) + } +} + #[no_mangle] pub extern "C" fn askar_key_from_jwk(jwk: FfiStr<'_>, out: *mut LocalKeyHandle) -> ErrorCode { catch_err! { diff --git a/src/kms/key.rs b/src/kms/key.rs index 6884b079..9b594958 100644 --- a/src/kms/key.rs +++ b/src/kms/key.rs @@ -33,6 +33,16 @@ impl LocalKey { Ok(Self { inner, ephemeral }) } + /// Create a new deterministic key or keypair + pub fn from_seed(alg: KeyAlg, seed: &[u8], _method: Option<&str>) -> Result { + // FIXME - method is just a placeholder + let inner = Box::::from_seed(alg, seed.into())?; + Ok(Self { + inner, + ephemeral: false, + }) + } + /// Import a key or keypair from a JWK pub fn from_jwk(jwk: &str) -> Result { let inner = Box::::from_jwk(jwk)?; diff --git a/src/kms/mod.rs b/src/kms/mod.rs index 0629bece..099f07df 100644 --- a/src/kms/mod.rs +++ b/src/kms/mod.rs @@ -26,6 +26,7 @@ pub use self::key::{KeyAlg, LocalKey}; pub(crate) enum KmsCategory { /// A stored key or keypair CryptoKey, + // future options: Mnemonic, Entropy } impl KmsCategory { diff --git a/src/protect/store_key.rs b/src/protect/store_key.rs index fdbe6389..e210755a 100644 --- a/src/protect/store_key.rs +++ b/src/protect/store_key.rs @@ -22,7 +22,7 @@ type StoreKeyNonce = ArrayKey<::NonceSize>; /// Create a new raw (non-derived) store key pub fn generate_raw_store_key(seed: Option<&[u8]>) -> Result, Error> { let key = if let Some(seed) = seed { - StoreKey::from(StoreKeyType::from_seed(seed)?) + StoreKey::from(StoreKeyType::from_seed(seed.into())?) } else { StoreKey::from(StoreKeyType::generate()?) }; diff --git a/wrappers/python/aries_askar/bindings.py b/wrappers/python/aries_askar/bindings.py index f9ad314d..4926a716 100644 --- a/wrappers/python/aries_askar/bindings.py +++ b/wrappers/python/aries_askar/bindings.py @@ -887,6 +887,22 @@ def key_generate(alg: Union[str, KeyAlg], ephemeral: bool = False) -> LocalKeyHa return handle +def key_from_seed( + alg: Union[str, KeyAlg], seed: Union[str, bytes, ByteBuffer], method: str = None +) -> LocalKeyHandle: + handle = LocalKeyHandle() + if isinstance(alg, KeyAlg): + alg = alg.value + do_call( + "askar_key_from_seed", + encode_str(alg), + encode_bytes(seed), + encode_str(method), + byref(handle), + ) + return handle + + def key_from_public_bytes( alg: Union[str, KeyAlg], public: Union[bytes, ByteBuffer] ) -> LocalKeyHandle: diff --git a/wrappers/python/aries_askar/key.py b/wrappers/python/aries_askar/key.py index 6b9da568..3085e30d 100644 --- a/wrappers/python/aries_askar/key.py +++ b/wrappers/python/aries_askar/key.py @@ -18,6 +18,12 @@ def __init__(self, handle: bindings.LocalKeyHandle): def generate(cls, alg: Union[str, KeyAlg], *, ephemeral: bool = False) -> "Key": return cls(bindings.key_generate(alg, ephemeral)) + @classmethod + def from_seed( + cls, alg: Union[str, KeyAlg], seed: Union[str, bytes], *, method: str = None + ) -> "Key": + return cls(bindings.key_from_seed(alg, seed, method)) + @classmethod def from_secret_bytes(cls, alg: Union[str, KeyAlg], secret: bytes) -> "Key": return cls(bindings.key_from_secret_bytes(alg, secret)) diff --git a/wrappers/python/demo/test.py b/wrappers/python/demo/test.py index adc507ac..710ff81c 100644 --- a/wrappers/python/demo/test.py +++ b/wrappers/python/demo/test.py @@ -79,7 +79,7 @@ def keys_test(): derived = derive_key_ecdh_es("A256GCM", ephem, bob, "Alice", "Bob") log("Derived:", derived.get_jwk_thumbprint()) - key = Key.generate(KeyAlg.BLS12_381_G1G2) + key = Key.from_seed(KeyAlg.BLS12_381_G1G2, b"testseed000000000000000000000001") log("BLS key G1:", key.get_jwk_public(KeyAlg.BLS12_381_G1)) log("BLS key G2:", key.get_jwk_public(KeyAlg.BLS12_381_G2)) log("BLS key G1G2:", key.get_jwk_public()) From 2db659f6b2ab5f3d5208e2bdeff7cda44ecbc3e4 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Wed, 5 May 2021 18:39:01 -0700 Subject: [PATCH 102/116] return KeyAlg from Key.algorithm Signed-off-by: Andrew Whitehead --- wrappers/python/aries_askar/key.py | 5 +++-- wrappers/python/aries_askar/store.py | 8 +++----- wrappers/python/aries_askar/types.py | 11 ++++++++++- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/wrappers/python/aries_askar/key.py b/wrappers/python/aries_askar/key.py index 3085e30d..94bdbd07 100644 --- a/wrappers/python/aries_askar/key.py +++ b/wrappers/python/aries_askar/key.py @@ -38,8 +38,9 @@ def handle(self) -> bindings.LocalKeyHandle: return self._handle @property - def algorithm(self) -> str: - return bindings.key_get_algorithm(self._handle) + def algorithm(self) -> KeyAlg: + alg = bindings.key_get_algorithm(self._handle) + return KeyAlg.from_key_alg(alg) @property def ephemeral(self) -> "Key": diff --git a/wrappers/python/aries_askar/store.py b/wrappers/python/aries_askar/store.py index 884bfeff..a2dbf382 100644 --- a/wrappers/python/aries_askar/store.py +++ b/wrappers/python/aries_askar/store.py @@ -27,7 +27,6 @@ def __init__(self, lst: EntryListHandle, pos: int): """Initialize the EntryHandle.""" self._list = lst self._pos = pos - self._cache = dict() @cached_property def category(self) -> str: @@ -114,21 +113,20 @@ def __init__(self, lst: KeyEntryListHandle, pos: int): """Initialize the KeyEntryHandle.""" self._list = lst self._pos = pos - self._cache = dict() @cached_property def algorithm(self) -> str: - """Accessor for the entry algorithm.""" + """Accessor for the key entry algorithm.""" return self._list.get_algorithm(self._pos) @cached_property def name(self) -> str: - """Accessor for the entry name.""" + """Accessor for the key entry name.""" return self._list.get_name(self._pos) @cached_property def metadata(self) -> str: - """Accessor for the entry metadata.""" + """Accessor for the key entry metadata.""" return self._list.get_metadata(self._pos) @cached_property diff --git a/wrappers/python/aries_askar/types.py b/wrappers/python/aries_askar/types.py index bdfff7bd..37d97d43 100644 --- a/wrappers/python/aries_askar/types.py +++ b/wrappers/python/aries_askar/types.py @@ -1,7 +1,7 @@ import json as _json from enum import Enum -from typing import Mapping, Union +from typing import Mapping, Optional, Union def _make_binary(value: Union[str, bytes]) -> bytes: @@ -69,6 +69,15 @@ class KeyAlg(Enum): K256 = "k256" P256 = "p256" + @classmethod + def from_key_alg(cls, alg: str) -> Optional["KeyAlg"]: + """Get KeyAlg instance from the algorithm identifier.""" + for cmp_alg in KeyAlg: + if cmp_alg.value == alg: + return cmp_alg + + return None + class EntryOperation(Enum): INSERT = 0 From ad70cca01bb3a82aecaa5fd16b573a8e97eeee00 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Wed, 5 May 2021 18:39:17 -0700 Subject: [PATCH 103/116] adjust version for pre-release Signed-off-by: Andrew Whitehead --- Cargo.toml | 4 ++-- askar-crypto/Cargo.toml | 2 +- wrappers/python/aries_askar/version.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e24fe1bc..ada2c0d7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = ["askar-crypto"] [package] name = "aries-askar" -version = "0.2.0" +version = "0.2.0-pre.1" authors = ["Hyperledger Aries Contributors "] edition = "2018" description = "Hyperledger Aries Askar secure storage" @@ -38,7 +38,7 @@ pg_test = ["postgres"] hex-literal = "0.3" [dependencies] -askar-crypto = { version = "0.2", path = "./askar-crypto", features = ["argon2", "std"] } +askar-crypto = { version = "0.2.0-pre.1", path = "./askar-crypto", features = ["argon2", "std"] } async-mutex = "1.4" async-stream = "0.3" bs58 = "0.4" diff --git a/askar-crypto/Cargo.toml b/askar-crypto/Cargo.toml index 25c1a589..a1ae8c1d 100644 --- a/askar-crypto/Cargo.toml +++ b/askar-crypto/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "askar-crypto" -version = "0.2.0" +version = "0.2.0-pre.1" authors = ["Hyperledger Aries Contributors "] edition = "2018" description = "Hyperledger Aries Askar cryptography" diff --git a/wrappers/python/aries_askar/version.py b/wrappers/python/aries_askar/version.py index a310d3d3..66873115 100644 --- a/wrappers/python/aries_askar/version.py +++ b/wrappers/python/aries_askar/version.py @@ -1,3 +1,3 @@ """aries_askar library wrapper version.""" -__version__ = "0.2.0" +__version__ = "0.2.0-pre.1" From 2d07f8b2009eaa6c6472ad838546fb77a2872f3f Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Wed, 5 May 2021 19:24:28 -0700 Subject: [PATCH 104/116] check/test all packages Signed-off-by: Andrew Whitehead --- .github/workflows/build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e361442b..a9222103 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -39,6 +39,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: check + args: --workspace - name: Cargo fmt uses: actions-rs/cargo@v1 @@ -56,6 +57,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: test + args: --workspace build-manylinux: name: Build Library From bd740d209acd1a1e6eee2f0936d927646cb52b3a Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Wed, 5 May 2021 19:24:58 -0700 Subject: [PATCH 105/116] fix length handling in KDF Signed-off-by: Andrew Whitehead --- askar-crypto/src/kdf/concat.rs | 5 ++--- askar-crypto/src/kdf/ecdh_1pu.rs | 6 ++++-- askar-crypto/src/kdf/ecdh_es.rs | 6 ++++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/askar-crypto/src/kdf/concat.rs b/askar-crypto/src/kdf/concat.rs index a16c5f73..4a3b803b 100644 --- a/askar-crypto/src/kdf/concat.rs +++ b/askar-crypto/src/kdf/concat.rs @@ -4,7 +4,7 @@ use core::{fmt::Debug, marker::PhantomData}; use digest::Digest; -use crate::generic_array::GenericArray; +use crate::generic_array::{typenum::Unsigned, GenericArray}; use crate::{buffer::WriteBuffer, error::Error}; @@ -38,8 +38,7 @@ where mut output: &mut [u8], ) -> Result<(), Error> { let output_len = output.len(); - if output_len > u32::MAX as usize / 8 { - // output_len is used as SuppPubInfo later + if output_len > H::OutputSize::USIZE * (u32::MAX as usize) - 1 { return Err(err_msg!(Usage, "Exceeded max output size for concat KDF")); } let mut hasher = ConcatKDFHash::::new(); diff --git a/askar-crypto/src/kdf/ecdh_1pu.rs b/askar-crypto/src/kdf/ecdh_1pu.rs index ddf45236..eeeaa217 100644 --- a/askar-crypto/src/kdf/ecdh_1pu.rs +++ b/askar-crypto/src/kdf/ecdh_1pu.rs @@ -45,7 +45,9 @@ impl KeyDerivation for Ecdh1PU<'_, Key> { fn derive_key_bytes(&mut self, key_output: &mut [u8]) -> Result<(), Error> { let output_len = key_output.len(); // one-pass KDF only produces 256 bits of output - assert!(output_len <= 32); + if output_len > 32 { + return Err(err_msg!(Unsupported, "Exceeded maximum output length")); + } let mut kdf = ConcatKDFHash::::new(); kdf.start_pass(); @@ -58,7 +60,7 @@ impl KeyDerivation for Ecdh1PU<'_, Key> { alg: self.alg, apu: self.apu, apv: self.apv, - pub_info: &(output_len * 8).to_be_bytes(), // output length in bits + pub_info: &((output_len as u32) * 8).to_be_bytes(), // output length in bits prv_info: &[], }); diff --git a/askar-crypto/src/kdf/ecdh_es.rs b/askar-crypto/src/kdf/ecdh_es.rs index 3d269f67..e707ff66 100644 --- a/askar-crypto/src/kdf/ecdh_es.rs +++ b/askar-crypto/src/kdf/ecdh_es.rs @@ -45,7 +45,9 @@ impl KeyDerivation for EcdhEs<'_, Key> { fn derive_key_bytes(&mut self, key_output: &mut [u8]) -> Result<(), Error> { let output_len = key_output.len(); // one-pass KDF only produces 256 bits of output - assert!(output_len <= 32); + if output_len > 32 { + return Err(err_msg!(Unsupported, "Exceeded maximum output length")); + } let mut kdf = ConcatKDFHash::::new(); kdf.start_pass(); @@ -57,7 +59,7 @@ impl KeyDerivation for EcdhEs<'_, Key> { alg: self.alg, apu: self.apu, apv: self.apv, - pub_info: &(output_len * 8).to_be_bytes(), // output length in bits + pub_info: &((output_len as u32) * 8).to_be_bytes(), // output length in bits prv_info: &[], }); From aeacd61b964437cb728fe915fde022c35f74eb48 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Wed, 5 May 2021 19:28:01 -0700 Subject: [PATCH 106/116] metadata fixes Signed-off-by: Andrew Whitehead --- askar-crypto/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/askar-crypto/Cargo.toml b/askar-crypto/Cargo.toml index a1ae8c1d..029473db 100644 --- a/askar-crypto/Cargo.toml +++ b/askar-crypto/Cargo.toml @@ -5,7 +5,7 @@ authors = ["Hyperledger Aries Contributors "] edition = "2018" description = "Hyperledger Aries Askar cryptography" license = "MIT OR Apache-2.0" -readme = "README.md" +readme = "../README.md" repository = "https://github.com/hyperledger/aries-askar/" categories = ["cryptography", "database"] keywords = ["hyperledger", "aries", "ssi", "verifiable", "credentials"] @@ -28,7 +28,7 @@ ec_curves = ["k256", "p256"] ed25519 = ["curve25519-dalek", "ed25519-dalek", "x25519-dalek"] [dev-dependencies] -base64 = { version = "*", default-features = false, features = ["alloc"] } +base64 = { version = "0.13", default-features = false, features = ["alloc"] } criterion = "0.3" hex-literal = "0.3" serde_cbor = "0.11" From 44c9aac98d438b416e39e460e611a31296460cec Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Wed, 5 May 2021 22:45:59 -0700 Subject: [PATCH 107/116] fix docs generation; misc cleanups Signed-off-by: Andrew Whitehead --- askar-crypto/src/alg/aesgcm.rs | 4 ++-- askar-crypto/src/alg/bls.rs | 7 ++----- askar-crypto/src/alg/chacha20.rs | 2 +- askar-crypto/src/alg/mod.rs | 4 ++-- askar-crypto/src/buffer/array.rs | 4 ++-- askar-crypto/src/error.rs | 2 ++ askar-crypto/src/lib.rs | 8 +++++++- 7 files changed, 18 insertions(+), 13 deletions(-) diff --git a/askar-crypto/src/alg/aesgcm.rs b/askar-crypto/src/alg/aesgcm.rs index 7846197a..2a3474c8 100644 --- a/askar-crypto/src/alg/aesgcm.rs +++ b/askar-crypto/src/alg/aesgcm.rs @@ -1,4 +1,4 @@ -//! AES-GCM (Advanced Encryption Standard, Galois Counter Mode) key support +//! AES-GCM key representations with AEAD support use core::fmt::{self, Debug, Formatter}; @@ -22,7 +22,7 @@ use crate::{ /// The 'kty' value of a symmetric key JWK pub static JWK_KEY_TYPE: &'static str = "oct"; -/// A common trait among supported AES-GCM sizes +/// Trait implemented by supported AES-GCM algorithms pub trait AesGcmType: 'static { /// The AEAD implementation type Aead: NewAead + Aead + AeadInPlace; diff --git a/askar-crypto/src/alg/bls.rs b/askar-crypto/src/alg/bls.rs index dd529fa8..147c4284 100644 --- a/askar-crypto/src/alg/bls.rs +++ b/askar-crypto/src/alg/bls.rs @@ -232,11 +232,8 @@ impl BlsSecretKey { } } -/// A common trait among supported ChaCha20 key types +/// Trait implemented by supported BLS public key types pub trait BlsPublicKeyType: 'static { - // /// The AEAD implementation - // type Aead: NewAead + Aead + AeadInPlace; - /// The concrete key representation type Buffer: Clone + Debug + PartialEq + Sized; @@ -410,7 +407,7 @@ mod tests { use crate::repr::{ToPublicBytes, ToSecretBytes}; use std::string::ToString; - // test against EIP-2333 (updated for signatures draft 4) + // test against EIP-2333 (as updated for signatures draft 4) #[test] fn key_gen_expected() { let seed = &hex!( diff --git a/askar-crypto/src/alg/chacha20.rs b/askar-crypto/src/alg/chacha20.rs index 997d25e5..0ae3260f 100644 --- a/askar-crypto/src/alg/chacha20.rs +++ b/askar-crypto/src/alg/chacha20.rs @@ -22,7 +22,7 @@ use crate::{ /// The 'kty' value of a symmetric key JWK pub static JWK_KEY_TYPE: &'static str = "oct"; -/// A common trait among supported ChaCha20 key types +/// Trait implemented by supported ChaCha20 algorithms pub trait Chacha20Type: 'static { /// The AEAD implementation type Aead: NewAead + Aead + AeadInPlace; diff --git a/askar-crypto/src/alg/mod.rs b/askar-crypto/src/alg/mod.rs index 241d2492..54aa7a74 100644 --- a/askar-crypto/src/alg/mod.rs +++ b/askar-crypto/src/alg/mod.rs @@ -219,8 +219,8 @@ pub enum EcCurves { Secp256k1, } -/// A common trait for accessing the algorithm of a key, -/// used when converting to generic `AnyKey` instances. +/// A trait for accessing the algorithm of a key, used when +/// converting to generic `AnyKey` instances. pub trait HasKeyAlg: Debug { /// Get the corresponding key algorithm. fn algorithm(&self) -> KeyAlg; diff --git a/askar-crypto/src/buffer/array.rs b/askar-crypto/src/buffer/array.rs index da57ac21..eea83066 100644 --- a/askar-crypto/src/buffer/array.rs +++ b/askar-crypto/src/buffer/array.rs @@ -64,8 +64,8 @@ impl> ArrayKey { /// Get the length of the array #[inline] - pub fn len(&self) -> usize { - self.0.len() + pub fn len() -> usize { + Self::SIZE } /// Create a new array of random bytes diff --git a/askar-crypto/src/error.rs b/askar-crypto/src/error.rs index 0a57a6b2..aa90ef54 100644 --- a/askar-crypto/src/error.rs +++ b/askar-crypto/src/error.rs @@ -114,6 +114,8 @@ impl Display for Error { #[cfg(feature = "std")] impl StdError for Error { fn source(&self) -> Option<&(dyn StdError + 'static)> { + // the transmute operation here is only removing Send and Sync markers + #[allow(unsafe_code)] self.cause .as_ref() .map(|err| unsafe { std::mem::transmute(&**err) }) diff --git a/askar-crypto/src/lib.rs b/askar-crypto/src/lib.rs index d8e045ad..8ed88f09 100644 --- a/askar-crypto/src/lib.rs +++ b/askar-crypto/src/lib.rs @@ -1,7 +1,13 @@ //! Cryptography primitives and operations for aries-askar. #![no_std] -#![deny(missing_docs, missing_debug_implementations, rust_2018_idioms)] +#![cfg_attr(docsrs, feature(doc_cfg))] +#![deny( + missing_docs, + missing_debug_implementations, + rust_2018_idioms, + unsafe_code +)] #[cfg(feature = "alloc")] extern crate alloc; From 183004e048bef603920117149285a711b4744ec6 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Thu, 6 May 2021 10:00:44 -0700 Subject: [PATCH 108/116] fix features for docs.rs Signed-off-by: Andrew Whitehead --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ada2c0d7..bd109176 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ path = "src/lib.rs" crate-type = ["staticlib", "rlib", "cdylib"] [package.metadata.docs.rs] -features = ["all"] +features = ["all_backends"] no-default-features = true rustdoc-args = ["--cfg", "docsrs"] From e8419db70126e13cdd18fffad9a57b8b106a136c Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Thu, 6 May 2021 11:07:45 -0700 Subject: [PATCH 109/116] initial key type documentation Signed-off-by: Andrew Whitehead --- askar-crypto/Cargo.toml | 6 +++--- askar-crypto/README.md | 30 ++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 askar-crypto/README.md diff --git a/askar-crypto/Cargo.toml b/askar-crypto/Cargo.toml index 029473db..4d5f9ff8 100644 --- a/askar-crypto/Cargo.toml +++ b/askar-crypto/Cargo.toml @@ -5,10 +5,10 @@ authors = ["Hyperledger Aries Contributors "] edition = "2018" description = "Hyperledger Aries Askar cryptography" license = "MIT OR Apache-2.0" -readme = "../README.md" +readme = "README.md" repository = "https://github.com/hyperledger/aries-askar/" -categories = ["cryptography", "database"] -keywords = ["hyperledger", "aries", "ssi", "verifiable", "credentials"] +categories = ["cryptography", "no-std"] +keywords = ["hyperledger", "aries", "didcomm", "ssi"] [package.metadata.docs.rs] features = ["argon2", "std"] diff --git a/askar-crypto/README.md b/askar-crypto/README.md new file mode 100644 index 00000000..ab74e16f --- /dev/null +++ b/askar-crypto/README.md @@ -0,0 +1,30 @@ +# askar-crypto + +[![Rust Crate](https://img.shields.io/crates/v/askar-crypto.svg)](https://crates.io/crates/askar-crypto) +[![Rust Documentation](https://docs.rs/aries-askar/badge.svg)](https://docs.rs/aries-askar) + +The `askar-crypto` crate provides the basic key representations and cryptographic operations used by [`aries-askar`](https://github.com/hyperledger/aries-askar). + +## Supported Key Types + +| Key Type | Feature | Operations | Notes | +| -------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------ | -------------------------- | +| AES-GCM | `aes` | AEAD encryption
JWK export | A128GCM and A256GCM | +| (X)ChaCha20-Poly1305 | `chacha` | AEAD encryption
JWK export | aka C20P, XC20P | +| BLS12-381 | `bls` | [`bls-signature`](https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-04)-compatible key generation
JWK import/export | G1, G2, and G1G2 key types | +| Ed25519 | `ed25519` | EdDSA signatures
JWK import/export | | +| X25519 | `ed25519` | DH key exchange
JWK import/export | | +| K-256 | `k256` | ECDSA signatures
DH key exchange
JWK import/export | aka secp256k1 | +| P-256 | `p256` | ECDSA signatures
DH key exchange
JWK import/export | aka nist256p1, secp256r1 | + +## 'Any' Key support + +The `any_key` feature (which depends on `alloc`) provides a generic interface for creating and working with any supported key type. + +## JOSE Authenticated Encryption + +This crate provides implementations of the [ECDH-ES](https://tools.ietf.org/html/rfc7518#section-4.6) and [ECDH-1PU (draft 3)](https://tools.ietf.org/html/draft-madden-jose-ecdh-1pu-03) key agreement operations, for use in deriving the CEK or key wrapping key when producing or consuming JWE envelopes using these protection algorithms. + +## no-std + +This crate supports the optional `alloc` feature, gating types and operations that depend on a global allocator. The `std` feature depends on `alloc`, and adds support for `std::error::Error`. From f82742b17fd86a9714f994adabb5ea1730b59a41 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Thu, 6 May 2021 11:07:59 -0700 Subject: [PATCH 110/116] add badges Signed-off-by: Andrew Whitehead --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2c171687..d4e39e2b 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,15 @@ # aries-askar -Aries-Askar, secure storage designed for Hyperledger Aries agents. +[![Unit Tests](https://github.com/hyperledger/aries-askar/workflows/Aries-Askar/badge.svg)](https://github.com/hyperledger/aries-askar/actions) +[![Rust Crate](https://img.shields.io/crates/v/aries-askar.svg)](https://crates.io/crates/aries-askar) +[![Rust Documentation](https://docs.rs/aries-askar/badge.svg)](https://docs.rs/aries-askar) +[![Python Package](https://img.shields.io/pypi/v/aries_askar)](https://pypi.org/project/aries-askar/) + +Secure storage and cryptographic support designed for Hyperledger Aries agents. ## Credit -The initial implementation of `aries-askar` was developed by the Verifiable Organizations Network (VON) team based at the Province of British Columbia. The database structure derives largely from the wallet design within [Hyperledger Indy-SDK](https://github.com/hyperledger/indy-sdk). To learn more about VON and what's happening with decentralized identity in British Columbia, please go to [https://vonx.io](https://vonx.io). +The initial implementation of `aries-askar` was developed by the Verifiable Organizations Network (VON) team based at the Province of British Columbia, and inspired by the wallet design within [Hyperledger Indy-SDK](https://github.com/hyperledger/indy-sdk). To learn more about VON and what's happening with decentralized identity in British Columbia, please go to [https://vonx.io](https://vonx.io). ## Contributing From d1f31e441d04ce123bf4a8553791bdfeea1e87a6 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Fri, 7 May 2021 14:59:16 -0700 Subject: [PATCH 111/116] add A128CBC-HS256, A256CBC-HS512 key types Signed-off-by: Andrew Whitehead --- askar-crypto/Cargo.toml | 6 +- askar-crypto/README.md | 19 +- askar-crypto/src/alg/aes.rs | 579 +++++++++++++++++++++++++++ askar-crypto/src/alg/aesgcm.rs | 290 -------------- askar-crypto/src/alg/any.rs | 99 +++-- askar-crypto/src/alg/mod.rs | 30 +- askar-crypto/src/buffer/array.rs | 11 +- wrappers/python/aries_askar/types.py | 6 +- wrappers/python/demo/test.py | 2 +- 9 files changed, 703 insertions(+), 339 deletions(-) create mode 100644 askar-crypto/src/alg/aes.rs delete mode 100644 askar-crypto/src/alg/aesgcm.rs diff --git a/askar-crypto/Cargo.toml b/askar-crypto/Cargo.toml index 4d5f9ff8..23ae1a2e 100644 --- a/askar-crypto/Cargo.toml +++ b/askar-crypto/Cargo.toml @@ -20,7 +20,7 @@ alloc = [] std = ["alloc"] all_keys = ["aes", "bls", "chacha", "ec_curves", "ed25519"] any_key = ["alloc"] -aes = ["aes-gcm"] +aes = ["aes-core", "aes-gcm", "block-modes", "hmac"] bls = ["bls12_381", "hkdf"] chacha = ["chacha20poly1305"] crypto_box = ["alloc", "crypto_box_rs", "ed25519"] @@ -43,10 +43,12 @@ harness = false [dependencies] aead = "0.3" +aes-core = { package = "aes", version = "0.6", default-features = false, optional = true } aes-gcm = { version = "0.8", default-features = false, features = ["aes"], optional = true } argon2 = { version = "0.1", default-features = false, features = ["password-hash"], optional = true } base64 = { version = "0.13", default-features = false } blake2 = { version = "0.9", default-features = false } +block-modes = { version = "0.7", default-features = false, optional = true } bls12_381 = { version = "0.4", default-features = false, features = ["groups"], optional = true } chacha20 = { version = "0.6" } # should match chacha20poly1305 chacha20poly1305 = { version = "0.7", default-features = false, features = ["chacha20", "xchacha20poly1305"], optional = true } @@ -56,11 +58,13 @@ ed25519-dalek = { version = "1.0", default-features = false, features = ["u64_ba digest = "0.9" group = "0.9" hkdf = { version = "0.11", optional = true } +hmac = { version = "0.11", optional = true } k256 = { version = "0.8", default-features = false, features = ["arithmetic", "ecdsa", "ecdh", "sha256", "zeroize"], optional = true } p256 = { version = "0.8", default-features = false, features = ["arithmetic", "ecdsa", "ecdh", "zeroize"], optional = true } rand = { version = "0.8", default-features = false, features = ["getrandom"] } serde = { version = "1.0", default-features = false, features = ["derive"] } serde_json = { version = "1.0", default-features = false } +subtle = "2.4" sha2 = { version = "0.9", default-features = false } x25519-dalek = { version = "1.1", default-features = false, features = ["u64_backend"], optional = true } zeroize = { version = "1.1.0", features = ["zeroize_derive"] } diff --git a/askar-crypto/README.md b/askar-crypto/README.md index ab74e16f..deb36829 100644 --- a/askar-crypto/README.md +++ b/askar-crypto/README.md @@ -7,15 +7,16 @@ The `askar-crypto` crate provides the basic key representations and cryptographi ## Supported Key Types -| Key Type | Feature | Operations | Notes | -| -------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------ | -------------------------- | -| AES-GCM | `aes` | AEAD encryption
JWK export | A128GCM and A256GCM | -| (X)ChaCha20-Poly1305 | `chacha` | AEAD encryption
JWK export | aka C20P, XC20P | -| BLS12-381 | `bls` | [`bls-signature`](https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-04)-compatible key generation
JWK import/export | G1, G2, and G1G2 key types | -| Ed25519 | `ed25519` | EdDSA signatures
JWK import/export | | -| X25519 | `ed25519` | DH key exchange
JWK import/export | | -| K-256 | `k256` | ECDSA signatures
DH key exchange
JWK import/export | aka secp256k1 | -| P-256 | `p256` | ECDSA signatures
DH key exchange
JWK import/export | aka nist256p1, secp256r1 | +| Key Type | Feature | Operations | Notes | +| -------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------- | +| AES-GCM | `aes` | AEAD encryption
JWK export | A128GCM and A256GCM | +| AES-CBC-HMAC-SHA2 | `aes` | AEAD encryption
JWK export | A128CBC-HS256 and A256CBC-HS512 | +| (X)ChaCha20-Poly1305 | `chacha` | AEAD encryption
JWK export | aka C20P, XC20P | +| BLS12-381 | `bls` | [`bls-signature`](https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-04)-compatible key generation
JWK import/export | G1, G2, and G1G2 key types | +| Ed25519 | `ed25519` | EdDSA signatures
JWK import/export | | +| X25519 | `ed25519` | DH key exchange
JWK import/export | | +| K-256 | `k256` | ECDSA signatures
DH key exchange
JWK import/export | aka secp256k1 | +| P-256 | `p256` | ECDSA signatures
DH key exchange
JWK import/export | aka nist256p1, secp256r1 | ## 'Any' Key support diff --git a/askar-crypto/src/alg/aes.rs b/askar-crypto/src/alg/aes.rs new file mode 100644 index 00000000..0ca180f7 --- /dev/null +++ b/askar-crypto/src/alg/aes.rs @@ -0,0 +1,579 @@ +//! AES-GCM key representations with AEAD support + +use core::{ + fmt::{self, Debug, Formatter}, + marker::PhantomData, +}; + +use aead::{generic_array::ArrayLength, AeadInPlace, NewAead}; +use aes_gcm::{Aes128Gcm, Aes256Gcm}; +use block_modes::{ + block_padding::Pkcs7, + cipher::{BlockCipher, NewBlockCipher}, + BlockMode, Cbc, +}; +use digest::{BlockInput, FixedOutput, Reset, Update}; +use hmac::{Hmac, Mac, NewMac}; +use serde::{Deserialize, Serialize}; +use zeroize::Zeroize; + +use super::{AesTypes, HasKeyAlg, KeyAlg}; +use crate::{ + buffer::{ArrayKey, ResizeBuffer, Writer}, + encrypt::{KeyAeadInPlace, KeyAeadMeta, KeyAeadParams}, + error::Error, + generic_array::{ + typenum::{self, Unsigned}, + GenericArray, + }, + jwk::{JwkEncoder, ToJwk}, + kdf::{FromKeyDerivation, FromKeyExchange, KeyDerivation, KeyExchange}, + random::fill_random_deterministic, + repr::{KeyGen, KeyMeta, KeySecretBytes, Seed, SeedMethod}, +}; + +/// The 'kty' value of a symmetric key JWK +pub static JWK_KEY_TYPE: &'static str = "oct"; + +/// Trait implemented by supported AES authenticated encryption algorithms +pub trait AesType: 'static { + /// The AEAD implementation + type Aead: AesAead; + + /// The associated algorithm type + const ALG_TYPE: AesTypes; + /// The associated JWK algorithm name + const JWK_ALG: &'static str; +} + +type KeyType
= ArrayKey<<::Aead as AesAead>::KeySize>; + +type NonceSize = <::Aead as AesAead>::NonceSize; + +type TagSize = <::Aead as AesAead>::TagSize; + +/// An AES-GCM symmetric encryption key +#[derive(Serialize, Deserialize, Zeroize)] +#[serde( + transparent, + bound( + deserialize = "KeyType: for<'a> Deserialize<'a>", + serialize = "KeyType: Serialize" + ) +)] +// SECURITY: ArrayKey is zeroized on drop +pub struct AesKey(KeyType); + +impl AesKey { + /// The length of the secret key in bytes + pub const KEY_LENGTH: usize = KeyType::::SIZE; + /// The length of the AEAD encryption nonce + pub const NONCE_LENGTH: usize = NonceSize::::USIZE; + /// The length of the AEAD encryption tag + pub const TAG_LENGTH: usize = TagSize::::USIZE; +} + +impl Clone for AesKey { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl Debug for AesKey { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("AesKey") + .field("alg", &T::JWK_ALG) + .field("key", &self.0) + .finish() + } +} + +impl PartialEq for AesKey { + fn eq(&self, other: &Self) -> bool { + other.0 == self.0 + } +} + +impl Eq for AesKey {} + +impl HasKeyAlg for AesKey { + fn algorithm(&self) -> KeyAlg { + KeyAlg::Aes(T::ALG_TYPE) + } +} + +impl KeyMeta for AesKey { + type KeySize = ::KeySize; +} + +impl KeyGen for AesKey { + fn generate() -> Result { + Ok(AesKey(KeyType::::random())) + } + + fn from_seed(seed: Seed<'_>) -> Result + where + Self: Sized, + { + match seed { + Seed::Bytes(ikm, SeedMethod::Preferred) | Seed::Bytes(ikm, SeedMethod::RandomDet) => { + Ok(Self(KeyType::::try_new_with(|arr| { + fill_random_deterministic(ikm, arr) + })?)) + } + #[allow(unreachable_patterns)] + _ => Err(err_msg!(Unsupported)), + } + } +} + +impl KeySecretBytes for AesKey { + fn from_secret_bytes(key: &[u8]) -> Result { + if key.len() != KeyType::::SIZE { + return Err(err_msg!(InvalidKeyData)); + } + Ok(Self(KeyType::::from_slice(key))) + } + + fn with_secret_bytes(&self, f: impl FnOnce(Option<&[u8]>) -> O) -> O { + f(Some(self.0.as_ref())) + } +} + +impl FromKeyDerivation for AesKey { + fn from_key_derivation(mut derive: D) -> Result + where + Self: Sized, + { + Ok(Self(KeyType::::try_new_with(|arr| { + derive.derive_key_bytes(arr) + })?)) + } +} + +impl KeyAeadMeta for AesKey { + type NonceSize = NonceSize; + type TagSize = TagSize; +} + +impl KeyAeadInPlace for AesKey { + /// Encrypt a secret value in place, appending the verification tag + fn encrypt_in_place( + &self, + buffer: &mut dyn ResizeBuffer, + nonce: &[u8], + aad: &[u8], + ) -> Result<(), Error> { + if nonce.len() != NonceSize::::USIZE { + return Err(err_msg!(InvalidNonce)); + } + T::Aead::aes_encrypt_in_place( + self.0.as_ref(), + buffer, + GenericArray::from_slice(nonce), + aad, + ) + } + + /// Decrypt an encrypted (verification tag appended) value in place + fn decrypt_in_place( + &self, + buffer: &mut dyn ResizeBuffer, + nonce: &[u8], + aad: &[u8], + ) -> Result<(), Error> { + if nonce.len() != NonceSize::::USIZE { + return Err(err_msg!(InvalidNonce)); + } + T::Aead::aes_decrypt_in_place( + self.0.as_ref(), + buffer, + GenericArray::from_slice(nonce), + aad, + ) + } + + fn aead_params(&self) -> KeyAeadParams { + KeyAeadParams { + nonce_length: NonceSize::::USIZE, + tag_length: TagSize::::USIZE, + } + } +} + +impl ToJwk for AesKey { + fn encode_jwk(&self, enc: &mut JwkEncoder<'_>) -> Result<(), Error> { + if enc.is_public() { + return Err(err_msg!(Unsupported, "Cannot export as a public key")); + } + if !enc.is_thumbprint() { + enc.add_str("alg", T::JWK_ALG)?; + } + enc.add_as_base64("k", self.0.as_ref())?; + enc.add_str("kty", JWK_KEY_TYPE)?; + Ok(()) + } +} + +// for direct key agreement (not used currently) +impl FromKeyExchange for AesKey +where + Lhs: KeyExchange + ?Sized, + Rhs: ?Sized, + T: AesType, +{ + fn from_key_exchange(lhs: &Lhs, rhs: &Rhs) -> Result { + Ok(Self(KeyType::::try_new_with(|arr| { + let mut buf = Writer::from_slice(arr); + lhs.write_key_exchange(rhs, &mut buf)?; + if buf.position() != Self::KEY_LENGTH { + return Err(err_msg!(Usage, "Invalid length for key exchange output")); + } + Ok(()) + })?)) + } +} + +/// 128 bit AES-GCM +#[derive(Debug)] +pub struct A128Gcm; + +impl AesType for A128Gcm { + type Aead = Aes128Gcm; + + const ALG_TYPE: AesTypes = AesTypes::A128Gcm; + const JWK_ALG: &'static str = "A128GCM"; +} + +/// 256 bit AES-GCM +#[derive(Debug)] +pub struct A256Gcm; + +impl AesType for A256Gcm { + type Aead = Aes256Gcm; + + const ALG_TYPE: AesTypes = AesTypes::A256Gcm; + const JWK_ALG: &'static str = "A256GCM"; +} + +/// Specialized trait for performing AEAD encryption +pub trait AesAead { + /// The size of the associated key + type KeySize: ArrayLength; + /// The size of the nonce + type NonceSize: ArrayLength; + /// The size of the authentication tag + type TagSize: ArrayLength; + + /// Perform AEAD encryption + fn aes_encrypt_in_place( + key: &GenericArray, + buffer: &mut dyn ResizeBuffer, + key: &GenericArray, + aad: &[u8], + ) -> Result<(), Error>; + + /// Perform AEAD decryption + fn aes_decrypt_in_place( + key: &GenericArray, + buffer: &mut dyn ResizeBuffer, + nonce: &GenericArray, + aad: &[u8], + ) -> Result<(), Error>; + + /// Calculate padding length for a plaintext length + fn aes_padding_length(len: usize) -> usize; +} + +// Generic implementation for AesGcm +impl AesAead for T +where + T: NewAead + AeadInPlace, +{ + type KeySize = T::KeySize; + type NonceSize = T::NonceSize; + type TagSize = T::TagSize; + + fn aes_encrypt_in_place( + key: &GenericArray, + buffer: &mut dyn ResizeBuffer, + nonce: &GenericArray, + aad: &[u8], + ) -> Result<(), Error> { + let enc = ::new(key); + let tag = enc + .encrypt_in_place_detached(nonce, aad, buffer.as_mut()) + .map_err(|_| err_msg!(Encryption, "AEAD encryption error"))?; + buffer.buffer_write(&tag[..])?; + Ok(()) + } + + fn aes_decrypt_in_place( + key: &GenericArray, + buffer: &mut dyn ResizeBuffer, + nonce: &GenericArray, + aad: &[u8], + ) -> Result<(), Error> { + let buf_len = buffer.as_ref().len(); + if buf_len < Self::TagSize::USIZE { + return Err(err_msg!(Encryption, "Invalid size for encrypted data")); + } + let tag_start = buf_len - Self::TagSize::USIZE; + let mut tag = GenericArray::default(); + tag.clone_from_slice(&buffer.as_ref()[tag_start..]); + let enc = ::new(key); + enc.decrypt_in_place_detached(nonce, aad, &mut buffer.as_mut()[..tag_start], &tag) + .map_err(|_| err_msg!(Encryption, "AEAD decryption error"))?; + buffer.buffer_resize(tag_start)?; + Ok(()) + } + + fn aes_padding_length(_len: usize) -> usize { + 0 + } +} + +/// 128 bit AES-CBC with HMAC-256 +#[derive(Debug)] +pub struct A128CbcHs256; + +impl AesType for A128CbcHs256 { + type Aead = AesCbcHmac; + + const ALG_TYPE: AesTypes = AesTypes::A128CbcHs256; + const JWK_ALG: &'static str = "A128CBC-HS256"; +} + +/// 256 bit AES-CBC with HMAC-512 +#[derive(Debug)] +pub struct A256CbcHs512; + +impl AesType for A256CbcHs512 { + type Aead = AesCbcHmac; + + const ALG_TYPE: AesTypes = AesTypes::A256CbcHs512; + const JWK_ALG: &'static str = "A256CBC-HS512"; +} + +/// AES-CBC-HMAC implementation +#[derive(Debug)] +pub struct AesCbcHmac(PhantomData<(C, D)>); + +// Specific implementation, cannot implement normal AeadInPlace trait +impl AesAead for AesCbcHmac +where + C: BlockCipher + NewBlockCipher, + D: Update + BlockInput + FixedOutput + Reset + Default + Clone, + C::KeySize: core::ops::Shl, + >::Output: ArrayLength, +{ + type KeySize = typenum::Double; + type NonceSize = C::BlockSize; + type TagSize = C::KeySize; + + fn aes_encrypt_in_place( + key: &GenericArray, + buffer: &mut dyn ResizeBuffer, + nonce: &GenericArray, + aad: &[u8], + ) -> Result<(), Error> { + // FIXME validate maximum input length + + // this should be optimized unless it matters + if Self::TagSize::USIZE > D::OutputSize::USIZE { + return Err(err_msg!( + Encryption, + "AES-CBC-HMAC tag size exceeds maximum supported" + )); + } + + if aad.len() as u64 > u64::MAX / 8 { + return Err(err_msg!( + Encryption, + "AES-CBC-HMAC aad size exceeds maximum supported" + )); + } + + let msg_len = buffer.as_ref().len(); + let pad_len = Self::aes_padding_length(msg_len); + buffer.buffer_extend(pad_len + Self::TagSize::USIZE)?; + let enc_key = GenericArray::from_slice(&key[C::KeySize::USIZE..]); + Cbc::::new_fix(enc_key, nonce) + .encrypt(buffer.as_mut(), msg_len) + .map_err(|_| err_msg!(Encryption, "AES-CBC encryption error"))?; + let ctext_end = msg_len + pad_len; + + let mut hmac = Hmac::::new_from_slice(&key[..C::KeySize::USIZE]) + .expect("Incompatible HMAC key length"); + hmac.update(aad); + hmac.update(nonce.as_ref()); + hmac.update(&buffer.as_ref()[..ctext_end]); + hmac.update(&((aad.len() as u64) * 8).to_be_bytes()); + let mac = hmac.finalize().into_bytes(); + buffer.as_mut()[ctext_end..].copy_from_slice(&mac[..Self::TagSize::USIZE]); + + Ok(()) + } + + fn aes_decrypt_in_place( + key: &GenericArray, + buffer: &mut dyn ResizeBuffer, + nonce: &GenericArray, + aad: &[u8], + ) -> Result<(), Error> { + let buf_len = buffer.as_ref().len(); + if buf_len < Self::TagSize::USIZE { + return Err(err_msg!(Encryption, "Invalid size for encrypted data")); + } + let ctext_end = buf_len - Self::TagSize::USIZE; + let tag = GenericArray::::from_slice(&buffer.as_ref()[ctext_end..]); + + let mut hmac = Hmac::::new_from_slice(&key[..C::KeySize::USIZE]) + .expect("Incompatible HMAC key length"); + hmac.update(aad); + hmac.update(nonce.as_ref()); + hmac.update(&buffer.as_ref()[..ctext_end]); + hmac.update(&(aad.len() as u64).to_be_bytes()); + let mac = hmac.finalize().into_bytes(); + let tag_match = + subtle::ConstantTimeEq::ct_eq(tag.as_ref(), &mac[..Self::TagSize::USIZE]).unwrap_u8(); + + let enc_key = GenericArray::from_slice(&key[C::KeySize::USIZE..]); + let dec_len = Cbc::::new_fix(enc_key, nonce) + .decrypt(&mut buffer.as_mut()[..ctext_end]) + .map_err(|_| err_msg!(Encryption, "AES-CBC decryption error"))? + .len(); + buffer.buffer_resize(dec_len)?; + + if tag_match != 1 { + Err(err_msg!(Encryption, "AEAD decryption error")) + } else { + Ok(()) + } + } + + #[inline] + fn aes_padding_length(len: usize) -> usize { + Self::NonceSize::USIZE - (len % Self::NonceSize::USIZE) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::buffer::SecretBytes; + use crate::repr::ToSecretBytes; + use std::string::ToString; + + #[test] + fn encrypt_round_trip() { + fn test_encrypt() { + let input = b"hello"; + let key = AesKey::::generate().unwrap(); + let mut buffer = SecretBytes::from_slice(input); + let pad_len = T::Aead::aes_padding_length(input.len()); + let nonce = AesKey::::random_nonce(); + key.encrypt_in_place(&mut buffer, &nonce, &[]).unwrap(); + let enc_len = buffer.len(); + assert_eq!(enc_len, input.len() + pad_len + AesKey::::TAG_LENGTH); + assert_ne!(&buffer[..], input); + let mut dec = buffer.clone(); + key.decrypt_in_place(&mut dec, &nonce, &[]).unwrap(); + assert_eq!(&dec[..], input); + + // test tag validation + buffer.as_mut()[enc_len - 1] += 1; + assert!(key.decrypt_in_place(&mut buffer, &nonce, &[]).is_err()); + } + test_encrypt::(); + test_encrypt::(); + test_encrypt::(); + test_encrypt::(); + } + + #[test] + fn serialize_round_trip() { + fn test_serialize() { + let key = AesKey::::generate().unwrap(); + let sk = key.to_secret_bytes().unwrap(); + let bytes = serde_cbor::to_vec(&key).unwrap(); + let deser: &[u8] = serde_cbor::from_slice(bytes.as_ref()).unwrap(); + assert_eq!(deser, sk.as_ref()); + } + test_serialize::(); + test_serialize::(); + test_serialize::(); + test_serialize::(); + } + + #[test] + fn encrypt_expected_cbc_hmac_128() { + let key_data = &hex!("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"); + let input = b"A cipher system must not be required to be secret, and it must be able to fall into the hands of the enemy without inconvenience"; + let nonce = &hex!("1af38c2dc2b96ffdd86694092341bc04"); + let aad = b"The second principle of Auguste Kerckhoffs"; + let key = AesKey::::from_secret_bytes(key_data).unwrap(); + let mut buffer = SecretBytes::from_slice(input); + key.encrypt_in_place(&mut buffer, &nonce[..], &aad[..]) + .unwrap(); + + assert_eq!( + buffer.as_hex().to_string(), + "c80edfa32ddf39d5ef00c0b468834279a2e46a1b8049f792f76bfe54b903a9c9\ + a94ac9b47ad2655c5f10f9aef71427e2fc6f9b3f399a221489f16362c7032336\ + 09d45ac69864e3321cf82935ac4096c86e133314c54019e8ca7980dfa4b9cf1b\ + 384c486f3a54c51078158ee5d79de59fbd34d848b3d69550a67646344427ade5\ + 4b8851ffb598f7f80074b9473c82e2db\ + 652c3fa36b0a7c5b3219fab3a30bc1c4" + ) + } + + #[test] + fn encrypt_expected_cbc_hmac_256() { + let key_data = &hex!( + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f + 202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f" + ); + let input = b"A cipher system must not be required to be secret, and it must be able to fall into the hands of the enemy without inconvenience"; + let nonce = &hex!("1af38c2dc2b96ffdd86694092341bc04"); + let aad = b"The second principle of Auguste Kerckhoffs"; + let key = AesKey::::from_secret_bytes(key_data).unwrap(); + let mut buffer = SecretBytes::from_slice(input); + key.encrypt_in_place(&mut buffer, &nonce[..], &aad[..]) + .unwrap(); + + assert_eq!( + buffer.as_hex().to_string(), + "4affaaadb78c31c5da4b1b590d10ffbd3dd8d5d302423526912da037ecbcc7bd\ + 822c301dd67c373bccb584ad3e9279c2e6d12a1374b77f077553df829410446b\ + 36ebd97066296ae6427ea75c2e0846a11a09ccf5370dc80bfecbad28c73f09b3\ + a3b75e662a2594410ae496b2e2e6609e31e6e02cc837f053d21f37ff4f51950b\ + be2638d09dd7a4930930806d0703b1f6\ + 4dd3b4c088a7f45c216839645b2012bf2e6269a8c56a816dbc1b267761955bc5" + ) + } + + #[test] + fn encrypt_expected_cbc_hmac_1pu() { + let key_data = &hex!( + "fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0efeeedecebeae9e8e7e6e5e4e3e2e1e0 + dfdedddcdbdad9d8d7d6d5d4d3d2d1d0cfcecdcccbcac9c8c7c6c5c4c3c2c1c0" + ); + let nonce = &hex!("000102030405060708090a0b0c0d0e0f"); + let protected = "{\"alg\":\"ECDH-1PU+A128KW\",\"enc\":\"A256CBC-HS512\",\ + \"apu\":\"QWxpY2U\",\"apv\":\"Qm9iIGFuZCBDaGFybGll\",\"epk\":{\ + \"kty\":\"OKP\",\"crv\":\"X25519\",\ + \"x\":\"k9of_cpAajy0poW5gaixXGs9nHkwg1AFqUAFa39dyBc\"}}"; + let aad = base64::encode_config(protected, base64::URL_SAFE_NO_PAD); + let input = b"Three is a magic number."; + let key = AesKey::::from_secret_bytes(key_data).unwrap(); + let mut buffer = SecretBytes::from_slice(input); + key.encrypt_in_place(&mut buffer, &nonce[..], aad.as_bytes()) + .unwrap(); + let ct_len = buffer.len() - key.aead_params().tag_length; + let ctext = base64::encode_config(&buffer.as_ref()[..ct_len], base64::URL_SAFE_NO_PAD); + let tag = base64::encode_config(&buffer.as_ref()[ct_len..], base64::URL_SAFE_NO_PAD); + assert_eq!(ctext, "Az2IWsISEMDJvyc5XRL-3-d-RgNBOGolCsxFFoUXFYw"); + assert_eq!(tag, "HLb4fTlm8spGmij3RyOs2gJ4DpHM4hhVRwdF_hGb3WQ"); + } +} diff --git a/askar-crypto/src/alg/aesgcm.rs b/askar-crypto/src/alg/aesgcm.rs deleted file mode 100644 index 2a3474c8..00000000 --- a/askar-crypto/src/alg/aesgcm.rs +++ /dev/null @@ -1,290 +0,0 @@ -//! AES-GCM key representations with AEAD support - -use core::fmt::{self, Debug, Formatter}; - -use aead::{Aead, AeadInPlace, NewAead}; -use aes_gcm::{Aes128Gcm, Aes256Gcm}; -use serde::{Deserialize, Serialize}; -use zeroize::Zeroize; - -use super::{AesTypes, HasKeyAlg, KeyAlg}; -use crate::{ - buffer::{ArrayKey, ResizeBuffer, Writer}, - encrypt::{KeyAeadInPlace, KeyAeadMeta, KeyAeadParams}, - error::Error, - generic_array::{typenum::Unsigned, GenericArray}, - jwk::{JwkEncoder, ToJwk}, - kdf::{FromKeyDerivation, FromKeyExchange, KeyDerivation, KeyExchange}, - random::fill_random_deterministic, - repr::{KeyGen, KeyMeta, KeySecretBytes, Seed, SeedMethod}, -}; - -/// The 'kty' value of a symmetric key JWK -pub static JWK_KEY_TYPE: &'static str = "oct"; - -/// Trait implemented by supported AES-GCM algorithms -pub trait AesGcmType: 'static { - /// The AEAD implementation - type Aead: NewAead + Aead + AeadInPlace; - - /// The associated algorithm type - const ALG_TYPE: AesTypes; - /// The associated JWK algorithm name - const JWK_ALG: &'static str; -} - -/// 128 bit AES-GCM -#[derive(Debug)] -pub struct A128GCM; - -impl AesGcmType for A128GCM { - type Aead = Aes128Gcm; - - const ALG_TYPE: AesTypes = AesTypes::A128GCM; - const JWK_ALG: &'static str = "A128GCM"; -} - -/// 256 bit AES-GCM -#[derive(Debug)] -pub struct A256GCM; - -impl AesGcmType for A256GCM { - type Aead = Aes256Gcm; - - const ALG_TYPE: AesTypes = AesTypes::A256GCM; - const JWK_ALG: &'static str = "A256GCM"; -} - -type KeyType = ArrayKey<<::Aead as NewAead>::KeySize>; - -type NonceSize = <::Aead as Aead>::NonceSize; - -type TagSize = <::Aead as Aead>::TagSize; - -/// An AES-GCM symmetric encryption key -#[derive(Serialize, Deserialize, Zeroize)] -#[serde( - transparent, - bound( - deserialize = "KeyType: for<'a> Deserialize<'a>", - serialize = "KeyType: Serialize" - ) -)] -// SECURITY: ArrayKey is zeroized on drop -pub struct AesGcmKey(KeyType); - -impl AesGcmKey { - /// The length of the secret key in bytes - pub const KEY_LENGTH: usize = KeyType::::SIZE; - /// The length of the AEAD encryption nonce - pub const NONCE_LENGTH: usize = NonceSize::::USIZE; - /// The length of the AEAD encryption tag - pub const TAG_LENGTH: usize = TagSize::::USIZE; -} - -impl Clone for AesGcmKey { - fn clone(&self) -> Self { - Self(self.0.clone()) - } -} - -impl Debug for AesGcmKey { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_struct("AesGcmKey") - .field("alg", &T::JWK_ALG) - .field("key", &self.0) - .finish() - } -} - -impl PartialEq for AesGcmKey { - fn eq(&self, other: &Self) -> bool { - other.0 == self.0 - } -} - -impl Eq for AesGcmKey {} - -impl HasKeyAlg for AesGcmKey { - fn algorithm(&self) -> KeyAlg { - KeyAlg::Aes(T::ALG_TYPE) - } -} - -impl KeyMeta for AesGcmKey { - type KeySize = ::KeySize; -} - -impl KeyGen for AesGcmKey { - fn generate() -> Result { - Ok(AesGcmKey(KeyType::::random())) - } - - fn from_seed(seed: Seed<'_>) -> Result - where - Self: Sized, - { - match seed { - Seed::Bytes(ikm, SeedMethod::Preferred) | Seed::Bytes(ikm, SeedMethod::RandomDet) => { - Ok(Self(KeyType::::try_new_with(|arr| { - fill_random_deterministic(ikm, arr) - })?)) - } - #[allow(unreachable_patterns)] - _ => Err(err_msg!(Unsupported)), - } - } -} - -impl KeySecretBytes for AesGcmKey { - fn from_secret_bytes(key: &[u8]) -> Result { - if key.len() != ::KeySize::USIZE { - return Err(err_msg!(InvalidKeyData)); - } - Ok(Self(KeyType::::from_slice(key))) - } - - fn with_secret_bytes(&self, f: impl FnOnce(Option<&[u8]>) -> O) -> O { - f(Some(self.0.as_ref())) - } -} - -impl FromKeyDerivation for AesGcmKey { - fn from_key_derivation(mut derive: D) -> Result - where - Self: Sized, - { - Ok(Self(KeyType::::try_new_with(|arr| { - derive.derive_key_bytes(arr) - })?)) - } -} - -impl KeyAeadMeta for AesGcmKey { - type NonceSize = NonceSize; - type TagSize = TagSize; -} - -impl KeyAeadInPlace for AesGcmKey { - /// Encrypt a secret value in place, appending the verification tag - fn encrypt_in_place( - &self, - buffer: &mut dyn ResizeBuffer, - nonce: &[u8], - aad: &[u8], - ) -> Result<(), Error> { - if nonce.len() != NonceSize::::USIZE { - return Err(err_msg!(InvalidNonce)); - } - let nonce = GenericArray::from_slice(nonce); - let chacha = T::Aead::new(self.0.as_ref()); - let tag = chacha - .encrypt_in_place_detached(nonce, aad, buffer.as_mut()) - .map_err(|_| err_msg!(Encryption, "AEAD encryption error"))?; - buffer.buffer_write(&tag[..])?; - Ok(()) - } - - /// Decrypt an encrypted (verification tag appended) value in place - fn decrypt_in_place( - &self, - buffer: &mut dyn ResizeBuffer, - nonce: &[u8], - aad: &[u8], - ) -> Result<(), Error> { - if nonce.len() != NonceSize::::USIZE { - return Err(err_msg!(InvalidNonce)); - } - let nonce = GenericArray::from_slice(nonce); - let buf_len = buffer.as_ref().len(); - if buf_len < TagSize::::USIZE { - return Err(err_msg!(Encryption, "Invalid size for encrypted data")); - } - let tag_start = buf_len - TagSize::::USIZE; - let mut tag = GenericArray::default(); - tag.clone_from_slice(&buffer.as_ref()[tag_start..]); - let chacha = T::Aead::new(self.0.as_ref()); - chacha - .decrypt_in_place_detached(nonce, aad, &mut buffer.as_mut()[..tag_start], &tag) - .map_err(|_| err_msg!(Encryption, "AEAD decryption error"))?; - buffer.buffer_resize(tag_start)?; - Ok(()) - } - - fn aead_params(&self) -> KeyAeadParams { - KeyAeadParams { - nonce_length: NonceSize::::USIZE, - tag_length: TagSize::::USIZE, - } - } -} - -impl ToJwk for AesGcmKey { - fn encode_jwk(&self, enc: &mut JwkEncoder<'_>) -> Result<(), Error> { - if enc.is_public() { - return Err(err_msg!(Unsupported, "Cannot export as a public key")); - } - if !enc.is_thumbprint() { - enc.add_str("alg", T::JWK_ALG)?; - } - enc.add_as_base64("k", self.0.as_ref())?; - enc.add_str("kty", JWK_KEY_TYPE)?; - Ok(()) - } -} - -// for direct key agreement (not used currently) -impl FromKeyExchange for AesGcmKey -where - Lhs: KeyExchange + ?Sized, - Rhs: ?Sized, - T: AesGcmType, -{ - fn from_key_exchange(lhs: &Lhs, rhs: &Rhs) -> Result { - Ok(Self(KeyType::::try_new_with(|arr| { - let mut buf = Writer::from_slice(arr); - lhs.write_key_exchange(rhs, &mut buf)?; - if buf.position() != Self::KEY_LENGTH { - return Err(err_msg!(Usage, "Invalid length for key exchange output")); - } - Ok(()) - })?)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::buffer::SecretBytes; - use crate::repr::ToSecretBytes; - - #[test] - fn encrypt_round_trip() { - fn test_encrypt() { - let input = b"hello"; - let key = AesGcmKey::::generate().unwrap(); - let mut buffer = SecretBytes::from_slice(input); - let nonce = AesGcmKey::::random_nonce(); - key.encrypt_in_place(&mut buffer, &nonce, &[]).unwrap(); - assert_eq!(buffer.len(), input.len() + AesGcmKey::::TAG_LENGTH); - assert_ne!(&buffer[..], input); - key.decrypt_in_place(&mut buffer, &nonce, &[]).unwrap(); - assert_eq!(&buffer[..], input); - } - test_encrypt::(); - test_encrypt::(); - } - - #[test] - fn serialize_round_trip() { - fn test_serialize() { - let key = AesGcmKey::::generate().unwrap(); - let sk = key.to_secret_bytes().unwrap(); - let bytes = serde_cbor::to_vec(&key).unwrap(); - let deser: &[u8] = serde_cbor::from_slice(bytes.as_ref()).unwrap(); - assert_eq!(deser, sk.as_ref()); - } - test_serialize::(); - test_serialize::(); - } -} diff --git a/askar-crypto/src/alg/any.rs b/askar-crypto/src/alg/any.rs index fc983c90..cb737743 100644 --- a/askar-crypto/src/alg/any.rs +++ b/askar-crypto/src/alg/any.rs @@ -8,7 +8,7 @@ use core::{ #[cfg(feature = "aes")] use super::{ - aesgcm::{AesGcmKey, A128GCM, A256GCM}, + aes::{A128CbcHs256, A128Gcm, A256CbcHs512, A256Gcm, AesKey}, AesTypes, }; @@ -196,9 +196,13 @@ impl AnyKeyCreate for Arc { fn generate_any(alg: KeyAlg) -> Result { match alg { #[cfg(feature = "aes")] - KeyAlg::Aes(AesTypes::A128GCM) => AesGcmKey::::generate().map(R::alloc_key), + KeyAlg::Aes(AesTypes::A128Gcm) => AesKey::::generate().map(R::alloc_key), #[cfg(feature = "aes")] - KeyAlg::Aes(AesTypes::A256GCM) => AesGcmKey::::generate().map(R::alloc_key), + KeyAlg::Aes(AesTypes::A256Gcm) => AesKey::::generate().map(R::alloc_key), + #[cfg(feature = "aes")] + KeyAlg::Aes(AesTypes::A128CbcHs256) => AesKey::::generate().map(R::alloc_key), + #[cfg(feature = "aes")] + KeyAlg::Aes(AesTypes::A256CbcHs512) => AesKey::::generate().map(R::alloc_key), #[cfg(feature = "bls")] KeyAlg::Bls12_381(BlsCurves::G1) => BlsKeyPair::::generate().map(R::alloc_key), #[cfg(feature = "bls")] @@ -233,9 +237,17 @@ fn generate_any(alg: KeyAlg) -> Result { fn from_seed_any(alg: KeyAlg, seed: Seed<'_>) -> Result { match alg { #[cfg(feature = "aes")] - KeyAlg::Aes(AesTypes::A128GCM) => AesGcmKey::::from_seed(seed).map(R::alloc_key), + KeyAlg::Aes(AesTypes::A128Gcm) => AesKey::::from_seed(seed).map(R::alloc_key), #[cfg(feature = "aes")] - KeyAlg::Aes(AesTypes::A256GCM) => AesGcmKey::::from_seed(seed).map(R::alloc_key), + KeyAlg::Aes(AesTypes::A256Gcm) => AesKey::::from_seed(seed).map(R::alloc_key), + #[cfg(feature = "aes")] + KeyAlg::Aes(AesTypes::A128CbcHs256) => { + AesKey::::from_seed(seed).map(R::alloc_key) + } + #[cfg(feature = "aes")] + KeyAlg::Aes(AesTypes::A256CbcHs512) => { + AesKey::::from_seed(seed).map(R::alloc_key) + } #[cfg(feature = "bls")] KeyAlg::Bls12_381(BlsCurves::G1) => BlsKeyPair::::from_seed(seed).map(R::alloc_key), #[cfg(feature = "bls")] @@ -301,12 +313,20 @@ fn from_public_bytes_any(alg: KeyAlg, public: &[u8]) -> Result(alg: KeyAlg, secret: &[u8]) -> Result { match alg { #[cfg(feature = "aes")] - KeyAlg::Aes(AesTypes::A128GCM) => { - AesGcmKey::::from_secret_bytes(secret).map(R::alloc_key) + KeyAlg::Aes(AesTypes::A128Gcm) => { + AesKey::::from_secret_bytes(secret).map(R::alloc_key) + } + #[cfg(feature = "aes")] + KeyAlg::Aes(AesTypes::A256Gcm) => { + AesKey::::from_secret_bytes(secret).map(R::alloc_key) + } + #[cfg(feature = "aes")] + KeyAlg::Aes(AesTypes::A128CbcHs256) => { + AesKey::::from_secret_bytes(secret).map(R::alloc_key) } #[cfg(feature = "aes")] - KeyAlg::Aes(AesTypes::A256GCM) => { - AesGcmKey::::from_secret_bytes(secret).map(R::alloc_key) + KeyAlg::Aes(AesTypes::A256CbcHs512) => { + AesKey::::from_secret_bytes(secret).map(R::alloc_key) } #[cfg(feature = "bls")] KeyAlg::Bls12_381(BlsCurves::G1) => { @@ -360,12 +380,20 @@ where { match alg { #[cfg(feature = "aes")] - KeyAlg::Aes(AesTypes::A128GCM) => { - AesGcmKey::::from_key_exchange(secret, public).map(R::alloc_key) + KeyAlg::Aes(AesTypes::A128Gcm) => { + AesKey::::from_key_exchange(secret, public).map(R::alloc_key) + } + #[cfg(feature = "aes")] + KeyAlg::Aes(AesTypes::A256Gcm) => { + AesKey::::from_key_exchange(secret, public).map(R::alloc_key) + } + #[cfg(feature = "aes")] + KeyAlg::Aes(AesTypes::A128CbcHs256) => { + AesKey::::from_key_exchange(secret, public).map(R::alloc_key) } #[cfg(feature = "aes")] - KeyAlg::Aes(AesTypes::A256GCM) => { - AesGcmKey::::from_key_exchange(secret, public).map(R::alloc_key) + KeyAlg::Aes(AesTypes::A256CbcHs512) => { + AesKey::::from_key_exchange(secret, public).map(R::alloc_key) } #[cfg(feature = "chacha")] KeyAlg::Chacha20(Chacha20Types::C20P) => { @@ -406,12 +434,20 @@ fn from_key_derivation_any( ) -> Result { match alg { #[cfg(feature = "aes")] - KeyAlg::Aes(AesTypes::A128GCM) => { - AesGcmKey::::from_key_derivation(derive).map(R::alloc_key) + KeyAlg::Aes(AesTypes::A128Gcm) => { + AesKey::::from_key_derivation(derive).map(R::alloc_key) } #[cfg(feature = "aes")] - KeyAlg::Aes(AesTypes::A256GCM) => { - AesGcmKey::::from_key_derivation(derive).map(R::alloc_key) + KeyAlg::Aes(AesTypes::A256Gcm) => { + AesKey::::from_key_derivation(derive).map(R::alloc_key) + } + #[cfg(feature = "aes")] + KeyAlg::Aes(AesTypes::A128CbcHs256) => { + AesKey::::from_key_derivation(derive).map(R::alloc_key) + } + #[cfg(feature = "aes")] + KeyAlg::Aes(AesTypes::A256CbcHs512) => { + AesKey::::from_key_derivation(derive).map(R::alloc_key) } #[cfg(feature = "chacha")] KeyAlg::Chacha20(Chacha20Types::C20P) => { @@ -520,22 +556,35 @@ macro_rules! match_key_alg { }}; (@ ; $key:ident, $alg:ident) => {()}; (@ Aes $($rest:ident)*; $key:ident, $alg:ident) => {{ - #[cfg(feature = "aes")] if $alg == KeyAlg::Aes(AesTypes::A128GCM) { - return Ok($key.assume::>()); + #[cfg(feature = "aes")] + if $alg == KeyAlg::Aes(AesTypes::A128Gcm) { + return Ok($key.assume::>()); } - #[cfg(feature = "aes")] if $alg == KeyAlg::Aes(AesTypes::A256GCM) { - return Ok($key.assume::>()); + #[cfg(feature = "aes")] + if $alg == KeyAlg::Aes(AesTypes::A256Gcm) { + return Ok($key.assume::>()); + } + #[cfg(feature = "aes")] + if $alg == KeyAlg::Aes(AesTypes::A128CbcHs256) { + return Ok($key.assume::>()); + } + #[cfg(feature = "aes")] + if $alg == KeyAlg::Aes(AesTypes::A256CbcHs512) { + return Ok($key.assume::>()); } match_key_alg!(@ $($rest)*; $key, $alg) }}; (@ Bls $($rest:ident)*; $key:ident, $alg:ident) => {{ - #[cfg(feature = "bls")] if $alg == KeyAlg::Bls12_381(BlsCurves::G1) { + #[cfg(feature = "bls")] + if $alg == KeyAlg::Bls12_381(BlsCurves::G1) { return Ok($key.assume::>()); } - #[cfg(feature = "bls")] if $alg == KeyAlg::Bls12_381(BlsCurves::G2) { + #[cfg(feature = "bls")] + if $alg == KeyAlg::Bls12_381(BlsCurves::G2) { return Ok($key.assume::>()); } - #[cfg(feature = "bls")] if $alg == KeyAlg::Bls12_381(BlsCurves::G1G2) { + #[cfg(feature = "bls")] + if $alg == KeyAlg::Bls12_381(BlsCurves::G1G2) { return Ok($key.assume::>()); } match_key_alg!(@ $($rest)*; $key, $alg) @@ -794,7 +843,7 @@ mod tests { assert_eq!(exch_a, exch_b); let _aes_key = - Box::::from_key_exchange(KeyAlg::Aes(AesTypes::A256GCM), &*alice, &*bob) + Box::::from_key_exchange(KeyAlg::Aes(AesTypes::A256Gcm), &*alice, &*bob) .unwrap(); } diff --git a/askar-crypto/src/alg/mod.rs b/askar-crypto/src/alg/mod.rs index 54aa7a74..d7724892 100644 --- a/askar-crypto/src/alg/mod.rs +++ b/askar-crypto/src/alg/mod.rs @@ -20,7 +20,7 @@ pub use any::{AnyKey, AnyKeyCreate}; #[cfg(feature = "aes")] #[cfg_attr(docsrs, doc(cfg(feature = "aes")))] -pub mod aesgcm; +pub mod aes; #[cfg(feature = "bls")] #[cfg_attr(docsrs, doc(cfg(feature = "bls")))] @@ -66,8 +66,10 @@ impl KeyAlg { /// Get a reference to a string representing the `KeyAlg` pub fn as_str(&self) -> &'static str { match self { - Self::Aes(AesTypes::A128GCM) => "a128gcm", - Self::Aes(AesTypes::A256GCM) => "a256gcm", + Self::Aes(AesTypes::A128Gcm) => "a128gcm", + Self::Aes(AesTypes::A256Gcm) => "a256gcm", + Self::Aes(AesTypes::A128CbcHs256) => "a128cbchs256", + Self::Aes(AesTypes::A256CbcHs512) => "a256cbchs512", Self::Bls12_381(BlsCurves::G1) => "bls12381g1", Self::Bls12_381(BlsCurves::G2) => "bls12381g2", Self::Bls12_381(BlsCurves::G1G2) => "bls12381g1g2", @@ -92,8 +94,14 @@ impl FromStr for KeyAlg { fn from_str(s: &str) -> Result { match normalize_alg(s)? { - a if a == "a128gcm" || a == "aes128gcm" => Ok(Self::Aes(AesTypes::A128GCM)), - a if a == "a256gcm" || a == "aes256gcm" => Ok(Self::Aes(AesTypes::A256GCM)), + a if a == "a128gcm" || a == "aes128gcm" => Ok(Self::Aes(AesTypes::A128Gcm)), + a if a == "a256gcm" || a == "aes256gcm" => Ok(Self::Aes(AesTypes::A256Gcm)), + a if a == "a128cbchs256" || a == "aes128cbchs256" => { + Ok(Self::Aes(AesTypes::A128CbcHs256)) + } + a if a == "a256cbchs512" || a == "aes256cbchs512" => { + Ok(Self::Aes(AesTypes::A256CbcHs512)) + } a if a == "bls12381g1" => Ok(Self::Bls12_381(BlsCurves::G1)), a if a == "bls12381g2" => Ok(Self::Bls12_381(BlsCurves::G2)), a if a == "bls12381g1g2" => Ok(Self::Bls12_381(BlsCurves::G1G2)), @@ -184,10 +192,14 @@ impl Display for KeyAlg { /// Supported algorithms for AES #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Zeroize)] pub enum AesTypes { - /// AES 128-bit GCM - A128GCM, - /// AES 256-bit GCM - A256GCM, + /// 128-bit AES-GCM + A128Gcm, + /// 256-bit AES-GCM + A256Gcm, + /// 128-bit AES-CBC with HMAC-256 + A128CbcHs256, + /// 256-bit AES-CBC with HMAC-512 + A256CbcHs512, } /// Supported public key types for Bls12_381 diff --git a/askar-crypto/src/buffer/array.rs b/askar-crypto/src/buffer/array.rs index eea83066..697be8c7 100644 --- a/askar-crypto/src/buffer/array.rs +++ b/askar-crypto/src/buffer/array.rs @@ -6,6 +6,7 @@ use core::{ use crate::generic_array::{ArrayLength, GenericArray}; use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; +use subtle::{Choice, ConstantTimeEq}; use zeroize::Zeroize; use super::HexRepr; @@ -127,10 +128,16 @@ impl> Debug for ArrayKey { } } +impl> ConstantTimeEq for ArrayKey { + fn ct_eq(&self, other: &Self) -> Choice { + ConstantTimeEq::ct_eq(self.0.as_ref(), other.0.as_ref()) + } +} + impl> PartialEq for ArrayKey { + #[inline] fn eq(&self, other: &Self) -> bool { - // FIXME implement constant-time equality? - self.as_ref() == other.as_ref() + self.ct_eq(other).unwrap_u8() == 1 } } impl> Eq for ArrayKey {} diff --git a/wrappers/python/aries_askar/types.py b/wrappers/python/aries_askar/types.py index 37d97d43..095fc679 100644 --- a/wrappers/python/aries_askar/types.py +++ b/wrappers/python/aries_askar/types.py @@ -57,8 +57,10 @@ def __repr__(self) -> str: class KeyAlg(Enum): - AES128GCM = "a128gcm" - AES256GCM = "a256gcm" + A128GCM = "a128gcm" + A256GCM = "a256gcm" + A128CBC_HS256 = "a128cbchs256" + A256CBC_HS512 = "a256cbchs512" BLS12_381_G1 = "bls12381g1" BLS12_381_G2 = "bls12381g2" BLS12_381_G1G2 = "bls12381g1g2" diff --git a/wrappers/python/demo/test.py b/wrappers/python/demo/test.py index 710ff81c..4707c560 100644 --- a/wrappers/python/demo/test.py +++ b/wrappers/python/demo/test.py @@ -59,7 +59,7 @@ def keys_test(): jwk = key.get_jwk_public() log("JWK:", jwk) - key = Key.generate(KeyAlg.AES128GCM) + key = Key.generate(KeyAlg.A128GCM) log("Key algorithm:", key.algorithm) data = b"test message" From 6c40419b2d622f3a0cebc65531030d8fa051e599 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Fri, 7 May 2021 15:32:36 -0700 Subject: [PATCH 112/116] update ecdh-1pu to draft 4 Signed-off-by: Andrew Whitehead --- askar-crypto/src/kdf/ecdh_1pu.rs | 87 +++++++++++++++++++++++-- src/ffi/key.rs | 19 +++++- src/kms/envelope.rs | 2 + wrappers/python/aries_askar/bindings.py | 2 + wrappers/python/aries_askar/key.py | 9 ++- 5 files changed, 111 insertions(+), 8 deletions(-) diff --git a/askar-crypto/src/kdf/ecdh_1pu.rs b/askar-crypto/src/kdf/ecdh_1pu.rs index eeeaa217..5286097b 100644 --- a/askar-crypto/src/kdf/ecdh_1pu.rs +++ b/askar-crypto/src/kdf/ecdh_1pu.rs @@ -7,7 +7,10 @@ use super::{ concat::{ConcatKDFHash, ConcatKDFParams}, KeyDerivation, KeyExchange, }; -use crate::error::Error; +use crate::{ + buffer::{WriteBuffer, Writer}, + error::Error, +}; /// An instantiation of the ECDH-1PU key derivation #[derive(Debug)] @@ -18,6 +21,7 @@ pub struct Ecdh1PU<'d, Key: KeyExchange + ?Sized> { alg: &'d [u8], apu: &'d [u8], apv: &'d [u8], + cc_tag: &'d [u8], } impl<'d, Key: KeyExchange + ?Sized> Ecdh1PU<'d, Key> { @@ -29,6 +33,7 @@ impl<'d, Key: KeyExchange + ?Sized> Ecdh1PU<'d, Key> { alg: &'d [u8], apu: &'d [u8], apv: &'d [u8], + cc_tag: &'d [u8], ) -> Self { Self { ephem_key, @@ -37,6 +42,7 @@ impl<'d, Key: KeyExchange + ?Sized> Ecdh1PU<'d, Key> { alg, apu, apv, + cc_tag, } } } @@ -48,6 +54,9 @@ impl KeyDerivation for Ecdh1PU<'_, Key> { if output_len > 32 { return Err(err_msg!(Unsupported, "Exceeded maximum output length")); } + if self.cc_tag.len() > 128 { + return Err(err_msg!(Unsupported, "Exceeded maximum length for cc_tag")); + } let mut kdf = ConcatKDFHash::::new(); kdf.start_pass(); @@ -56,11 +65,22 @@ impl KeyDerivation for Ecdh1PU<'_, Key> { .write_key_exchange(self.recip_key, &mut kdf)?; self.send_key.write_key_exchange(self.recip_key, &mut kdf)?; + // the authentication tag is appended to pub_info, if any. + let mut pub_info = [0u8; 132]; + let mut pub_w = Writer::from_slice(&mut pub_info[..]); + pub_w.buffer_write(&((output_len as u32) * 8).to_be_bytes())?; // output length in bits + if !self.cc_tag.is_empty() { + pub_w.buffer_write(&(self.cc_tag.len() as u32).to_be_bytes())?; + pub_w.buffer_write(&self.cc_tag)?; + } + #[cfg(test)] + println!("w: {:?}", pub_w.as_ref()); + kdf.hash_params(ConcatKDFParams { alg: self.alg, apu: self.apu, apv: self.apv, - pub_info: &((output_len as u32) * 8).to_be_bytes(), // output length in bits + pub_info: pub_w.as_ref(), prv_info: &[], }); @@ -111,13 +131,70 @@ mod tests { let mut key_output = [0u8; 32]; - Ecdh1PU::new(&ephem_sk, &alice_sk, &bob_sk, b"A256GCM", b"Alice", b"Bob") - .derive_key_bytes(&mut key_output) - .unwrap(); + Ecdh1PU::new( + &ephem_sk, + &alice_sk, + &bob_sk, + b"A256GCM", + b"Alice", + b"Bob", + &[], + ) + .derive_key_bytes(&mut key_output) + .unwrap(); assert_eq!( key_output, hex!("6caf13723d14850ad4b42cd6dde935bffd2fff00a9ba70de05c203a5e1722ca7") ); } + + #[cfg(feature = "ed25519")] + #[test] + // from RFC: https://tools.ietf.org/html/draft-madden-jose-ecdh-1pu-04#appendix-B + fn expected_1pu_wrapped_output() { + use crate::alg::x25519::X25519KeyPair; + use crate::jwk::FromJwk; + + let alice_sk = X25519KeyPair::from_jwk( + r#"{"kty": "OKP", + "crv": "X25519", + "x": "Knbm_BcdQr7WIoz-uqit9M0wbcfEr6y-9UfIZ8QnBD4", + "d": "i9KuFhSzEBsiv3PKVL5115OCdsqQai5nj_Flzfkw5jU"}"#, + ) + .unwrap(); + let bob_sk = X25519KeyPair::from_jwk( + r#"{"kty": "OKP", + "crv": "X25519", + "x": "BT7aR0ItXfeDAldeeOlXL_wXqp-j5FltT0vRSG16kRw", + "d": "1gDirl_r_Y3-qUa3WXHgEXrrEHngWThU3c9zj9A2uBg"}"#, + ) + .unwrap(); + let ephem_sk = X25519KeyPair::from_jwk( + r#"{"kty": "OKP", + "crv": "X25519", + "x": "k9of_cpAajy0poW5gaixXGs9nHkwg1AFqUAFa39dyBc", + "d": "x8EVZH4Fwk673_mUujnliJoSrLz0zYzzCWp5GUX2fc8"}"#, + ) + .unwrap(); + + let mut key_output = [0u8; 16]; + + Ecdh1PU::new( + &ephem_sk, + &alice_sk, + &bob_sk, + b"ECDH-1PU+A128KW", + b"Alice", + b"Bob and Charlie", + &hex!( + "1cb6f87d3966f2ca469a28f74723acda + 02780e91cce21855470745fe119bdd64" + ), + ) + .derive_key_bytes(&mut key_output) + .unwrap(); + + assert_eq!(key_output, hex!("df4c37a0668306a11e3d6b0074b5d8df")); + } } diff --git a/src/ffi/key.rs b/src/ffi/key.rs index 702548e0..c847c697 100644 --- a/src/ffi/key.rs +++ b/src/ffi/key.rs @@ -445,7 +445,13 @@ pub extern "C" fn askar_key_derive_ecdh_es( check_useful_c_ptr!(out); let ephem_key = ephem_key.load()?; let recip_key = recip_key.load()?; - let key = derive_key_ecdh_es(&ephem_key, &recip_key, alg.as_str(), apu.as_slice(), apv.as_slice())?; + let key = derive_key_ecdh_es( + &ephem_key, + &recip_key, + alg.as_str(), + apu.as_slice(), + apv.as_slice(), + )?; unsafe { *out = LocalKeyHandle::create(key) }; Ok(ErrorCode::Success) } @@ -459,6 +465,7 @@ pub extern "C" fn askar_key_derive_ecdh_1pu( recip_key: LocalKeyHandle, apu: ByteBuffer, apv: ByteBuffer, + cc_tag: ByteBuffer, out: *mut LocalKeyHandle, ) -> ErrorCode { catch_err! { @@ -467,7 +474,15 @@ pub extern "C" fn askar_key_derive_ecdh_1pu( let ephem_key = ephem_key.load()?; let sender_key = sender_key.load()?; let recip_key = recip_key.load()?; - let key = derive_key_ecdh_1pu(&ephem_key, &sender_key, &recip_key, alg.as_str(), apu.as_slice(), apv.as_slice())?; + let key = derive_key_ecdh_1pu( + &ephem_key, + &sender_key, + &recip_key, + alg.as_str(), + apu.as_slice(), + apv.as_slice(), + cc_tag.as_slice(), + )?; unsafe { *out = LocalKeyHandle::create(key) }; Ok(ErrorCode::Success) } diff --git a/src/kms/envelope.rs b/src/kms/envelope.rs index e397813d..b0401920 100644 --- a/src/kms/envelope.rs +++ b/src/kms/envelope.rs @@ -84,6 +84,7 @@ pub fn derive_key_ecdh_1pu( alg: &str, apu: &[u8], apv: &[u8], + cc_tag: &[u8], ) -> Result { let key_alg = KeyAlg::from_str(alg)?; let derive = Ecdh1PU::new( @@ -93,6 +94,7 @@ pub fn derive_key_ecdh_1pu( alg.as_bytes(), apu, apv, + cc_tag, ); LocalKey::from_key_derivation(key_alg, derive) } diff --git a/wrappers/python/aries_askar/bindings.py b/wrappers/python/aries_askar/bindings.py index 4926a716..db1e22c5 100644 --- a/wrappers/python/aries_askar/bindings.py +++ b/wrappers/python/aries_askar/bindings.py @@ -1199,6 +1199,7 @@ def key_derive_ecdh_1pu( recip_key: LocalKeyHandle, apu: Union[bytes, str, ByteBuffer], apv: Union[bytes, str, ByteBuffer], + cc_tag: Optional[Union[bytes, ByteBuffer]], ) -> LocalKeyHandle: key = LocalKeyHandle() if isinstance(alg, KeyAlg): @@ -1211,6 +1212,7 @@ def key_derive_ecdh_1pu( recip_key, encode_bytes(apu), encode_bytes(apv), + encode_bytes(cc_tag), byref(key), ) return key diff --git a/wrappers/python/aries_askar/key.py b/wrappers/python/aries_askar/key.py index 94bdbd07..59a01a31 100644 --- a/wrappers/python/aries_askar/key.py +++ b/wrappers/python/aries_askar/key.py @@ -145,10 +145,17 @@ def derive_key_ecdh_1pu( recip_key: Key, apu: Union[bytes, str], apv: Union[bytes, str], + cc_tag: bytes = None, ) -> Key: return Key( bindings.key_derive_ecdh_1pu( - alg, ephem_key._handle, sender_key._handle, recip_key._handle, apu, apv + alg, + ephem_key._handle, + sender_key._handle, + recip_key._handle, + apu, + apv, + cc_tag, ) ) From 4938b3cb423252695473b00b42e8d7ab33d85224 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Fri, 7 May 2021 16:19:09 -0700 Subject: [PATCH 113/116] misc cleanups Signed-off-by: Andrew Whitehead --- askar-crypto/src/alg/aes.rs | 4 +--- askar-crypto/src/alg/ed25519.rs | 1 - askar-crypto/src/alg/k256.rs | 1 - askar-crypto/src/alg/p256.rs | 1 - askar-crypto/src/alg/x25519.rs | 1 - askar-crypto/src/encrypt/mod.rs | 2 +- src/backend/any.rs | 1 - src/kms/key.rs | 1 - 8 files changed, 2 insertions(+), 10 deletions(-) diff --git a/askar-crypto/src/alg/aes.rs b/askar-crypto/src/alg/aes.rs index 0ca180f7..a53db408 100644 --- a/askar-crypto/src/alg/aes.rs +++ b/askar-crypto/src/alg/aes.rs @@ -377,8 +377,6 @@ where nonce: &GenericArray, aad: &[u8], ) -> Result<(), Error> { - // FIXME validate maximum input length - // this should be optimized unless it matters if Self::TagSize::USIZE > D::OutputSize::USIZE { return Err(err_msg!( @@ -482,7 +480,7 @@ mod tests { assert_eq!(&dec[..], input); // test tag validation - buffer.as_mut()[enc_len - 1] += 1; + buffer.as_mut()[enc_len - 1] = buffer.as_mut()[enc_len - 1].wrapping_add(1); assert!(key.decrypt_in_place(&mut buffer, &nonce, &[]).is_err()); } test_encrypt::(); diff --git a/askar-crypto/src/alg/ed25519.rs b/askar-crypto/src/alg/ed25519.rs index e9c97a22..d3207c60 100644 --- a/askar-crypto/src/alg/ed25519.rs +++ b/askar-crypto/src/alg/ed25519.rs @@ -173,7 +173,6 @@ impl KeypairBytes for Ed25519KeyPair { let sk = SecretKey::from_bytes(&kp[..SECRET_KEY_LENGTH]).unwrap(); let pk = PublicKey::from_bytes(&kp[SECRET_KEY_LENGTH..]) .map_err(|_| err_msg!(InvalidKeyData))?; - // FIXME: derive pk from sk and check value? Ok(Self { secret: Some(sk), diff --git a/askar-crypto/src/alg/k256.rs b/askar-crypto/src/alg/k256.rs index 57e1e9e4..4a326779 100644 --- a/askar-crypto/src/alg/k256.rs +++ b/askar-crypto/src/alg/k256.rs @@ -132,7 +132,6 @@ impl KeypairBytes for K256KeyPair { let pk = EncodedPoint::from_bytes(&kp[SECRET_KEY_LENGTH..]) .and_then(|pt| pt.decode()) .map_err(|_| err_msg!(InvalidKeyData))?; - // FIXME: derive pk from sk and check value? Ok(Self { secret: Some(sk), diff --git a/askar-crypto/src/alg/p256.rs b/askar-crypto/src/alg/p256.rs index cdc9e108..b92addf1 100644 --- a/askar-crypto/src/alg/p256.rs +++ b/askar-crypto/src/alg/p256.rs @@ -132,7 +132,6 @@ impl KeypairBytes for P256KeyPair { let pk = EncodedPoint::from_bytes(&kp[SECRET_KEY_LENGTH..]) .and_then(|pt| pt.decode()) .map_err(|_| err_msg!(InvalidKeyData))?; - // FIXME: derive pk from sk and check value? Ok(Self { secret: Some(sk), diff --git a/askar-crypto/src/alg/x25519.rs b/askar-crypto/src/alg/x25519.rs index a3c78dc1..2f600489 100644 --- a/askar-crypto/src/alg/x25519.rs +++ b/askar-crypto/src/alg/x25519.rs @@ -143,7 +143,6 @@ impl KeypairBytes for X25519KeyPair { let pk = PublicKey::from( TryInto::<[u8; PUBLIC_KEY_LENGTH]>::try_into(&kp[SECRET_KEY_LENGTH..]).unwrap(), ); - // FIXME: derive pk from sk and check value? Ok(Self { secret: Some(sk), diff --git a/askar-crypto/src/encrypt/mod.rs b/askar-crypto/src/encrypt/mod.rs index e58e24f5..9e70c2b6 100644 --- a/askar-crypto/src/encrypt/mod.rs +++ b/askar-crypto/src/encrypt/mod.rs @@ -7,7 +7,7 @@ use crate::{ random::fill_random, }; -#[cfg(feature = "crypto_box")] // FIXME - support non-alloc? +#[cfg(feature = "crypto_box")] #[cfg_attr(docsrs, doc(cfg(feature = "crypto_box")))] pub mod crypto_box; diff --git a/src/backend/any.rs b/src/backend/any.rs index bcd540be..4c440e79 100644 --- a/src/backend/any.rs +++ b/src/backend/any.rs @@ -91,7 +91,6 @@ impl Backend for AnyBackend { #[cfg(feature = "sqlite")] Self::Sqlite(store) => { - // FIXME - avoid double boxed futures by exposing public method let session = store.session(profile, transaction)?; Ok(AnyQueryBackend::SqliteSession(session)) } diff --git a/src/kms/key.rs b/src/kms/key.rs index 9b594958..580b703b 100644 --- a/src/kms/key.rs +++ b/src/kms/key.rs @@ -35,7 +35,6 @@ impl LocalKey { /// Create a new deterministic key or keypair pub fn from_seed(alg: KeyAlg, seed: &[u8], _method: Option<&str>) -> Result { - // FIXME - method is just a placeholder let inner = Box::::from_seed(alg, seed.into())?; Ok(Self { inner, From 4aa370120b6ac334e6df55c4f96c72833ced076f Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Fri, 7 May 2021 16:19:25 -0700 Subject: [PATCH 114/116] ConstantTimeEq for SecretBytes Signed-off-by: Andrew Whitehead --- askar-crypto/src/buffer/secret.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/askar-crypto/src/buffer/secret.rs b/askar-crypto/src/buffer/secret.rs index d4a8f63a..8abc4b95 100644 --- a/askar-crypto/src/buffer/secret.rs +++ b/askar-crypto/src/buffer/secret.rs @@ -6,13 +6,14 @@ use core::{ }; use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; +use subtle::{Choice, ConstantTimeEq}; use zeroize::Zeroize; use super::{string::MaybeStr, HexRepr, ResizeBuffer, WriteBuffer}; use crate::error::Error; /// A heap-allocated, zeroized byte buffer -#[derive(Clone, Default, Hash, PartialEq, Eq, PartialOrd, Ord, Zeroize)] +#[derive(Clone, Default, Hash, Zeroize)] pub struct SecretBytes(Vec); impl SecretBytes { @@ -174,6 +175,20 @@ impl Drop for SecretBytes { } } +impl ConstantTimeEq for SecretBytes { + fn ct_eq(&self, other: &Self) -> Choice { + ConstantTimeEq::ct_eq(self.0.as_slice(), other.0.as_slice()) + } +} + +impl PartialEq for SecretBytes { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.ct_eq(other).unwrap_u8() == 1 + } +} +impl Eq for SecretBytes {} + impl From<&[u8]> for SecretBytes { fn from(inner: &[u8]) -> Self { Self(inner.to_vec()) From 14b98ced82fbddc88cf4e6bfec95756d3cba6c32 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Fri, 7 May 2021 16:25:02 -0700 Subject: [PATCH 115/116] update version Signed-off-by: Andrew Whitehead --- Cargo.toml | 4 ++-- askar-crypto/Cargo.toml | 2 +- wrappers/python/aries_askar/version.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bd109176..4c81bddf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = ["askar-crypto"] [package] name = "aries-askar" -version = "0.2.0-pre.1" +version = "0.2.0-pre.2" authors = ["Hyperledger Aries Contributors "] edition = "2018" description = "Hyperledger Aries Askar secure storage" @@ -38,7 +38,7 @@ pg_test = ["postgres"] hex-literal = "0.3" [dependencies] -askar-crypto = { version = "0.2.0-pre.1", path = "./askar-crypto", features = ["argon2", "std"] } +askar-crypto = { version = "0.2.0-pre.2", path = "./askar-crypto", features = ["argon2", "std"] } async-mutex = "1.4" async-stream = "0.3" bs58 = "0.4" diff --git a/askar-crypto/Cargo.toml b/askar-crypto/Cargo.toml index 23ae1a2e..ad82df92 100644 --- a/askar-crypto/Cargo.toml +++ b/askar-crypto/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "askar-crypto" -version = "0.2.0-pre.1" +version = "0.2.0-pre.2" authors = ["Hyperledger Aries Contributors "] edition = "2018" description = "Hyperledger Aries Askar cryptography" diff --git a/wrappers/python/aries_askar/version.py b/wrappers/python/aries_askar/version.py index 66873115..59cff6ae 100644 --- a/wrappers/python/aries_askar/version.py +++ b/wrappers/python/aries_askar/version.py @@ -1,3 +1,3 @@ """aries_askar library wrapper version.""" -__version__ = "0.2.0-pre.1" +__version__ = "0.2.0-pre.2" From 5804a5eac97a60d037405099e423f6b4ccb0dc8f Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Fri, 7 May 2021 16:33:45 -0700 Subject: [PATCH 116/116] fix links; update ECDH-1PU draft version Signed-off-by: Andrew Whitehead --- askar-crypto/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/askar-crypto/README.md b/askar-crypto/README.md index deb36829..686b5ece 100644 --- a/askar-crypto/README.md +++ b/askar-crypto/README.md @@ -1,7 +1,7 @@ # askar-crypto [![Rust Crate](https://img.shields.io/crates/v/askar-crypto.svg)](https://crates.io/crates/askar-crypto) -[![Rust Documentation](https://docs.rs/aries-askar/badge.svg)](https://docs.rs/aries-askar) +[![Rust Documentation](https://docs.rs/askar-crypto/badge.svg)](https://docs.rs/askar-crypto) The `askar-crypto` crate provides the basic key representations and cryptographic operations used by [`aries-askar`](https://github.com/hyperledger/aries-askar). @@ -24,7 +24,7 @@ The `any_key` feature (which depends on `alloc`) provides a generic interface fo ## JOSE Authenticated Encryption -This crate provides implementations of the [ECDH-ES](https://tools.ietf.org/html/rfc7518#section-4.6) and [ECDH-1PU (draft 3)](https://tools.ietf.org/html/draft-madden-jose-ecdh-1pu-03) key agreement operations, for use in deriving the CEK or key wrapping key when producing or consuming JWE envelopes using these protection algorithms. +This crate provides implementations of the [ECDH-ES](https://tools.ietf.org/html/rfc7518#section-4.6) and [ECDH-1PU (draft 4)](https://tools.ietf.org/html/draft-madden-jose-ecdh-1pu-04) key agreement operations, for use in deriving the CEK or key wrapping key when producing or consuming JWE envelopes using these protection algorithms. ## no-std