From 27e9b1cc0ab360de4b6477672974ac5a0f7a2068 Mon Sep 17 00:00:00 2001 From: Nicolas Stalder Date: Fri, 11 Jun 2021 12:43:59 +0200 Subject: [PATCH] Trial miracl-trussed wrapping --- .github/workflows/ci.yml | 6 +- Cargo.toml | 10 ++ bacon.toml | 5 + src/client/mechanisms.rs | 52 +++++++ src/config.rs | 3 +- src/key.rs | 37 ++++- src/mechanisms.rs | 26 ++++ src/mechanisms/p384.rs | 306 +++++++++++++++++++++++++++++++++++++++ src/service.rs | 7 + src/types.rs | 1 + tests/p384.rs | 35 +++++ 11 files changed, 480 insertions(+), 8 deletions(-) create mode 100644 src/mechanisms/p384.rs create mode 100644 tests/p384.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5540e53e033..9da069c5f2a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,6 +39,10 @@ jobs: run: cargo build --verbose --target ${{ matrix.target }} - name: Run tests - run: cargo test --verbose + run: cargo test + if: matrix.target == 'x86_64-unknown-linux-gnu' + + - name: Run tests + run: cargo test --features p384 if: matrix.target == 'x86_64-unknown-linux-gnu' diff --git a/Cargo.toml b/Cargo.toml index ce788e7bf4a..c46fe87e293 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,9 @@ hmac = "0.11" sha-1 = { version = "0.9", default-features = false, optional = true } sha2 = { version = "0.9", default-features = false } +# miracl +miracl32 = { version = "0.1.0-alpha.0", optional = true } + # ours cosey = "0.3" delog = "0.1.0" @@ -95,6 +98,13 @@ aes256-cbc = [] chacha8-poly1305 = [] ed255 = [] x255 = [] +ed448 = ["miracl32"] +x448 = ["miracl32"] +rsa2k = ["miracl32"] +rsa3k = ["miracl32"] +rsa4k = ["miracl32"] +p384 = ["miracl32"] +p521 = ["miracl32"] hmac-blake2s = ["blake2"] hmac-sha1 = [] hmac-sha256 = [] diff --git a/bacon.toml b/bacon.toml index aabaf5f3794..483e62c8847 100644 --- a/bacon.toml +++ b/bacon.toml @@ -11,6 +11,11 @@ default_job = "check" command = ["cargo", "check", "--color", "always"] need_stdout = false +[jobs.check-miracl] +# command = ["cargo", "check", "--color", "always", "--features", "clients-1"] +command = ["cargo", "check", "--color", "always", "--features", "ed448,x448,rsa2k,rsa3k,rsa4k,p384,p521"] +need_stdout = false + [jobs.check-cortex-m4] # command = ["cargo", "check", "--color", "always", "--features", "clients-1"] command = ["cargo", "check", "--color", "always", "--target", "thumbv7em-none-eabi"] diff --git a/src/client/mechanisms.rs b/src/client/mechanisms.rs index 3a5e8b2df2d..50683e61784 100644 --- a/src/client/mechanisms.rs +++ b/src/client/mechanisms.rs @@ -245,6 +245,58 @@ pub trait P256: CryptoClient { } } +#[cfg(feature = "p384")] +impl P384 for ClientImplementation {} + +pub trait P384: CryptoClient { + fn generate_p384_private_key(&mut self, persistence: Location) + -> ClientResult<'_, reply::GenerateKey, Self> + { + self.generate_key(Mechanism::P384, StorageAttributes::new().set_persistence(persistence)) + } + + fn derive_p384_public_key(&mut self, private_key: KeyId, persistence: Location) + -> ClientResult<'_, reply::DeriveKey, Self> + { + self.derive_key(Mechanism::P384, private_key, None, StorageAttributes::new().set_persistence(persistence)) + } + + fn deserialize_p384_key<'c>(&'c mut self, serialized_key: &[u8], format: KeySerialization, attributes: StorageAttributes) + -> ClientResult<'c, reply::DeserializeKey, Self> + { + self.deserialize_key(Mechanism::P384, serialized_key, format, attributes) + } + + fn serialize_p384_key(&mut self, key: KeyId, format: KeySerialization) + -> ClientResult<'_, reply::SerializeKey, Self> + { + self.serialize_key(Mechanism::P384, key, format) + } + + fn sign_p384<'c>(&'c mut self, key: KeyId, message: &[u8], format: SignatureSerialization) + -> ClientResult<'c, reply::Sign, Self> + { + self.sign(Mechanism::P384, key, message, format) + } + + fn verify_p384<'c>(&'c mut self, key: KeyId, message: &[u8], signature: &[u8]) + -> ClientResult<'c, reply::Verify, Self> + { + self.verify(Mechanism::P384, key, message, signature, SignatureSerialization::Raw) + } + + fn agree_p384(&mut self, private_key: KeyId, public_key: KeyId, persistence: Location) + -> ClientResult<'_, reply::Agree, Self> + { + self.agree( + Mechanism::P384, + private_key, + public_key, + StorageAttributes::new().set_persistence(persistence), + ) + } +} + #[cfg(feature = "sha256")] impl Sha256 for ClientImplementation {} diff --git a/src/config.rs b/src/config.rs index 06ae49c405a..14a51b74562 100644 --- a/src/config.rs +++ b/src/config.rs @@ -18,7 +18,8 @@ pub const MAX_KEY_MATERIAL_LENGTH: usize = 128; pub const MAX_SERIALIZED_KEY_LENGTH: usize = 132; pub type MAX_SERVICE_CLIENTS = consts::U5; pub const MAX_SHORT_DATA_LENGTH: usize = 128; -pub const MAX_SIGNATURE_LENGTH: usize = 72; +// 72 was for P256, raw P384 is 96 +pub const MAX_SIGNATURE_LENGTH: usize = 96; pub const MAX_USER_ATTRIBUTE_LENGTH: usize = 256; pub const USER_ATTRIBUTE_NUMBER: u8 = 37; diff --git a/src/key.rs b/src/key.rs index ce74889773d..cea52fd7c83 100644 --- a/src/key.rs +++ b/src/key.rs @@ -63,6 +63,13 @@ pub enum Kind { Ed255, P256, X255, + P384, + P521, + Rsa2k, + Rsa3k, + Rsa4k, + Ed448, + X448, } bitflags::bitflags! { @@ -136,17 +143,35 @@ impl Kind { Kind::Ed255 => 4, Kind::P256 => 5, Kind::X255 => 6, + // following PIV and our extensions + Kind::P384 => 0x14, + Kind::P521 => 0x15, + Kind::Rsa2k => 0x7, + Kind::Rsa3k => 0xE0, + Kind::Rsa4k => 0xE1, + Kind::Ed448 => 0xE4, + Kind::X448 => 0xE5, } } pub fn try_from(code: u16, length: usize) -> Result { + use Kind::*; Ok(match code { - 1 => Self::Shared(length), - 2 => Self::Symmetric(length), - 3 => Self::Symmetric32Nonce(length - 32), - 4 => Self::Ed255, - 5 => Self::P256, - 6 => Self::X255, + 1 => Shared(length), + 2 => Symmetric(length), + 3 => Symmetric32Nonce(length - 32), + 4 => Ed255, + 5 => P256, + 6 => X255, + + 0x14 => P384, + 0x15 => P521, + 0x7 => Rsa2k, + 0xE0 => Rsa3k, + 0xE1 => Rsa4k, + 0xE4 => Ed448, + 0xE5 => X448, + _ => return Err(Error::InvalidSerializedKey), }) } diff --git a/src/mechanisms.rs b/src/mechanisms.rs index f55392a4260..9d827f272df 100644 --- a/src/mechanisms.rs +++ b/src/mechanisms.rs @@ -11,6 +11,21 @@ // should be revisited. // TODO: rename to aes256-cbc-zero-iv + +#[allow(unused_macros)] +macro_rules! dummy_impl { + ($Mechanism:ident) => { + impl crate::service::Agree for $Mechanism {} + impl crate::service::DeriveKey for $Mechanism {} + impl crate::service::DeserializeKey for $Mechanism {} + impl crate::service::Exists for $Mechanism {} + impl crate::service::GenerateKey for $Mechanism {} + impl crate::service::SerializeKey for $Mechanism {} + impl crate::service::Sign for $Mechanism {} + impl crate::service::Verify for $Mechanism {} + } +} + pub struct Aes256Cbc {} mod aes256cbc; @@ -42,6 +57,17 @@ impl crate::service::DeriveKey for HmacSha512 {} #[cfg(not(feature = "hmac-sha512"))] impl crate::service::Sign for HmacSha512 {} +pub struct P384 {} +#[cfg(feature = "p384")] +mod p384; +#[cfg(not(feature = "p384"))] +dummy_impl!(P384); + + +pub struct Rsa2k {} +#[cfg(feature = "rsa2k")] +mod rsa2k; + pub struct P256 {} pub struct P256Prehashed {} mod p256; diff --git a/src/mechanisms/p384.rs b/src/mechanisms/p384.rs new file mode 100644 index 00000000000..73cc39ee194 --- /dev/null +++ b/src/mechanisms/p384.rs @@ -0,0 +1,306 @@ +use core::convert::TryInto; +use chacha20::ChaCha8Rng; +use rand_core::RngCore; + +use crate::{ + api::{request, reply}, + Error, + key, + service::{Agree, DeriveKey, DeserializeKey, Exists, GenerateKey, SerializeKey, Sign, Verify}, + store::keystore::Keystore, + types::{KeyId, KeySerialization, Message, Signature, SignatureSerialization}, +}; + +const SIZE: usize = 48; +type SecretKey = [u8; SIZE]; +type PublicKey = [u8; 2*SIZE + 1]; +type SharedSecret = [u8; SIZE]; + +#[inline(never)] +fn load_secret_key(keystore: &mut impl Keystore, key_id: &KeyId) -> Result +{ + + let secret_key: SecretKey = keystore + .load_key(key::Secrecy::Secret, Some(key::Kind::P384), &key_id)? + .material.as_slice() + .try_into() + .map_err(|_| Error::InternalError)?; + + Ok(secret_key) +} + +#[inline(never)] +fn load_public_key(keystore: &mut impl Keystore, key_id: &KeyId) -> Result +{ + let public_key: PublicKey = keystore + .load_key(key::Secrecy::Public, Some(key::Kind::P384), &key_id)? + .material.as_slice() + .try_into() + .map_err(|_| Error::InternalError)?; + + Ok(public_key) +} + + +#[cfg(feature = "p384")] +impl GenerateKey for super::P384 +{ + #[inline(never)] + fn generate_key(keystore: &mut impl Keystore, request: &request::GenerateKey) + -> Result + { + // secret key + // I dunno, Rust... in this day and age, no Default?! + let mut secret_key: SecretKey = [0u8; SIZE]; + keystore.rng().fill_bytes(&mut secret_key); + + // store keys + let key_id = keystore.store_key( + request.attributes.persistence, + key::Secrecy::Secret, + key::Info::from(key::Kind::P384).with_local_flag(), + &secret_key, + )?; + + // return handle + Ok(reply::GenerateKey { key: key_id }) + } +} + +#[cfg(feature = "p384")] +impl Agree for super::P384 +{ + #[inline(never)] + fn agree(keystore: &mut impl Keystore, request: &request::Agree) + -> Result + { + let private_id = request.private_key; + let public_id = request.public_key; + + let secret_key = load_secret_key(keystore, &private_id)?; + let public_key = load_public_key(keystore, &public_id)?; + + let mut shared_secret: SharedSecret = [0u8; SIZE]; + miracl32::nist384::ecdh::ecpsvdp_dh(&secret_key, &public_key, &mut shared_secret, 0); + + let key_id = keystore.store_key( + request.attributes.persistence, + key::Secrecy::Secret, key::Kind::Shared(SIZE), + &shared_secret)?; + + // return handle + Ok(reply::Agree { shared_secret: key_id }) + } +} + +#[cfg(feature = "p384")] +impl DeriveKey for super::P384 +{ + #[inline(never)] + fn derive_key(keystore: &mut impl Keystore, request: &request::DeriveKey) + -> Result + { + let base_id = request.base_key; + + // secret_key is not actually mutated as None is passed as rng. + let mut secret_key = load_secret_key(keystore, &base_id)?; + + // public key + let mut public_key: PublicKey = [0u8; 2*SIZE + 1]; + miracl32::nist384::ecdh::key_pair_generate(None::<&mut miracl32::rand::RAND_impl>, &mut secret_key, &mut public_key); + + let public_id = keystore.store_key( + request.attributes.persistence, + key::Secrecy::Public, key::Kind::P384, + &public_key)?; + + Ok(reply::DeriveKey { + key: public_id + }) + } +} + +#[cfg(feature = "p384")] +impl DeserializeKey for super::P384 +{ + #[inline(never)] + fn deserialize_key(keystore: &mut impl Keystore, request: &request::DeserializeKey) + -> Result + { + // deserialize to miracl format + let public_key = match request.format { + KeySerialization::Raw => { + if request.serialized_key.len() != 2*SIZE { + return Err(Error::InvalidSerializedKey); + } + + &request.serialized_key + } + + _ => { return Err(Error::FunctionNotSupported); } + }; + + // validate public key + if 0 != miracl32::nist384::ecdh::public_key_validate(public_key) { + return Err(Error::InvalidSerializedKey); + } + + // store it + let public_id = keystore.store_key( + request.attributes.persistence, + key::Secrecy::Public, key::Kind::P384, + &public_key)?; + + Ok(reply::DeserializeKey { key: public_id }) + } +} + +#[cfg(feature = "p384")] +impl SerializeKey for super::P384 +{ + #[inline(never)] + fn serialize_key(keystore: &mut impl Keystore, request: &request::SerializeKey) + -> Result + { + let key_id = request.key; + let public_key = load_public_key(keystore, &key_id)?; + + let serialized_key = match request.format { + KeySerialization::Raw => { + let mut serialized_key = Message::new(); + serialized_key.extend_from_slice(&public_key).unwrap(); + serialized_key + } + _ => return Err(Error::FunctionNotSupported), + }; + + Ok(reply::SerializeKey { serialized_key }) + } +} + +#[cfg(feature = "p384")] +impl Exists for super::P384 +{ + #[inline(never)] + fn exists(keystore: &mut impl Keystore, request: &request::Exists) + -> Result + { + let key_id = request.key; + let exists = keystore.exists_key(key::Secrecy::Secret, Some(key::Kind::P384), &key_id); + Ok(reply::Exists { exists }) + } +} + +struct MiraclRng<'rng>(&'rng mut ChaCha8Rng); + +impl miracl32::rand::RAND for MiraclRng<'_> { + // we're already "seeded" + fn seed(&mut self, _rawlen: usize, _raw: &[u8]) {} + + fn getbyte(&mut self) -> u8 { + let mut wrapped_byte = [0u8; 1]; + self.0.fill_bytes(&mut wrapped_byte); + wrapped_byte[0] + } +} + +#[cfg(feature = "p384")] +impl Sign for super::P384 +{ + #[inline(never)] + fn sign(keystore: &mut impl Keystore, request: &request::Sign) + -> Result + { + let key_id = request.key; + + let secret_key = load_secret_key(keystore, &key_id)?; + + let mut rng = MiraclRng(keystore.rng()); + + let sha = SIZE; + let mut c = [0u8; SIZE]; + let mut d = [0u8; SIZE]; + + // "IEEE ECDSA Signature, C and D are signature on F using private key S" + if miracl32::nist384::ecdh::ecpsp_dsa( + sha, + &mut rng, + // secret key + &secret_key, + // message + &request.message, + &mut c, + &mut d, + ) != 0 { + info!("signing with P384 key failed!"); + return Err(Error::InternalError); + } + + // debug_now!("making signature"); + let serialized_signature = match request.format { + SignatureSerialization::Asn1Der => { + // let mut buffer = [0u8; 72]; + // let l = signature.to_sec1_bytes(&mut buffer); + // Signature::from_slice(&buffer[..l]).unwrap() + return Err(Error::FunctionNotSupported); + } + SignatureSerialization::Raw => { + let mut sig: Signature = Default::default(); + sig.extend_from_slice(&c).unwrap(); + sig.extend_from_slice(&d).unwrap(); + sig + } + }; + + // return signature + Ok(reply::Sign { signature: serialized_signature }) + } + +} + +#[cfg(feature = "p384")] +impl Verify for super::P384 +{ + #[inline(never)] + fn verify(keystore: &mut impl Keystore, request: &request::Verify) + -> Result + { + let key_id = request.key; + + let public_key = load_public_key(keystore, &key_id)?; + + let (c, d) = match request.format { + SignatureSerialization::Raw => { + if request.signature.len() != 2*SIZE { + return Err(Error::InvalidSerializationFormat); + } + request.signature.split_at(SIZE) + } + SignatureSerialization::Asn1Der => return Err(Error::FunctionNotSupported), + }; + + let sha = SIZE; + // "IEEE1363 ECDSA Signature Verification. Signature C and D on F is verified using public key W" + let result = miracl32::nist384::ecdh::ecpvp_dsa( + sha, + &public_key, + &request.message, + c, + d, + ); + + let valid = result == 0; + Ok(reply::Verify { valid } ) + } +} + +#[cfg(not(feature = "p384"))] +impl Agree for super::P384 {} +#[cfg(not(feature = "p384"))] +impl DeriveKey for super::P384 {} +#[cfg(not(feature = "p384"))] +impl GenerateKey for super::P384 {} +#[cfg(not(feature = "p384"))] +impl Sign for super::P384 {} +#[cfg(not(feature = "p384"))] +impl Verify for super::P384 {} diff --git a/src/service.rs b/src/service.rs index 9e8ce27fedf..428c94f33b5 100644 --- a/src/service.rs +++ b/src/service.rs @@ -134,6 +134,7 @@ impl ServiceResources

