diff --git a/libraries/opensk/Cargo.toml b/libraries/opensk/Cargo.toml index 6052572..295bc0e 100644 --- a/libraries/opensk/Cargo.toml +++ b/libraries/opensk/Cargo.toml @@ -24,6 +24,8 @@ rand = { version = "0.8.4", optional = true } ed25519-compact = { version = "1", default-features = false, optional = true } p256 = { version = "0.13.0", features = ["ecdh"], optional = true } rand_core = { version = "0.6.4", optional = true } +sha2 = { version = "0.10.6", optional = true } +hmac = { version = "0.12.1", optional = true } [features] debug_ctap = [] @@ -32,7 +34,7 @@ with_ctap1 = ["crypto/with_ctap1"] vendor_hid = [] fuzz = ["arbitrary", "std"] ed25519 = ["ed25519-compact"] -rust_crypto = ["p256", "rand_core"] +rust_crypto = ["p256", "rand_core", "sha2", "hmac"] [dev-dependencies] enum-iterator = "0.6.0" diff --git a/libraries/opensk/src/api/crypto/hmac256.rs b/libraries/opensk/src/api/crypto/hmac256.rs new file mode 100644 index 0000000..bc3f5de --- /dev/null +++ b/libraries/opensk/src/api/crypto/hmac256.rs @@ -0,0 +1,37 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::{HASH_SIZE, HMAC_KEY_SIZE, TRUNCATED_HMAC_SIZE}; + +/// For a given hash function, computes and verifies the HMAC. +pub trait Hmac256 { + /// Computes the HMAC. + fn mac(key: &[u8; HMAC_KEY_SIZE], data: &[u8]) -> [u8; HASH_SIZE]; + + /// Verifies the HMAC. + /// + /// This function does best effort to not leak information about the key through side-channels + /// (e.g. usage of constant time comparison). + fn verify(key: &[u8; HMAC_KEY_SIZE], data: &[u8], mac: &[u8; HASH_SIZE]) -> bool; + + /// Verifies the first bytes of an HMAC. + /// + /// This function does best effort to not leak information about the key through side-channels + /// (e.g. usage of constant time comparison). + fn verify_truncated_left( + key: &[u8; HMAC_KEY_SIZE], + data: &[u8], + mac: &[u8; TRUNCATED_HMAC_SIZE], + ) -> bool; +} diff --git a/libraries/opensk/src/api/crypto/mod.rs b/libraries/opensk/src/api/crypto/mod.rs index b90a0b5..7479c8d 100644 --- a/libraries/opensk/src/api/crypto/mod.rs +++ b/libraries/opensk/src/api/crypto/mod.rs @@ -20,9 +20,13 @@ pub mod rust_crypto; pub mod software_crypto; #[cfg(feature = "rust_crypto")] pub use rust_crypto as software_crypto; +pub mod hmac256; +pub mod sha256; use self::ecdh::Ecdh; use self::ecdsa::Ecdsa; +use self::hmac256::Hmac256; +use self::sha256::Sha256; /// The size of field elements in the elliptic curve P256. pub const EC_FIELD_SIZE: usize = 32; @@ -30,8 +34,21 @@ pub const EC_FIELD_SIZE: usize = 32; /// The size of a serialized ECDSA signature. pub const EC_SIGNATURE_SIZE: usize = 2 * EC_FIELD_SIZE; +/// The size in bytes of a SHA256. +pub const HASH_SIZE: usize = 32; + +/// The size in bytes of an HMAC. +pub const HMAC_KEY_SIZE: usize = 32; + +/// The size in bytes of a truncated HMAC. +/// +/// Truncated HMACs are used in PIN protocol V1 in CTAP2. +pub const TRUNCATED_HMAC_SIZE: usize = 16; + /// Necessary cryptographic primitives for CTAP. pub trait Crypto { type Ecdh: Ecdh; type Ecdsa: Ecdsa; + type Sha256: Sha256; + type Hmac256: Hmac256; } diff --git a/libraries/opensk/src/api/crypto/rust_crypto.rs b/libraries/opensk/src/api/crypto/rust_crypto.rs index ef86f58..d43785e 100644 --- a/libraries/opensk/src/api/crypto/rust_crypto.rs +++ b/libraries/opensk/src/api/crypto/rust_crypto.rs @@ -20,8 +20,14 @@ //! //! If you want to use OpenSK outside of Tock v1, maybe this is useful for you though! -use crate::api::crypto::{ecdh, ecdsa, Crypto, EC_FIELD_SIZE, EC_SIGNATURE_SIZE}; +use crate::api::crypto::hmac256::Hmac256; +use crate::api::crypto::sha256::Sha256; +use crate::api::crypto::{ + ecdh, ecdsa, Crypto, EC_FIELD_SIZE, EC_SIGNATURE_SIZE, HASH_SIZE, HMAC_KEY_SIZE, + TRUNCATED_HMAC_SIZE, +}; use core::convert::TryFrom; +use hmac::Mac; use p256::ecdh::EphemeralSecret; use p256::ecdsa::signature::{SignatureEncoding, Signer, Verifier}; use p256::ecdsa::{SigningKey, VerifyingKey}; @@ -29,6 +35,7 @@ use p256::elliptic_curve::sec1::ToEncodedPoint; // TODO: implement CryptoRngCore for our Rng instead use rand_core::OsRng; use rng256::Rng256; +use sha2::Digest; pub struct SoftwareCrypto; pub struct SoftwareEcdh; @@ -37,6 +44,8 @@ pub struct SoftwareEcdsa; impl Crypto for SoftwareCrypto { type Ecdh = SoftwareEcdh; type Ecdsa = SoftwareEcdsa; + type Sha256 = SoftwareSha256; + type Hmac256 = SoftwareHmac256; } impl ecdh::Ecdh for SoftwareEcdh { @@ -188,6 +197,57 @@ impl ecdsa::Signature for SoftwareEcdsaSignature { } } +pub struct SoftwareSha256 { + hasher: sha2::Sha256, +} + +impl Sha256 for SoftwareSha256 { + fn digest(data: &[u8]) -> [u8; HASH_SIZE] { + sha2::Sha256::digest(data).into() + } + + fn new() -> Self { + let hasher = sha2::Sha256::new(); + Self { hasher } + } + + /// Digest the next part of the message to hash. + fn update(&mut self, data: &[u8]) { + self.hasher.update(data); + } + + /// Finalizes the hashing process, returns the hash value. + fn finalize(self) -> [u8; HASH_SIZE] { + self.hasher.finalize().into() + } +} + +pub struct SoftwareHmac256; + +impl Hmac256 for SoftwareHmac256 { + fn mac(key: &[u8; HMAC_KEY_SIZE], data: &[u8]) -> [u8; HASH_SIZE] { + let mut hmac = hmac::Hmac::::new_from_slice(key).unwrap(); + hmac.update(data); + hmac.finalize().into_bytes().into() + } + + fn verify(key: &[u8; HMAC_KEY_SIZE], data: &[u8], mac: &[u8; HASH_SIZE]) -> bool { + let mut hmac = hmac::Hmac::::new_from_slice(key).unwrap(); + hmac.update(data); + hmac.verify_slice(mac).is_ok() + } + + fn verify_truncated_left( + key: &[u8; HMAC_KEY_SIZE], + data: &[u8], + mac: &[u8; TRUNCATED_HMAC_SIZE], + ) -> bool { + let mut hmac = hmac::Hmac::::new_from_slice(key).unwrap(); + hmac.update(data); + hmac.verify_truncated_left(mac).is_ok() + } +} + #[cfg(test)] mod test { use super::*; @@ -246,4 +306,27 @@ mod test { second_key.to_slice(&mut new_bytes); assert_eq!(key_bytes, new_bytes); } + + #[test] + fn test_sha256_hash_matches() { + let data = [0x55; 16]; + let mut hasher = SoftwareSha256::new(); + hasher.update(&data); + assert_eq!(SoftwareSha256::digest(&data), hasher.finalize()); + } + + #[test] + fn test_hmac256_verifies() { + let key = [0xAA; HMAC_KEY_SIZE]; + let data = [0x55; 16]; + let mac = SoftwareHmac256::mac(&key, &data); + assert!(SoftwareHmac256::verify(&key, &data, &mac)); + let truncated_mac = + <&[u8; TRUNCATED_HMAC_SIZE]>::try_from(&mac[..TRUNCATED_HMAC_SIZE]).unwrap(); + assert!(SoftwareHmac256::verify_truncated_left( + &key, + &data, + &truncated_mac + )); + } } diff --git a/libraries/opensk/src/api/crypto/sha256.rs b/libraries/opensk/src/api/crypto/sha256.rs new file mode 100644 index 0000000..b38140e --- /dev/null +++ b/libraries/opensk/src/api/crypto/sha256.rs @@ -0,0 +1,34 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::HASH_SIZE; + +/// Hashes data using SHA256. +pub trait Sha256: Sized { + /// Computes the hash of a given message directly. + fn digest(data: &[u8]) -> [u8; HASH_SIZE] { + let mut hasher = Self::new(); + hasher.update(data); + hasher.finalize() + } + + /// Create a new object that can be incrementally updated for digesting. + fn new() -> Self; + + /// Digest the next part of the message to hash. + fn update(&mut self, data: &[u8]); + + /// Finalizes the hashing process, returns the hash value. + fn finalize(self) -> [u8; HASH_SIZE]; +} diff --git a/libraries/opensk/src/api/crypto/software_crypto.rs b/libraries/opensk/src/api/crypto/software_crypto.rs index debf0e5..400e7b5 100644 --- a/libraries/opensk/src/api/crypto/software_crypto.rs +++ b/libraries/opensk/src/api/crypto/software_crypto.rs @@ -12,8 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::api::crypto::{ecdh, ecdsa, Crypto, EC_FIELD_SIZE, EC_SIGNATURE_SIZE}; +use crate::api::crypto::hmac256::Hmac256; +use crate::api::crypto::sha256::Sha256; +use crate::api::crypto::{ + ecdh, ecdsa, Crypto, EC_FIELD_SIZE, EC_SIGNATURE_SIZE, HASH_SIZE, HMAC_KEY_SIZE, + TRUNCATED_HMAC_SIZE, +}; use alloc::vec::Vec; +use crypto::Hash256; use rng256::Rng256; pub struct SoftwareCrypto; @@ -23,6 +29,8 @@ pub struct SoftwareEcdsa; impl Crypto for SoftwareCrypto { type Ecdh = SoftwareEcdh; type Ecdsa = SoftwareEcdsa; + type Sha256 = SoftwareSha256; + type Hmac256 = SoftwareHmac256; } impl ecdh::Ecdh for SoftwareEcdh { @@ -152,6 +160,47 @@ impl ecdsa::Signature for SoftwareEcdsaSignature { } } +pub struct SoftwareSha256 { + hasher: crypto::sha256::Sha256, +} + +impl Sha256 for SoftwareSha256 { + fn new() -> Self { + let hasher = crypto::sha256::Sha256::new(); + Self { hasher } + } + + /// Digest the next part of the message to hash. + fn update(&mut self, data: &[u8]) { + self.hasher.update(data); + } + + /// Finalizes the hashing process, returns the hash value. + fn finalize(self) -> [u8; HASH_SIZE] { + self.hasher.finalize() + } +} + +pub struct SoftwareHmac256; + +impl Hmac256 for SoftwareHmac256 { + fn mac(key: &[u8; HMAC_KEY_SIZE], data: &[u8]) -> [u8; HASH_SIZE] { + crypto::hmac::hmac_256::(key, data) + } + + fn verify(key: &[u8; HMAC_KEY_SIZE], data: &[u8], mac: &[u8; HASH_SIZE]) -> bool { + crypto::hmac::verify_hmac_256::(key, data, mac) + } + + fn verify_truncated_left( + key: &[u8; HMAC_KEY_SIZE], + data: &[u8], + mac: &[u8; TRUNCATED_HMAC_SIZE], + ) -> bool { + crypto::hmac::verify_hmac_256_first_128bits::(key, data, mac) + } +} + #[cfg(test)] mod test { use super::*; @@ -160,6 +209,7 @@ mod test { }; use crate::api::crypto::ecdsa::{PublicKey as EcdsaPublicKey, SecretKey as EcdsaSecretKey}; use crate::env::test::TestEnv; + use core::convert::TryFrom; #[test] fn test_shared_secret_symmetric() { @@ -210,4 +260,27 @@ mod test { second_key.to_slice(&mut new_bytes); assert_eq!(key_bytes, new_bytes); } + + #[test] + fn test_sha256_hash_matches() { + let data = [0x55; 16]; + let mut hasher = SoftwareSha256::new(); + hasher.update(&data); + assert_eq!(SoftwareSha256::digest(&data), hasher.finalize()); + } + + #[test] + fn test_hmac256_verifies() { + let key = [0xAA; HMAC_KEY_SIZE]; + let data = [0x55; 16]; + let mac = SoftwareHmac256::mac(&key, &data); + assert!(SoftwareHmac256::verify(&key, &data, &mac)); + let truncated_mac = + <&[u8; TRUNCATED_HMAC_SIZE]>::try_from(&mac[..TRUNCATED_HMAC_SIZE]).unwrap(); + assert!(SoftwareHmac256::verify_truncated_left( + &key, + &data, + truncated_mac + )); + } } diff --git a/libraries/opensk/src/ctap/client_pin.rs b/libraries/opensk/src/ctap/client_pin.rs index 9d1d8bc..d165007 100644 --- a/libraries/opensk/src/ctap/client_pin.rs +++ b/libraries/opensk/src/ctap/client_pin.rs @@ -22,18 +22,16 @@ use super::status_code::Ctap2StatusCode; use super::token_state::PinUvAuthTokenState; #[cfg(test)] use crate::api::crypto::ecdh::SecretKey as _; +use crate::api::crypto::hmac256::Hmac256; +use crate::api::crypto::sha256::Sha256; use crate::api::customization::Customization; use crate::ctap::storage; #[cfg(test)] use crate::env::EcdhSk; -use crate::env::Env; -use alloc::boxed::Box; +use crate::env::{Env, Hmac, Sha}; use alloc::str; use alloc::string::String; use alloc::vec::Vec; -use crypto::hmac::hmac_256; -use crypto::sha256::Sha256; -use crypto::Hash256; #[cfg(test)] use enum_iterator::IntoEnumIterator; use subtle::ConstantTimeEq; @@ -60,8 +58,8 @@ pub const PIN_TOKEN_LENGTH: usize = 32; const PIN_PADDED_LENGTH: usize = 64; /// Decrypts the new_pin_enc and outputs the found PIN. -fn decrypt_pin( - shared_secret: &dyn SharedSecret, +fn decrypt_pin( + shared_secret: &SharedSecret, new_pin_enc: Vec, ) -> Result, Ctap2StatusCode> { let decrypted_pin = shared_secret.decrypt(&new_pin_enc)?; @@ -79,9 +77,9 @@ fn decrypt_pin( /// The new PIN is passed encrypted, so it is first decrypted and stripped from /// padding. Next, it is checked against the PIN policy. Last, it is hashed and /// truncated for persistent storage. -fn check_and_store_new_pin( - env: &mut impl Env, - shared_secret: &dyn SharedSecret, +fn check_and_store_new_pin( + env: &mut E, + shared_secret: &SharedSecret, new_pin_enc: Vec, ) -> Result<(), Ctap2StatusCode> { let pin = decrypt_pin(shared_secret, new_pin_enc)?; @@ -91,7 +89,7 @@ fn check_and_store_new_pin( return Err(Ctap2StatusCode::CTAP2_ERR_PIN_POLICY_VIOLATION); } let mut pin_hash = [0u8; PIN_AUTH_LENGTH]; - pin_hash.copy_from_slice(&Sha256::hash(&pin[..])[..PIN_AUTH_LENGTH]); + pin_hash.copy_from_slice(&Sha::::digest(&pin[..])[..PIN_AUTH_LENGTH]); // The PIN length is always < PIN_PADDED_LENGTH < 256. storage::set_pin(env, &pin_hash, pin_length as u8)?; Ok(()) @@ -149,7 +147,7 @@ impl ClientPin { &self, pin_uv_auth_protocol: PinUvAuthProtocol, key_agreement: CoseKey, - ) -> Result, Ctap2StatusCode> { + ) -> Result, Ctap2StatusCode> { self.get_pin_protocol(pin_uv_auth_protocol) .decapsulate(key_agreement, pin_uv_auth_protocol) } @@ -163,7 +161,7 @@ impl ClientPin { &mut self, env: &mut E, pin_uv_auth_protocol: PinUvAuthProtocol, - shared_secret: &dyn SharedSecret, + shared_secret: &SharedSecret, pin_hash_enc: Vec, ) -> Result<(), Ctap2StatusCode> { match storage::pin_hash(env)? { @@ -247,7 +245,7 @@ impl ClientPin { let shared_secret = self.get_shared_secret(pin_uv_auth_protocol, key_agreement)?; shared_secret.verify(&new_pin_enc, &pin_uv_auth_param)?; - check_and_store_new_pin(env, shared_secret.as_ref(), new_pin_enc)?; + check_and_store_new_pin(env, &shared_secret, new_pin_enc)?; storage::reset_pin_retries(env)?; Ok(()) } @@ -277,14 +275,9 @@ impl ClientPin { let mut auth_param_data = new_pin_enc.clone(); auth_param_data.extend(&pin_hash_enc); shared_secret.verify(&auth_param_data, &pin_uv_auth_param)?; - self.verify_pin_hash_enc( - env, - pin_uv_auth_protocol, - shared_secret.as_ref(), - pin_hash_enc, - )?; + self.verify_pin_hash_enc(env, pin_uv_auth_protocol, &shared_secret, pin_hash_enc)?; - check_and_store_new_pin(env, shared_secret.as_ref(), new_pin_enc)?; + check_and_store_new_pin(env, &shared_secret, new_pin_enc)?; self.pin_protocol_v1.reset_pin_uv_auth_token(env.rng()); self.pin_protocol_v2.reset_pin_uv_auth_token(env.rng()); Ok(()) @@ -313,12 +306,7 @@ impl ClientPin { return Err(Ctap2StatusCode::CTAP2_ERR_PIN_BLOCKED); } let shared_secret = self.get_shared_secret(pin_uv_auth_protocol, key_agreement)?; - self.verify_pin_hash_enc( - env, - pin_uv_auth_protocol, - shared_secret.as_ref(), - pin_hash_enc, - )?; + self.verify_pin_hash_enc(env, pin_uv_auth_protocol, &shared_secret, pin_hash_enc)?; if storage::has_force_pin_change(env)? { return Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID); } @@ -435,7 +423,7 @@ impl ClientPin { if !self.pin_uv_auth_token_state.is_in_use() { return Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID); } - verify_pin_uv_auth_token( + verify_pin_uv_auth_token::( self.get_pin_protocol(pin_uv_auth_protocol) .get_pin_uv_auth_token(), hmac_contents, @@ -483,9 +471,9 @@ impl ClientPin { if decrypted_salts.len() != 32 && decrypted_salts.len() != 64 { return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER); } - let mut output = hmac_256::(cred_random, &decrypted_salts[..32]).to_vec(); + let mut output = Hmac::::mac(cred_random, &decrypted_salts[..32]).to_vec(); if decrypted_salts.len() == 64 { - let mut output2 = hmac_256::(cred_random, &decrypted_salts[32..]).to_vec(); + let mut output2 = Hmac::::mac(cred_random, &decrypted_salts[32..]).to_vec(); output.append(&mut output2); } shared_secret.encrypt(env.rng(), &output) @@ -596,12 +584,12 @@ mod test { let mut pin = [0u8; 64]; pin[..4].copy_from_slice(b"1234"); let mut pin_hash = [0u8; 16]; - pin_hash.copy_from_slice(&Sha256::hash(&pin[..])[..16]); + pin_hash.copy_from_slice(&Sha::::digest(&pin[..])[..16]); storage::set_pin(env, &pin_hash, 4).unwrap(); } /// Fails on PINs bigger than 64 bytes. - fn encrypt_pin(shared_secret: &dyn SharedSecret, pin: Vec) -> Vec { + fn encrypt_pin(shared_secret: &SharedSecret, pin: Vec) -> Vec { assert!(pin.len() <= 64); let mut env = TestEnv::default(); let mut padded_pin = [0u8; 64]; @@ -617,7 +605,7 @@ mod test { /// should fail. fn create_client_pin_and_shared_secret( pin_uv_auth_protocol: PinUvAuthProtocol, - ) -> (ClientPin, Box) { + ) -> (ClientPin, SharedSecret) { let mut env = TestEnv::default(); let key_agreement_key = EcdhSk::::random(env.rng()); let pk = key_agreement_key.public_key(); @@ -649,16 +637,10 @@ mod test { let pin = b"1234"; let mut padded_pin = [0u8; 64]; padded_pin[..pin.len()].copy_from_slice(&pin[..]); - let pin_hash = Sha256::hash(&padded_pin); - let new_pin_enc = shared_secret - .as_ref() - .encrypt(env.rng(), &padded_pin) - .unwrap(); - let pin_uv_auth_param = shared_secret.as_ref().authenticate(&new_pin_enc); - let pin_hash_enc = shared_secret - .as_ref() - .encrypt(env.rng(), &pin_hash[..16]) - .unwrap(); + let pin_hash = Sha::::digest(&padded_pin); + let new_pin_enc = shared_secret.encrypt(env.rng(), &padded_pin).unwrap(); + let pin_uv_auth_param = shared_secret.authenticate(&new_pin_enc); + let pin_hash_enc = shared_secret.encrypt(env.rng(), &pin_hash[..16]).unwrap(); let (permissions, permissions_rp_id) = match sub_command { ClientPinSubCommand::GetPinUvAuthTokenUsingUvWithPermissions | ClientPinSubCommand::GetPinUvAuthTokenUsingPinWithPermissions => { @@ -739,15 +721,12 @@ mod test { ]; storage::set_pin(&mut env, &pin_hash, 4).unwrap(); - let pin_hash_enc = shared_secret - .as_ref() - .encrypt(env.rng(), &pin_hash) - .unwrap(); + let pin_hash_enc = shared_secret.encrypt(env.rng(), &pin_hash).unwrap(); assert_eq!( client_pin.verify_pin_hash_enc( &mut env, pin_uv_auth_protocol, - shared_secret.as_ref(), + &shared_secret, pin_hash_enc ), Ok(()) @@ -758,22 +737,19 @@ mod test { client_pin.verify_pin_hash_enc( &mut env, pin_uv_auth_protocol, - shared_secret.as_ref(), + &shared_secret, pin_hash_enc ), Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID) ); - let pin_hash_enc = shared_secret - .as_ref() - .encrypt(env.rng(), &pin_hash) - .unwrap(); + let pin_hash_enc = shared_secret.encrypt(env.rng(), &pin_hash).unwrap(); client_pin.consecutive_pin_mismatches = 3; assert_eq!( client_pin.verify_pin_hash_enc( &mut env, pin_uv_auth_protocol, - shared_secret.as_ref(), + &shared_secret, pin_hash_enc ), Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_BLOCKED) @@ -785,7 +761,7 @@ mod test { client_pin.verify_pin_hash_enc( &mut env, pin_uv_auth_protocol, - shared_secret.as_ref(), + &shared_secret, pin_hash_enc ), Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID) @@ -796,7 +772,7 @@ mod test { client_pin.verify_pin_hash_enc( &mut env, pin_uv_auth_protocol, - shared_secret.as_ref(), + &shared_secret, pin_hash_enc ), Err(Ctap2StatusCode::CTAP2_ERR_PIN_INVALID) @@ -1176,29 +1152,29 @@ mod test { .decapsulate(pin_protocol.get_public_key(), pin_uv_auth_protocol) .unwrap(); - let new_pin_enc = encrypt_pin(shared_secret.as_ref(), b"1234".to_vec()); + let new_pin_enc = encrypt_pin(&shared_secret, b"1234".to_vec()); assert_eq!( - decrypt_pin(shared_secret.as_ref(), new_pin_enc), + decrypt_pin::(&shared_secret, new_pin_enc), Ok(b"1234".to_vec()), ); - let new_pin_enc = encrypt_pin(shared_secret.as_ref(), b"123".to_vec()); + let new_pin_enc = encrypt_pin(&shared_secret, b"123".to_vec()); assert_eq!( - decrypt_pin(shared_secret.as_ref(), new_pin_enc), + decrypt_pin::(&shared_secret, new_pin_enc), Ok(b"123".to_vec()), ); // Encrypted PIN is too short. let new_pin_enc = vec![0x44; 63]; assert_eq!( - decrypt_pin(shared_secret.as_ref(), new_pin_enc), + decrypt_pin::(&shared_secret, new_pin_enc), Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER) ); // Encrypted PIN is too long. let new_pin_enc = vec![0x44; 65]; assert_eq!( - decrypt_pin(shared_secret.as_ref(), new_pin_enc), + decrypt_pin::(&shared_secret, new_pin_enc), Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER) ); } @@ -1241,10 +1217,10 @@ mod test { ]; for (pin, result) in test_cases { let old_pin_hash = storage::pin_hash(&mut env).unwrap(); - let new_pin_enc = encrypt_pin(shared_secret.as_ref(), pin); + let new_pin_enc = encrypt_pin(&shared_secret, pin); assert_eq!( - check_and_store_new_pin(&mut env, shared_secret.as_ref(), new_pin_enc), + check_and_store_new_pin(&mut env, &shared_secret, new_pin_enc), result ); if result.is_ok() { @@ -1274,7 +1250,7 @@ mod test { let mut env = TestEnv::default(); let (client_pin, shared_secret) = create_client_pin_and_shared_secret(pin_uv_auth_protocol); - let salt_enc = shared_secret.as_ref().encrypt(env.rng(), &salt).unwrap(); + let salt_enc = shared_secret.encrypt(env.rng(), &salt).unwrap(); let salt_auth = shared_secret.authenticate(&salt_enc); let hmac_secret_input = GetAssertionHmacSecretInput { key_agreement: client_pin @@ -1285,7 +1261,7 @@ mod test { pin_uv_auth_protocol, }; let output = client_pin.process_hmac_secret(&mut env, hmac_secret_input, cred_random); - output.map(|v| shared_secret.as_ref().decrypt(&v).unwrap()) + output.map(|v| shared_secret.decrypt(&v).unwrap()) } fn test_helper_process_hmac_secret_bad_salt_auth(pin_uv_auth_protocol: PinUvAuthProtocol) { @@ -1322,7 +1298,7 @@ mod test { let cred_random = [0xC9; 32]; let salt = vec![0x01; 32]; - let expected_output = hmac_256::(&cred_random, &salt); + let expected_output = Hmac::::mac(&cred_random, &salt); let output = get_process_hmac_secret_decrypted_output(pin_uv_auth_protocol, &cred_random, salt) @@ -1345,8 +1321,8 @@ mod test { let salt1 = [0x01; 32]; let salt2 = [0x02; 32]; - let expected_output1 = hmac_256::(&cred_random, &salt1); - let expected_output2 = hmac_256::(&cred_random, &salt2); + let expected_output1 = Hmac::::mac(&cred_random, &salt1); + let expected_output2 = Hmac::::mac(&cred_random, &salt2); let mut salt12 = vec![0x00; 64]; salt12[..32].copy_from_slice(&salt1); @@ -1460,7 +1436,7 @@ mod test { fn test_has_no_or_rp_id_hash_permission() { let mut env = TestEnv::default(); let mut client_pin = ClientPin::::new(&mut env); - let rp_id_hash = Sha256::hash(b"example.com"); + let rp_id_hash = Sha::::digest(b"example.com"); assert_eq!( client_pin.has_no_or_rp_id_hash_permission(&rp_id_hash), Ok(()) diff --git a/libraries/opensk/src/ctap/credential_id.rs b/libraries/opensk/src/ctap/credential_id.rs index 5d6359f..6062ef4 100644 --- a/libraries/opensk/src/ctap/credential_id.rs +++ b/libraries/opensk/src/ctap/credential_id.rs @@ -18,14 +18,13 @@ use super::data_formats::{ }; use super::status_code::Ctap2StatusCode; use super::{cbor_read, cbor_write}; +use crate::api::crypto::hmac256::Hmac256; use crate::api::key_store::KeyStore; use crate::ctap::data_formats::{extract_byte_string, extract_map}; -use crate::env::Env; +use crate::env::{Env, Hmac}; use alloc::string::String; use alloc::vec::Vec; use core::convert::{TryFrom, TryInto}; -use crypto::hmac::{hmac_256, verify_hmac_256}; -use crypto::sha256::Sha256; use sk_cbor::{cbor_map_options, destructure_cbor_map}; pub const LEGACY_CREDENTIAL_ID_SIZE: usize = 112; @@ -161,8 +160,8 @@ fn remove_padding(data: &mut Vec) -> Result<(), Ctap2StatusCode> { /// /// Other information, such as a user name, are not stored. Since encrypted credential IDs are /// stored server-side, this information is already available (unencrypted). -pub fn encrypt_to_credential_id( - env: &mut impl Env, +pub fn encrypt_to_credential_id( + env: &mut E, private_key: &PrivateKey, rp_id_hash: &[u8; 32], cred_protect_policy: Option, @@ -183,7 +182,7 @@ pub fn encrypt_to_credential_id( let mut credential_id = encrypted_payload; credential_id.insert(0, CBOR_CREDENTIAL_ID_VERSION); - let id_hmac = hmac_256::( + let id_hmac = Hmac::::mac( &env.key_store().key_handle_authentication()?, &credential_id[..], ); @@ -208,8 +207,8 @@ pub fn encrypt_to_credential_id( /// - 16 bytes: initialization vector for AES-256, /// - 192 bytes: encrypted CBOR-encoded credential source fields, /// - 32 bytes: HMAC-SHA256 over everything else. -pub fn decrypt_credential_id( - env: &mut impl Env, +pub fn decrypt_credential_id( + env: &mut E, credential_id: Vec, rp_id_hash: &[u8], ) -> Result, Ctap2StatusCode> { @@ -217,7 +216,7 @@ pub fn decrypt_credential_id( return Ok(None); } let hmac_message_size = credential_id.len() - 32; - if !verify_hmac_256::( + if !Hmac::::verify( &env.key_store().key_handle_authentication()?, &credential_id[..hmac_message_size], array_ref![credential_id, hmac_message_size, 32], @@ -274,7 +273,6 @@ mod test { use crate::ctap::SignatureAlgorithm; use crate::env::test::TestEnv; use crate::env::EcdsaSk; - use crypto::hmac::hmac_256; const UNSUPPORTED_CREDENTIAL_ID_VERSION: u8 = 0x80; @@ -315,7 +313,7 @@ mod test { // Override the HMAC to pass the check. encrypted_id.truncate(&encrypted_id.len() - 32); let hmac_key = env.key_store().key_handle_authentication().unwrap(); - let id_hmac = hmac_256::(&hmac_key, &encrypted_id[..]); + let id_hmac = Hmac::::mac(&hmac_key, &encrypted_id[..]); encrypted_id.extend(&id_hmac); assert_eq!( @@ -392,7 +390,7 @@ mod test { plaintext[32..64].copy_from_slice(application); let mut encrypted_id = aes256_cbc_encrypt(env.rng(), &aes_enc_key, &plaintext, true)?; - let id_hmac = hmac_256::( + let id_hmac = Hmac::::mac( &env.key_store().key_handle_authentication()?, &encrypted_id[..], ); diff --git a/libraries/opensk/src/ctap/credential_management.rs b/libraries/opensk/src/ctap/credential_management.rs index 6d7a024..2ed4ccb 100644 --- a/libraries/opensk/src/ctap/credential_management.rs +++ b/libraries/opensk/src/ctap/credential_management.rs @@ -22,14 +22,13 @@ use super::data_formats::{ use super::response::{AuthenticatorCredentialManagementResponse, ResponseData}; use super::status_code::Ctap2StatusCode; use super::{Channel, StatefulCommand, StatefulPermission}; +use crate::api::crypto::sha256::Sha256; use crate::ctap::storage; -use crate::env::Env; +use crate::env::{Env, Sha}; use alloc::collections::BTreeSet; use alloc::string::String; use alloc::vec; use alloc::vec::Vec; -use crypto::sha256::Sha256; -use crypto::Hash256; /// Generates a set with all existing RP IDs. fn get_stored_rp_ids(env: &mut impl Env) -> Result, Ctap2StatusCode> { @@ -43,11 +42,11 @@ fn get_stored_rp_ids(env: &mut impl Env) -> Result, Ctap2Status } /// Generates the response for subcommands enumerating RPs. -fn enumerate_rps_response( +fn enumerate_rps_response( rp_id: String, total_rps: Option, ) -> Result { - let rp_id_hash = Some(Sha256::hash(rp_id.as_bytes()).to_vec()); + let rp_id_hash = Some(Sha::::digest(rp_id.as_bytes()).to_vec()); let rp = Some(PublicKeyCredentialRpEntity { rp_id, rp_name: None, @@ -150,7 +149,7 @@ fn process_enumerate_rps_begin( .into_iter() .next() .ok_or(Ctap2StatusCode::CTAP2_ERR_NO_CREDENTIALS)?; - enumerate_rps_response(rp_id, Some(total_rps as u64)) + enumerate_rps_response::(rp_id, Some(total_rps as u64)) } /// Processes the subcommand enumerateRPsGetNextRP for CredentialManagement. @@ -165,7 +164,7 @@ fn process_enumerate_rps_get_next_rp( .into_iter() .nth(rp_id_index) .ok_or(Ctap2StatusCode::CTAP2_ERR_NOT_ALLOWED)?; - enumerate_rps_response(rp_id, None) + enumerate_rps_response::(rp_id, None) } /// Processes the subcommand enumerateCredentialsBegin for CredentialManagement. @@ -184,7 +183,7 @@ fn process_enumerate_credentials_begin( let iter = storage::iter_credentials(env, &mut iter_result)?; let mut rp_credentials: Vec = iter .filter_map(|(key, credential)| { - let cred_rp_id_hash = Sha256::hash(credential.rp_id.as_bytes()); + let cred_rp_id_hash = Sha::::digest(credential.rp_id.as_bytes()); if cred_rp_id_hash == rp_id_hash.as_slice() { Some(key) } else { @@ -511,7 +510,7 @@ mod test { ResponseData::AuthenticatorCredentialManagement(Some(response)) => { assert_eq!(response.total_rps, Some(2)); let rp_id = response.rp.unwrap().rp_id; - let rp_id_hash = Sha256::hash(rp_id.as_bytes()); + let rp_id_hash = Sha::::digest(rp_id.as_bytes()); assert_eq!(rp_id_hash, response.rp_id_hash.unwrap().as_slice()); rp_id } @@ -535,7 +534,7 @@ mod test { ResponseData::AuthenticatorCredentialManagement(Some(response)) => { assert_eq!(response.total_rps, None); let rp_id = response.rp.unwrap().rp_id; - let rp_id_hash = Sha256::hash(rp_id.as_bytes()); + let rp_id_hash = Sha::::digest(rp_id.as_bytes()); assert_eq!(rp_id_hash, response.rp_id_hash.unwrap().as_slice()); rp_id } @@ -617,7 +616,7 @@ mod test { assert_eq!(response.total_rps, None); } let rp_id = response.rp.unwrap().rp_id; - let rp_id_hash = Sha256::hash(rp_id.as_bytes()); + let rp_id_hash = Sha::::digest(rp_id.as_bytes()); assert_eq!(rp_id_hash, response.rp_id_hash.unwrap().as_slice()); assert!(!rp_set.contains(&rp_id)); rp_set.insert(rp_id); @@ -676,7 +675,7 @@ mod test { ]); let sub_command_params = CredentialManagementSubCommandParameters { - rp_id_hash: Some(Sha256::hash(b"example.com").to_vec()), + rp_id_hash: Some(Sha::::digest(b"example.com").to_vec()), credential_id: None, user: None, }; diff --git a/libraries/opensk/src/ctap/ctap1.rs b/libraries/opensk/src/ctap/ctap1.rs index ea2ce20..006cc15 100644 --- a/libraries/opensk/src/ctap/ctap1.rs +++ b/libraries/opensk/src/ctap/ctap1.rs @@ -19,7 +19,7 @@ use super::CtapState; use crate::api::attestation_store::{self, Attestation, AttestationStore}; use crate::api::crypto::ecdsa::{self, SecretKey as _, Signature}; use crate::api::crypto::EC_FIELD_SIZE; -use crate::env::Env; +use crate::env::{EcdsaSk, Env}; use alloc::vec::Vec; use arrayref::{array_ref, mut_array_refs}; use core::convert::TryFrom; @@ -249,8 +249,8 @@ impl Ctap1Command { // +------+-------------------+-----------------+------------+--------------------+ // + 0x00 | application (32B) | challenge (32B) | key handle | User pub key (65B) | // +------+-------------------+-----------------+------------+--------------------+ - fn process_register( - env: &mut impl Env, + fn process_register( + env: &mut E, challenge: [u8; 32], application: [u8; 32], ) -> Result, Ctap1StatusCode> { @@ -290,10 +290,10 @@ impl Ctap1Command { signature_data.extend(key_handle); signature_data.extend_from_slice(&user_pk); - let attestation_key = crypto::ecdsa::SecKey::from_bytes(&private_key).unwrap(); - let signature = attestation_key.sign_rfc6979::(&signature_data); + let attestation_key = EcdsaSk::::from_slice(&private_key).unwrap(); + let signature = attestation_key.sign(&signature_data); - response.extend(signature.to_asn1_der()); + response.extend(signature.to_der()); Ok(response) } @@ -357,10 +357,11 @@ mod test { use super::super::data_formats::SignatureAlgorithm; use super::super::TOUCH_TIMEOUT_MS; use super::*; + use crate::api::crypto::sha256::Sha256; use crate::api::customization::Customization; use crate::ctap::storage; use crate::env::test::TestEnv; - use crypto::Hash256; + use crate::env::Sha; fn create_register_message(application: &[u8; 32]) -> Vec { let mut message = vec![ @@ -497,7 +498,7 @@ mod test { let mut ctap_state = CtapState::new(&mut env); let rp_id = "example.com"; - let application = crypto::sha256::Sha256::hash(rp_id.as_bytes()); + let application = Sha::::digest(rp_id.as_bytes()); let key_handle = encrypt_to_credential_id(&mut env, &sk, &application, None, None).unwrap(); let message = create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle); @@ -514,7 +515,7 @@ mod test { let mut ctap_state = CtapState::new(&mut env); let rp_id = "example.com"; - let application = crypto::sha256::Sha256::hash(rp_id.as_bytes()); + let application = Sha::::digest(rp_id.as_bytes()); let key_handle = encrypt_to_credential_id(&mut env, &sk, &application, None, None).unwrap(); let application = [0x55; 32]; let message = create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle); @@ -532,7 +533,7 @@ mod test { let mut ctap_state = CtapState::new(&mut env); let rp_id = "example.com"; - let application = crypto::sha256::Sha256::hash(rp_id.as_bytes()); + let application = Sha::::digest(rp_id.as_bytes()); let key_handle = encrypt_to_credential_id(&mut env, &sk, &application, None, None).unwrap(); let mut message = create_authenticate_message( &application, @@ -566,7 +567,7 @@ mod test { let mut ctap_state = CtapState::new(&mut env); let rp_id = "example.com"; - let application = crypto::sha256::Sha256::hash(rp_id.as_bytes()); + let application = Sha::::digest(rp_id.as_bytes()); let key_handle = encrypt_to_credential_id(&mut env, &sk, &application, None, None).unwrap(); let mut message = create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle); @@ -585,7 +586,7 @@ mod test { let mut ctap_state = CtapState::new(&mut env); let rp_id = "example.com"; - let application = crypto::sha256::Sha256::hash(rp_id.as_bytes()); + let application = Sha::::digest(rp_id.as_bytes()); let key_handle = encrypt_to_credential_id(&mut env, &sk, &application, None, None).unwrap(); let mut message = create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle); @@ -604,7 +605,7 @@ mod test { let mut ctap_state = CtapState::new(&mut env); let rp_id = "example.com"; - let application = crypto::sha256::Sha256::hash(rp_id.as_bytes()); + let application = Sha::::digest(rp_id.as_bytes()); let key_handle = encrypt_to_credential_id(&mut env, &sk, &application, None, None).unwrap(); let mut message = create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle); @@ -631,7 +632,7 @@ mod test { let mut ctap_state = CtapState::new(&mut env); let rp_id = "example.com"; - let application = crypto::sha256::Sha256::hash(rp_id.as_bytes()); + let application = Sha::::digest(rp_id.as_bytes()); let key_handle = encrypt_to_credential_id(&mut env, &sk, &application, None, None).unwrap(); let message = create_authenticate_message(&application, Ctap1Flags::EnforceUpAndSign, &key_handle); @@ -657,7 +658,7 @@ mod test { let mut ctap_state = CtapState::new(&mut env); let rp_id = "example.com"; - let application = crypto::sha256::Sha256::hash(rp_id.as_bytes()); + let application = Sha::::digest(rp_id.as_bytes()); let key_handle = encrypt_to_credential_id(&mut env, &sk, &application, None, None).unwrap(); let message = create_authenticate_message( &application, diff --git a/libraries/opensk/src/ctap/large_blobs.rs b/libraries/opensk/src/ctap/large_blobs.rs index 445b6d1..ff92fa4 100644 --- a/libraries/opensk/src/ctap/large_blobs.rs +++ b/libraries/opensk/src/ctap/large_blobs.rs @@ -16,14 +16,13 @@ use super::client_pin::{ClientPin, PinPermission}; use super::command::AuthenticatorLargeBlobsParameters; use super::response::{AuthenticatorLargeBlobsResponse, ResponseData}; use super::status_code::Ctap2StatusCode; +use crate::api::crypto::sha256::Sha256; use crate::api::customization::Customization; use crate::ctap::storage; -use crate::env::Env; +use crate::env::{Env, Sha}; use alloc::vec; use alloc::vec::Vec; use byteorder::{ByteOrder, LittleEndian}; -use crypto::sha256::Sha256; -use crypto::Hash256; /// The length of the truncated hash that is appended to the large blob data. const TRUNCATED_HASH_LEN: usize = 16; @@ -97,7 +96,7 @@ impl LargeBlobs { let mut offset_bytes = [0u8; 4]; LittleEndian::write_u32(&mut offset_bytes, offset as u32); large_blob_data.extend(&offset_bytes); - large_blob_data.extend(&Sha256::hash(set.as_slice())); + large_blob_data.extend(&Sha::::digest(set.as_slice())); client_pin.verify_pin_uv_auth_token( &large_blob_data, &pin_uv_auth_param, @@ -118,7 +117,7 @@ impl LargeBlobs { self.expected_next_offset = 0; // Must be a positive number. let buffer_hash_index = self.buffer.len() - TRUNCATED_HASH_LEN; - if Sha256::hash(&self.buffer[..buffer_hash_index])[..TRUNCATED_HASH_LEN] + if Sha::::digest(&self.buffer[..buffer_hash_index])[..TRUNCATED_HASH_LEN] != self.buffer[buffer_hash_index..] { self.buffer = Vec::new(); @@ -195,7 +194,8 @@ mod test { const BLOB_LEN: usize = 200; const DATA_LEN: usize = BLOB_LEN - TRUNCATED_HASH_LEN; let mut large_blob = vec![0x1B; DATA_LEN]; - large_blob.extend_from_slice(&Sha256::hash(&large_blob[..])[..TRUNCATED_HASH_LEN]); + large_blob + .extend_from_slice(&Sha::::digest(&large_blob[..])[..TRUNCATED_HASH_LEN]); let large_blobs_params = AuthenticatorLargeBlobsParameters { get: None, @@ -261,7 +261,8 @@ mod test { const BLOB_LEN: usize = 200; const DATA_LEN: usize = BLOB_LEN - TRUNCATED_HASH_LEN; let mut large_blob = vec![0x1B; DATA_LEN]; - large_blob.extend_from_slice(&Sha256::hash(&large_blob[..])[..TRUNCATED_HASH_LEN]); + large_blob + .extend_from_slice(&Sha::::digest(&large_blob[..])[..TRUNCATED_HASH_LEN]); let large_blobs_params = AuthenticatorLargeBlobsParameters { get: None, @@ -311,7 +312,8 @@ mod test { const BLOB_LEN: usize = 200; const DATA_LEN: usize = BLOB_LEN - TRUNCATED_HASH_LEN; let mut large_blob = vec![0x1B; DATA_LEN]; - large_blob.extend_from_slice(&Sha256::hash(&large_blob[..])[..TRUNCATED_HASH_LEN]); + large_blob + .extend_from_slice(&Sha::::digest(&large_blob[..])[..TRUNCATED_HASH_LEN]); let large_blobs_params = AuthenticatorLargeBlobsParameters { get: None, @@ -420,13 +422,14 @@ mod test { const BLOB_LEN: usize = 20; const DATA_LEN: usize = BLOB_LEN - TRUNCATED_HASH_LEN; let mut large_blob = vec![0x1B; DATA_LEN]; - large_blob.extend_from_slice(&Sha256::hash(&large_blob[..])[..TRUNCATED_HASH_LEN]); + large_blob + .extend_from_slice(&Sha::::digest(&large_blob[..])[..TRUNCATED_HASH_LEN]); storage::set_pin(&mut env, &[0u8; 16], 4).unwrap(); let mut large_blob_data = vec![0xFF; 32]; // Command constant and offset bytes. large_blob_data.extend(&[0x0C, 0x00, 0x00, 0x00, 0x00, 0x00]); - large_blob_data.extend(&Sha256::hash(&large_blob)); + large_blob_data.extend(&Sha::::digest(&large_blob)); let pin_uv_auth_param = authenticate_pin_uv_auth_token( &pin_uv_auth_token, &large_blob_data, diff --git a/libraries/opensk/src/ctap/mod.rs b/libraries/opensk/src/ctap/mod.rs index a7eb68d..9909988 100644 --- a/libraries/opensk/src/ctap/mod.rs +++ b/libraries/opensk/src/ctap/mod.rs @@ -66,20 +66,19 @@ use crate::api::attestation_store::{self, Attestation, AttestationStore}; use crate::api::clock::Clock; use crate::api::connection::{HidConnection, SendOrRecvStatus, UsbEndpoint}; use crate::api::crypto::ecdsa::{SecretKey as _, Signature}; +use crate::api::crypto::hmac256::Hmac256; +use crate::api::crypto::sha256::Sha256; use crate::api::customization::Customization; use crate::api::firmware_protection::FirmwareProtection; use crate::api::upgrade_storage::UpgradeStorage; use crate::api::user_presence::{UserPresence, UserPresenceError}; -use crate::env::{EcdsaSk, Env}; +use crate::env::{EcdsaSk, Env, Hmac, Sha}; use alloc::boxed::Box; use alloc::string::{String, ToString}; use alloc::vec; use alloc::vec::Vec; use byteorder::{BigEndian, ByteOrder}; use core::convert::TryFrom; -use crypto::hmac::hmac_256; -use crypto::sha256::Sha256; -use crypto::Hash256; use rng256::Rng256; use sk_cbor as cbor; use sk_cbor::cbor_map_options; @@ -781,7 +780,7 @@ impl CtapState { }; flags |= UP_FLAG | AT_FLAG; - let rp_id_hash = Sha256::hash(rp_id.as_bytes()); + let rp_id_hash = Sha::::digest(rp_id.as_bytes()); if let Some(exclude_list) = exclude_list { for cred_desc in exclude_list { if self.check_cred_protect_for_listed_credential( @@ -958,7 +957,7 @@ impl CtapState { ) -> Result<[u8; 32], Ctap2StatusCode> { let entropy = private_key.to_bytes(); let key = storage::cred_random_secret(env, has_uv)?; - Ok(hmac_256::(&key, &entropy)) + Ok(Hmac::::mac(&key, &entropy)) } // Processes the input of a get_assertion operation for a given credential @@ -1135,7 +1134,7 @@ impl CtapState { flags |= ED_FLAG; } - let rp_id_hash = Sha256::hash(rp_id.as_bytes()); + let rp_id_hash = Sha::::digest(rp_id.as_bytes()); let (credential, next_credential_keys) = if let Some(allow_list) = allow_list { ( self.get_any_credential_from_allow_list( @@ -1382,7 +1381,7 @@ impl CtapState { params: AuthenticatorVendorUpgradeParameters, ) -> Result { let AuthenticatorVendorUpgradeParameters { offset, data, hash } = params; - let calculated_hash = Sha256::hash(&data); + let calculated_hash = Sha::::digest(&data); if hash != calculated_hash { return Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE); } @@ -2415,7 +2414,7 @@ mod test { .unwrap(); let salt = vec![0x01; 32]; - let salt_enc = shared_secret.as_ref().encrypt(env.rng(), &salt).unwrap(); + let salt_enc = shared_secret.encrypt(env.rng(), &salt).unwrap(); let salt_auth = shared_secret.authenticate(&salt_enc); let hmac_secret_input = GetAssertionHmacSecretInput { key_agreement: CoseKey::from_ecdh_public_key(platform_public_key), @@ -3332,9 +3331,9 @@ mod test { const METADATA_LEN: usize = 0x1000; let metadata = vec![0xFF; METADATA_LEN]; - let metadata_hash = Sha256::hash(&metadata); + let metadata_hash = Sha::::digest(&metadata); let data = vec![0xFF; 0x1000]; - let hash = Sha256::hash(&data); + let hash = Sha::::digest(&data); // Write to partition. let response = ctap_state.process_vendor_upgrade( @@ -3410,7 +3409,7 @@ mod test { let mut ctap_state = CtapState::::new(&mut env); let data = vec![0xFF; 0x1000]; - let hash = Sha256::hash(&data); + let hash = Sha::::digest(&data); let response = ctap_state.process_vendor_upgrade( &mut env, AuthenticatorVendorUpgradeParameters { diff --git a/libraries/opensk/src/ctap/pin_protocol.rs b/libraries/opensk/src/ctap/pin_protocol.rs index 12c2e1c..43d3d6b 100644 --- a/libraries/opensk/src/ctap/pin_protocol.rs +++ b/libraries/opensk/src/ctap/pin_protocol.rs @@ -12,22 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::api::crypto::ecdh::{ - PublicKey as EcdhPublicKey, SecretKey as EcdhSecretKey, SharedSecret as EcdhSharedSecret, -}; +use crate::api::crypto::ecdh::{PublicKey as _, SecretKey as _, SharedSecret as _}; +use crate::api::crypto::hmac256::Hmac256; +use crate::api::crypto::sha256::Sha256; use crate::ctap::client_pin::PIN_TOKEN_LENGTH; use crate::ctap::crypto_wrapper::{aes256_cbc_decrypt, aes256_cbc_encrypt}; use crate::ctap::data_formats::{CoseKey, PinUvAuthProtocol}; use crate::ctap::status_code::Ctap2StatusCode; -use crate::env::{EcdhPk, EcdhSk, Env}; -use alloc::boxed::Box; -use alloc::vec::Vec; -use crypto::hkdf::hkdf_empty_salt_256; #[cfg(test)] -use crypto::hmac::hmac_256; -use crypto::hmac::{verify_hmac_256, verify_hmac_256_first_128bits}; -use crypto::sha256::Sha256; -use crypto::Hash256; +use crate::env::test::TestEnv; +use crate::env::{EcdhPk, EcdhSk, Env, Hmac, Sha}; +use alloc::vec::Vec; +use core::marker::PhantomData; +use crypto::hkdf::hkdf_empty_salt_256; use rng256::Rng256; /// Implements common functions between existing PIN protocols for handshakes. @@ -69,7 +66,7 @@ impl PinProtocol { &self, peer_cose_key: CoseKey, pin_uv_auth_protocol: PinUvAuthProtocol, - ) -> Result, Ctap2StatusCode> { + ) -> Result, Ctap2StatusCode> { let (x_bytes, y_bytes) = peer_cose_key.try_into_ecdh_coordinates()?; let pk = EcdhPk::::from_coordinates(&x_bytes, &y_bytes) .ok_or(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)?; @@ -77,10 +74,7 @@ impl PinProtocol { .key_agreement_key .diffie_hellman(&pk) .raw_secret_bytes(); - match pin_uv_auth_protocol { - PinUvAuthProtocol::V1 => Ok(Box::new(SharedSecretV1::new(handshake))), - PinUvAuthProtocol::V2 => Ok(Box::new(SharedSecretV2::new(handshake))), - } + Ok(SharedSecret::new(pin_uv_auth_protocol, handshake)) } /// Getter for pinUvAuthToken. @@ -109,79 +103,127 @@ pub fn authenticate_pin_uv_auth_token( pin_uv_auth_protocol: PinUvAuthProtocol, ) -> Vec { match pin_uv_auth_protocol { - PinUvAuthProtocol::V1 => hmac_256::(token, message)[..16].to_vec(), - PinUvAuthProtocol::V2 => hmac_256::(token, message).to_vec(), + PinUvAuthProtocol::V1 => Hmac::::mac(token, message)[..16].to_vec(), + PinUvAuthProtocol::V2 => Hmac::::mac(token, message).to_vec(), } } /// Verifies the pinUvAuthToken for the given PIN protocol. -pub fn verify_pin_uv_auth_token( +pub fn verify_pin_uv_auth_token( token: &[u8; PIN_TOKEN_LENGTH], message: &[u8], signature: &[u8], pin_uv_auth_protocol: PinUvAuthProtocol, ) -> Result<(), Ctap2StatusCode> { match pin_uv_auth_protocol { - PinUvAuthProtocol::V1 => verify_v1(token, message, signature), - PinUvAuthProtocol::V2 => verify_v2(token, message, signature), + PinUvAuthProtocol::V1 => verify_v1::(token, message, signature), + PinUvAuthProtocol::V2 => verify_v2::(token, message, signature), } } -pub trait SharedSecret { +pub enum SharedSecret { + V1(SharedSecretV1), + V2(SharedSecretV2), +} + +impl SharedSecret { + /// Creates a new shared secret for the respective PIN protocol. + /// + /// This enum wraps all types of shared secrets. + fn new(pin_uv_auth_protocol: PinUvAuthProtocol, handshake: [u8; 32]) -> Self { + match pin_uv_auth_protocol { + PinUvAuthProtocol::V1 => SharedSecret::V1(SharedSecretV1::new(handshake)), + PinUvAuthProtocol::V2 => SharedSecret::V2(SharedSecretV2::new(handshake)), + } + } + /// Returns the encrypted plaintext. - fn encrypt(&self, rng: &mut dyn Rng256, plaintext: &[u8]) -> Result, Ctap2StatusCode>; + pub fn encrypt( + &self, + rng: &mut dyn Rng256, + plaintext: &[u8], + ) -> Result, Ctap2StatusCode> { + match self { + SharedSecret::V1(s) => s.encrypt(rng, plaintext), + SharedSecret::V2(s) => s.encrypt(rng, plaintext), + } + } /// Returns the decrypted ciphertext. - fn decrypt(&self, ciphertext: &[u8]) -> Result, Ctap2StatusCode>; + pub fn decrypt(&self, ciphertext: &[u8]) -> Result, Ctap2StatusCode> { + match self { + SharedSecret::V1(s) => s.decrypt(ciphertext), + SharedSecret::V2(s) => s.decrypt(ciphertext), + } + } /// Verifies that the signature is a valid MAC for the given message. - fn verify(&self, message: &[u8], signature: &[u8]) -> Result<(), Ctap2StatusCode>; + pub fn verify(&self, message: &[u8], signature: &[u8]) -> Result<(), Ctap2StatusCode> { + match self { + SharedSecret::V1(s) => s.verify(message, signature), + SharedSecret::V2(s) => s.verify(message, signature), + } + } /// Creates a signature that matches verify. #[cfg(test)] - fn authenticate(&self, message: &[u8]) -> Vec; -} - -fn verify_v1(key: &[u8; 32], message: &[u8], signature: &[u8]) -> Result<(), Ctap2StatusCode> { - if signature.len() != 16 { - return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER); - } - if verify_hmac_256_first_128bits::(key, message, array_ref![signature, 0, 16]) { - Ok(()) - } else { - Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID) - } -} - -fn verify_v2(key: &[u8; 32], message: &[u8], signature: &[u8]) -> Result<(), Ctap2StatusCode> { - if signature.len() != 32 { - return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER); - } - if verify_hmac_256::(key, message, array_ref![signature, 0, 32]) { - Ok(()) - } else { - Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID) - } -} - -pub struct SharedSecretV1 { - common_secret: [u8; 32], - aes_enc_key: crypto::aes256::EncryptionKey, -} - -impl SharedSecretV1 { - /// Creates a new shared secret from the handshake result. - fn new(handshake: [u8; 32]) -> SharedSecretV1 { - let common_secret = Sha256::hash(&handshake); - let aes_enc_key = crypto::aes256::EncryptionKey::new(&common_secret); - SharedSecretV1 { - common_secret, - aes_enc_key, + pub fn authenticate(&self, message: &[u8]) -> Vec { + match self { + SharedSecret::V1(s) => s.authenticate(message), + SharedSecret::V2(s) => s.authenticate(message), } } } -impl SharedSecret for SharedSecretV1 { +fn verify_v1( + key: &[u8; 32], + message: &[u8], + signature: &[u8], +) -> Result<(), Ctap2StatusCode> { + if signature.len() != 16 { + return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER); + } + if Hmac::::verify_truncated_left(key, message, array_ref![signature, 0, 16]) { + Ok(()) + } else { + Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID) + } +} + +fn verify_v2( + key: &[u8; 32], + message: &[u8], + signature: &[u8], +) -> Result<(), Ctap2StatusCode> { + if signature.len() != 32 { + return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER); + } + if Hmac::::verify(key, message, array_ref![signature, 0, 32]) { + Ok(()) + } else { + Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID) + } +} + +pub struct SharedSecretV1 { + common_secret: [u8; 32], + aes_enc_key: crypto::aes256::EncryptionKey, + // TODO: remove after porting AES to env crypto + phantom: PhantomData, +} + +impl SharedSecretV1 { + /// Creates a new shared secret from the handshake result. + fn new(handshake: [u8; 32]) -> Self { + let common_secret = Sha::::digest(&handshake); + let aes_enc_key = crypto::aes256::EncryptionKey::new(&common_secret); + SharedSecretV1 { + common_secret, + aes_enc_key, + phantom: PhantomData, + } + } + fn encrypt(&self, rng: &mut dyn Rng256, plaintext: &[u8]) -> Result, Ctap2StatusCode> { aes256_cbc_encrypt(rng, &self.aes_enc_key, plaintext, false) } @@ -191,32 +233,33 @@ impl SharedSecret for SharedSecretV1 { } fn verify(&self, message: &[u8], signature: &[u8]) -> Result<(), Ctap2StatusCode> { - verify_v1(&self.common_secret, message, signature) + verify_v1::(&self.common_secret, message, signature) } #[cfg(test)] fn authenticate(&self, message: &[u8]) -> Vec { - hmac_256::(&self.common_secret, message)[..16].to_vec() + Hmac::::mac(&self.common_secret, message)[..16].to_vec() } } -pub struct SharedSecretV2 { +pub struct SharedSecretV2 { aes_enc_key: crypto::aes256::EncryptionKey, hmac_key: [u8; 32], + // TODO: remove after porting AES to env crypto + phantom: PhantomData, } -impl SharedSecretV2 { +impl SharedSecretV2 { /// Creates a new shared secret from the handshake result. - fn new(handshake: [u8; 32]) -> SharedSecretV2 { - let aes_key = hkdf_empty_salt_256::(&handshake, b"CTAP2 AES key"); + fn new(handshake: [u8; 32]) -> Self { + let aes_key = hkdf_empty_salt_256::(&handshake, b"CTAP2 AES key"); SharedSecretV2 { aes_enc_key: crypto::aes256::EncryptionKey::new(&aes_key), - hmac_key: hkdf_empty_salt_256::(&handshake, b"CTAP2 HMAC key"), + hmac_key: hkdf_empty_salt_256::(&handshake, b"CTAP2 HMAC key"), + phantom: PhantomData, } } -} -impl SharedSecret for SharedSecretV2 { fn encrypt(&self, rng: &mut dyn Rng256, plaintext: &[u8]) -> Result, Ctap2StatusCode> { aes256_cbc_encrypt(rng, &self.aes_enc_key, plaintext, true) } @@ -226,12 +269,12 @@ impl SharedSecret for SharedSecretV2 { } fn verify(&self, message: &[u8], signature: &[u8]) -> Result<(), Ctap2StatusCode> { - verify_v2(&self.hmac_key, message, signature) + verify_v2::(&self.hmac_key, message, signature) } #[cfg(test)] fn authenticate(&self, message: &[u8]) -> Vec { - hmac_256::(&self.hmac_key, message).to_vec() + Hmac::::mac(&self.hmac_key, message).to_vec() } } @@ -263,7 +306,7 @@ mod test { #[test] fn test_shared_secret_v1_encrypt_decrypt() { let mut env = TestEnv::default(); - let shared_secret = SharedSecretV1::new([0x55; 32]); + let shared_secret = SharedSecretV1::::new([0x55; 32]); let plaintext = vec![0xAA; 64]; let ciphertext = shared_secret.encrypt(env.rng(), &plaintext).unwrap(); assert_eq!(shared_secret.decrypt(&ciphertext), Ok(plaintext)); @@ -271,7 +314,7 @@ mod test { #[test] fn test_shared_secret_v1_authenticate_verify() { - let shared_secret = SharedSecretV1::new([0x55; 32]); + let shared_secret = SharedSecretV1::::new([0x55; 32]); let message = [0xAA; 32]; let signature = shared_secret.authenticate(&message); assert_eq!(shared_secret.verify(&message, &signature), Ok(())); @@ -279,7 +322,7 @@ mod test { #[test] fn test_shared_secret_v1_verify() { - let shared_secret = SharedSecretV1::new([0x55; 32]); + let shared_secret = SharedSecretV1::::new([0x55; 32]); let message = [0xAA]; let signature = [ 0x8B, 0x60, 0x15, 0x7D, 0xF3, 0x44, 0x82, 0x2E, 0x54, 0x34, 0x7A, 0x01, 0xFB, 0x02, @@ -299,7 +342,7 @@ mod test { #[test] fn test_shared_secret_v2_encrypt_decrypt() { let mut env = TestEnv::default(); - let shared_secret = SharedSecretV2::new([0x55; 32]); + let shared_secret = SharedSecretV2::::new([0x55; 32]); let plaintext = vec![0xAA; 64]; let ciphertext = shared_secret.encrypt(env.rng(), &plaintext).unwrap(); assert_eq!(shared_secret.decrypt(&ciphertext), Ok(plaintext)); @@ -307,7 +350,7 @@ mod test { #[test] fn test_shared_secret_v2_authenticate_verify() { - let shared_secret = SharedSecretV2::new([0x55; 32]); + let shared_secret = SharedSecretV2::::new([0x55; 32]); let message = [0xAA; 32]; let signature = shared_secret.authenticate(&message); assert_eq!(shared_secret.verify(&message, &signature), Ok(())); @@ -315,7 +358,7 @@ mod test { #[test] fn test_shared_secret_v2_verify() { - let shared_secret = SharedSecretV2::new([0x55; 32]); + let shared_secret = SharedSecretV2::::new([0x55; 32]); let message = [0xAA]; let signature = [ 0xC0, 0x3F, 0x2A, 0x22, 0x5C, 0xC3, 0x4E, 0x05, 0xC1, 0x0E, 0x72, 0x9C, 0x8D, 0xD5, @@ -360,11 +403,16 @@ mod test { 0x49, 0x68, ]; assert_eq!( - verify_pin_uv_auth_token(&token, &message, &signature, PinUvAuthProtocol::V1), + verify_pin_uv_auth_token::( + &token, + &message, + &signature, + PinUvAuthProtocol::V1 + ), Ok(()) ); assert_eq!( - verify_pin_uv_auth_token( + verify_pin_uv_auth_token::( &[0x12; PIN_TOKEN_LENGTH], &message, &signature, @@ -373,11 +421,16 @@ mod test { Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID) ); assert_eq!( - verify_pin_uv_auth_token(&token, &[0xBB], &signature, PinUvAuthProtocol::V1), + verify_pin_uv_auth_token::(&token, &[0xBB], &signature, PinUvAuthProtocol::V1), Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID) ); assert_eq!( - verify_pin_uv_auth_token(&token, &message, &[0x12; 16], PinUvAuthProtocol::V1), + verify_pin_uv_auth_token::( + &token, + &message, + &[0x12; 16], + PinUvAuthProtocol::V1 + ), Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID) ); } @@ -392,11 +445,16 @@ mod test { 0x36, 0x93, 0xF7, 0x84, ]; assert_eq!( - verify_pin_uv_auth_token(&token, &message, &signature, PinUvAuthProtocol::V2), + verify_pin_uv_auth_token::( + &token, + &message, + &signature, + PinUvAuthProtocol::V2 + ), Ok(()) ); assert_eq!( - verify_pin_uv_auth_token( + verify_pin_uv_auth_token::( &[0x12; PIN_TOKEN_LENGTH], &message, &signature, @@ -405,11 +463,16 @@ mod test { Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID) ); assert_eq!( - verify_pin_uv_auth_token(&token, &[0xBB], &signature, PinUvAuthProtocol::V2), + verify_pin_uv_auth_token::(&token, &[0xBB], &signature, PinUvAuthProtocol::V2), Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID) ); assert_eq!( - verify_pin_uv_auth_token(&token, &message, &[0x12; 32], PinUvAuthProtocol::V2), + verify_pin_uv_auth_token::( + &token, + &message, + &[0x12; 32], + PinUvAuthProtocol::V2 + ), Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID) ); } diff --git a/libraries/opensk/src/ctap/token_state.rs b/libraries/opensk/src/ctap/token_state.rs index 795b259..041aee3 100644 --- a/libraries/opensk/src/ctap/token_state.rs +++ b/libraries/opensk/src/ctap/token_state.rs @@ -13,12 +13,11 @@ // limitations under the License. use crate::api::clock::Clock; +use crate::api::crypto::sha256::Sha256; use crate::ctap::client_pin::PinPermission; use crate::ctap::status_code::Ctap2StatusCode; -use crate::env::Env; +use crate::env::{Env, Sha}; use alloc::string::String; -use crypto::sha256::Sha256; -use crypto::Hash256; /// Timeout for auth tokens. /// @@ -87,7 +86,7 @@ impl PinUvAuthTokenState { /// Checks if the permissions RPID's association matches the hash. pub fn has_permissions_rp_id_hash(&self, rp_id_hash: &[u8]) -> Result<(), Ctap2StatusCode> { match &self.permissions_rp_id { - Some(p) if rp_id_hash == Sha256::hash(p.as_bytes()) => Ok(()), + Some(p) if rp_id_hash == Sha::::digest(p.as_bytes()) => Ok(()), _ => Err(Ctap2StatusCode::CTAP2_ERR_PIN_AUTH_INVALID), } } @@ -210,7 +209,7 @@ mod test { #[test] fn test_permissions_rp_id_none() { let mut token_state = PinUvAuthTokenState::::new(); - let example_hash = Sha256::hash(b"example.com"); + let example_hash = Sha::::digest(b"example.com"); token_state.set_permissions_rp_id(None); assert_eq!(token_state.has_no_permissions_rp_id(), Ok(())); assert_eq!( @@ -226,7 +225,7 @@ mod test { #[test] fn test_permissions_rp_id_some() { let mut token_state = PinUvAuthTokenState::::new(); - let example_hash = Sha256::hash(b"example.com"); + let example_hash = Sha::::digest(b"example.com"); token_state.set_permissions_rp_id(Some(String::from("example.com"))); assert_eq!( diff --git a/libraries/opensk/src/env/mod.rs b/libraries/opensk/src/env/mod.rs index f1836bf..9b0c5bc 100644 --- a/libraries/opensk/src/env/mod.rs +++ b/libraries/opensk/src/env/mod.rs @@ -33,6 +33,8 @@ pub type EcdhSk = <<::Crypto as Crypto>::Ecdh as Ecdh>::SecretKey; pub type EcdhPk = <<::Crypto as Crypto>::Ecdh as Ecdh>::PublicKey; pub type EcdsaSk = <<::Crypto as Crypto>::Ecdsa as Ecdsa>::SecretKey; pub type EcdsaPk = <<::Crypto as Crypto>::Ecdsa as Ecdsa>::PublicKey; +pub type Sha = <::Crypto as Crypto>::Sha256; +pub type Hmac = <::Crypto as Crypto>::Hmac256; /// Describes what CTAP needs to function. pub trait Env {