{ match request.mechanism { Mechanism::P256 => mechanisms::P256::agree(keystore, request), + Mechanism::P384 => mechanisms::P384::agree(keystore, request), Mechanism::X255 => mechanisms::X255::agree(keystore, request), _ => Err(Error::MechanismNotAvailable), @@ -169,6 +170,7 @@ impl ServiceResources

{ Mechanism::HmacSha512 => mechanisms::HmacSha512::derive_key(keystore, request), Mechanism::Ed255 => mechanisms::Ed255::derive_key(keystore, request), Mechanism::P256 => mechanisms::P256::derive_key(keystore, request), + Mechanism::P384 => mechanisms::P384::derive_key(keystore, request), Mechanism::Sha256 => mechanisms::Sha256::derive_key(keystore, request), Mechanism::X255 => mechanisms::X255::derive_key(keystore, request), _ => Err(Error::MechanismNotAvailable), @@ -181,6 +183,7 @@ impl ServiceResources

{ Mechanism::Ed255 => mechanisms::Ed255::deserialize_key(keystore, request), Mechanism::P256 => mechanisms::P256::deserialize_key(keystore, request), + Mechanism::P384 => mechanisms::P384::deserialize_key(keystore, request), Mechanism::X255 => mechanisms::X255::deserialize_key(keystore, request), _ => Err(Error::MechanismNotAvailable), @@ -213,6 +216,7 @@ impl ServiceResources

{ Mechanism::Ed255 => mechanisms::Ed255::exists(keystore, request), Mechanism::P256 => mechanisms::P256::exists(keystore, request), + Mechanism::P384 => mechanisms::P384::exists(keystore, request), Mechanism::Totp => mechanisms::Totp::exists(keystore, request), Mechanism::X255 => mechanisms::X255::exists(keystore, request), _ => Err(Error::MechanismNotAvailable), @@ -225,6 +229,7 @@ impl ServiceResources

{ Mechanism::Chacha8Poly1305 => mechanisms::Chacha8Poly1305::generate_key(keystore, request), Mechanism::Ed255 => mechanisms::Ed255::generate_key(keystore, request), Mechanism::P256 => mechanisms::P256::generate_key(keystore, request), + Mechanism::P384 => mechanisms::P384::generate_key(keystore, request), Mechanism::X255 => mechanisms::X255::generate_key(keystore, request), _ => Err(Error::MechanismNotAvailable), }.map(Reply::GenerateKey) @@ -438,6 +443,7 @@ impl ServiceResources

{ Mechanism::HmacSha512 => mechanisms::HmacSha512::sign(keystore, request), Mechanism::P256 => mechanisms::P256::sign(keystore, request), Mechanism::P256Prehashed => mechanisms::P256Prehashed::sign(keystore, request), + Mechanism::P384 => mechanisms::P384::sign(keystore, request), Mechanism::Totp => mechanisms::Totp::sign(keystore, request), _ => Err(Error::MechanismNotAvailable), @@ -463,6 +469,7 @@ impl ServiceResources

{ Mechanism::Ed255 => mechanisms::Ed255::verify(keystore, request), Mechanism::P256 => mechanisms::P256::verify(keystore, request), + Mechanism::P384 => mechanisms::P384::verify(keystore, request), _ => Err(Error::MechanismNotAvailable), }.map(Reply::Verify) diff --git a/src/types.rs b/src/types.rs index 9159fb1441f..dd4af7ee8e1 100644 --- a/src/types.rs +++ b/src/types.rs @@ -504,6 +504,7 @@ pub enum Mechanism { // P256XSha256, P256, P256Prehashed, + P384, // clients can also do hashing by themselves Sha256, Tdes, diff --git a/tests/p384.rs b/tests/p384.rs new file mode 100644 index 00000000000..3eb7516c8a2 --- /dev/null +++ b/tests/p384.rs @@ -0,0 +1,35 @@ +#[allow(dead_code)] +mod client; + +#[cfg(feature = "p384")] +mod test { + use super::client; + use trussed::client::mechanisms::{HmacSha256, P384}; + use trussed::syscall; + + use trussed::types::{Location::*, SignatureSerialization}; + + + #[test] + fn p384_agree() { + client::get(|client| { + let sk1 = syscall!(client.generate_p384_private_key(Internal)).key; + let pk1 = syscall!(client.derive_p384_public_key(sk1, Volatile)).key; + let sk2 = syscall!(client.generate_p384_private_key(Internal)).key; + let pk2 = syscall!(client.derive_p384_public_key(sk2, Volatile)).key; + + let secret1 = syscall!(client.agree_p384(sk1, pk2, Volatile)).shared_secret; + let secret2 = syscall!(client.agree_p384(sk2, pk1, Volatile)).shared_secret; + + // Trussed® won't give out secrets, but lets us use them + let derivative1 = syscall!(client.sign_hmacsha256(secret1, &[])).signature; + let derivative2 = syscall!(client.sign_hmacsha256(secret2, &[])).signature; + assert_eq!(derivative1, derivative2); + + let msg = b"It's a miracle!"; + let signature = syscall!(client.sign_p384(sk1, msg, SignatureSerialization::Raw)).signature; + assert!(syscall!(client.verify_p384(pk1, msg, &signature)).valid); + assert!(!syscall!(client.verify_p384(pk2, msg, &signature)).valid); + }) + } +}