diff --git a/src/ctap/key_material.rs b/src/ctap/key_material.rs index 018cdfc..a8ae6da 100644 --- a/src/ctap/key_material.rs +++ b/src/ctap/key_material.rs @@ -14,9 +14,6 @@ pub const ATTESTATION_PRIVATE_KEY_LENGTH: usize = 32; pub const AAGUID_LENGTH: usize = 16; -pub const UPGRADE_PUBLIC_KEY_LENGTH: usize = 65; pub const AAGUID: &[u8; AAGUID_LENGTH] = include_bytes!(concat!(env!("OUT_DIR"), "/opensk_aaguid.bin")); -pub const UPGRADE_PUBLIC_KEY: &[u8; UPGRADE_PUBLIC_KEY_LENGTH] = - include_bytes!(concat!(env!("OUT_DIR"), "/opensk_upgrade_pubkey.bin")); diff --git a/src/ctap/mod.rs b/src/ctap/mod.rs index d5b1413..c440ef0 100644 --- a/src/ctap/mod.rs +++ b/src/ctap/mod.rs @@ -75,8 +75,7 @@ use alloc::boxed::Box; use alloc::string::{String, ToString}; use alloc::vec; use alloc::vec::Vec; -use arrayref::array_ref; -use byteorder::{BigEndian, ByteOrder, LittleEndian}; +use byteorder::{BigEndian, ByteOrder}; use core::convert::TryFrom; use crypto::hmac::hmac_256; use crypto::sha256::Sha256; @@ -209,71 +208,6 @@ fn truncate_to_char_boundary(s: &str, mut max: usize) -> &str { } } -/// Parses the metadata of an upgrade, and checks its correctness. -/// -/// The metadata is a page starting with: -/// - 32 B upgrade hash (SHA256) -/// - 64 B signature, -/// that are not signed over. The second part is included in the signature with -/// - 8 B version and -/// - 4 B partition address in little endian encoding -/// written at METADATA_SIGN_OFFSET. -/// -/// Checks hash and signature correctness, and whether the partition offset matches. -fn parse_metadata( - upgrade_locations: &impl UpgradeStorage, - public_key_bytes: &[u8], - metadata: &[u8], -) -> Result<(), Ctap2StatusCode> { - const METADATA_LEN: usize = 0x1000; - const METADATA_SIGN_OFFSET: usize = 0x800; - if metadata.len() != METADATA_LEN { - return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER); - } - - let metadata_address = LittleEndian::read_u32(&metadata[METADATA_SIGN_OFFSET + 8..][..4]); - if metadata_address as usize != upgrade_locations.partition_address() { - return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER); - } - - // The hash implementation handles this in chunks, so no memory issues. - let partition_slice = upgrade_locations - .read_partition(0, upgrade_locations.partition_length()) - .map_err(|_| Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?; - let mut hasher = Sha256::new(); - hasher.update(&metadata[METADATA_SIGN_OFFSET..]); - hasher.update(partition_slice); - let computed_hash = hasher.finalize(); - if &computed_hash != array_ref!(metadata, 0, 32) { - return Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE); - } - - verify_signature( - array_ref!(metadata, 32, 64), - public_key_bytes, - &computed_hash, - )?; - Ok(()) -} - -/// Verifies the signature over the given hash. -/// -/// The public key is COSE encoded, and the hash is a SHA256. -fn verify_signature( - signature_bytes: &[u8; 64], - public_key_bytes: &[u8], - signed_hash: &[u8; 32], -) -> Result<(), Ctap2StatusCode> { - let signature = ecdsa::Signature::from_bytes(signature_bytes) - .ok_or(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)?; - let public_key = ecdsa::PubKey::from_bytes_uncompressed(public_key_bytes) - .ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?; - if !public_key.verify_hash_vartime(signed_hash, &signature) { - return Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE); - } - Ok(()) -} - // Sends keepalive packet during user presence checking. If user agent replies with CANCEL response, // returns Err(UserPresenceError::Canceled). fn send_keepalive_up_needed( @@ -1001,7 +935,7 @@ impl CtapState { .attestation_store() .get(&id)? .ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?; - let attestation_key = crypto::ecdsa::SecKey::from_bytes(&private_key).unwrap(); + let attestation_key = ecdsa::SecKey::from_bytes(&private_key).unwrap(); ( attestation_key .sign_rfc6979::(&signature_data) @@ -1480,12 +1414,10 @@ impl CtapState { .read_partition(address, data.len()) .map_err(|_| Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)? } else { - // Compares the hash inside the metadata to the actual hash. - parse_metadata(upgrade_locations, key_material::UPGRADE_PUBLIC_KEY, &data)?; - // Write the metadata page after verifying that its hash is signed. + // Write the metadata page after verifying it. upgrade_locations .write_metadata(&data) - .map_err(|_| Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)?; + .map_err(|_| Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE)?; &upgrade_locations .read_metadata() .map_err(|_| Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)?[..data.len()] @@ -1561,6 +1493,7 @@ mod test { use crate::api::user_presence::UserPresenceResult; use crate::env::test::TestEnv; use crate::test_helpers; + use byteorder::LittleEndian; use cbor::{cbor_array, cbor_array_vec, cbor_map}; // The keep-alive logic in the processing of some commands needs a channel ID to send @@ -3464,109 +3397,13 @@ mod test { ); } - #[test] - fn test_parse_metadata() { - let mut env = TestEnv::new(); - let private_key = crypto::ecdsa::SecKey::gensk(env.rng()); - let upgrade_locations = env.upgrade_storage().unwrap(); - - const METADATA_LEN: usize = 0x1000; - const METADATA_SIGN_OFFSET: usize = 0x800; - let mut metadata = vec![0xFF; METADATA_LEN]; - LittleEndian::write_u32(&mut metadata[METADATA_SIGN_OFFSET + 8..][..4], 0x60000); - - let partition_length = upgrade_locations.partition_length(); - let mut signed_over_data = metadata[METADATA_SIGN_OFFSET..].to_vec(); - signed_over_data.extend( - upgrade_locations - .read_partition(0, partition_length) - .unwrap(), - ); - let signed_hash = Sha256::hash(&signed_over_data); - - metadata[..32].copy_from_slice(&signed_hash); - let signature = private_key.sign_rfc6979::(&signed_over_data); - let mut signature_bytes = [0; ecdsa::Signature::BYTES_LENGTH]; - signature.to_bytes(&mut signature_bytes); - metadata[32..96].copy_from_slice(&signature_bytes); - - let public_key = private_key.genpk(); - let mut public_key_bytes = [0; 65]; - public_key.to_bytes_uncompressed(&mut public_key_bytes); - - assert_eq!( - parse_metadata(upgrade_locations, &public_key_bytes, &metadata), - Ok(()) - ); - // Any manipulation of data fails. - metadata[METADATA_SIGN_OFFSET] = 0x88; - assert_eq!( - parse_metadata(upgrade_locations, &public_key_bytes, &metadata), - Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE) - ); - metadata[METADATA_SIGN_OFFSET] = 0xFF; - metadata[0] ^= 0x01; - assert_eq!( - parse_metadata(upgrade_locations, &public_key_bytes, &metadata), - Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE) - ); - metadata[0] ^= 0x01; - metadata[32] ^= 0x01; - assert_eq!( - parse_metadata(upgrade_locations, &public_key_bytes, &metadata), - Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE) - ); - metadata[32] ^= 0x01; - upgrade_locations.write_partition(0, &[0x88; 1]).unwrap(); - assert_eq!( - parse_metadata(upgrade_locations, &public_key_bytes, &metadata), - Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE) - ); - } - - #[test] - fn test_verify_signature() { - let mut env = TestEnv::new(); - let private_key = crypto::ecdsa::SecKey::gensk(env.rng()); - let message = [0x44; 64]; - let signed_hash = Sha256::hash(&message); - let signature = private_key.sign_rfc6979::(&message); - - let mut signature_bytes = [0; ecdsa::Signature::BYTES_LENGTH]; - signature.to_bytes(&mut signature_bytes); - - let public_key = private_key.genpk(); - let mut public_key_bytes = [0; 65]; - public_key.to_bytes_uncompressed(&mut public_key_bytes); - - assert_eq!( - verify_signature(&signature_bytes, &public_key_bytes, &signed_hash), - Ok(()) - ); - assert_eq!( - verify_signature(&signature_bytes, &public_key_bytes, &[0x55; 32]), - Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE) - ); - public_key_bytes[0] ^= 0x01; - assert_eq!( - verify_signature(&signature_bytes, &public_key_bytes, &signed_hash), - Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR) - ); - public_key_bytes[0] ^= 0x01; - signature_bytes[0] ^= 0x01; - assert_eq!( - verify_signature(&signature_bytes, &public_key_bytes, &signed_hash), - Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE) - ); - } - #[test] fn test_vendor_upgrade() { // The test partition storage has size 0x40000. // The test metadata storage has size 0x1000. // The test identifier matches partition B. let mut env = TestEnv::new(); - let private_key = crypto::ecdsa::SecKey::gensk(env.rng()); + let private_key = ecdsa::SecKey::gensk(env.rng()); let mut ctap_state = CtapState::new(&mut env, CtapInstant::new(0)); const METADATA_LEN: usize = 0x1000; const METADATA_SIGN_OFFSET: usize = 0x800; @@ -3603,8 +3440,7 @@ mod test { ); assert_eq!(response, Ok(ResponseData::AuthenticatorVendorUpgrade)); - // We can't inject a public key for our known private key, so the last upgrade step fails. - // verify_signature is separately tested for that reason. + // TestEnv doesn't check the metadata, test parsing that in your Env. let response = ctap_state.process_vendor_upgrade( &mut env, AuthenticatorVendorUpgradeParameters { @@ -3613,7 +3449,7 @@ mod test { hash: metadata_hash.clone(), }, ); - assert_eq!(response, Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE)); + assert_eq!(response, Ok(ResponseData::AuthenticatorVendorUpgrade)); // Write metadata of a wrong size. let response = ctap_state.process_vendor_upgrade( @@ -3624,7 +3460,7 @@ mod test { hash: metadata_hash, }, ); - assert_eq!(response, Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)); + assert_eq!(response, Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE)); // Write outside of the partition. let response = ctap_state.process_vendor_upgrade( diff --git a/src/env/tock/storage.rs b/src/env/tock/storage.rs index 9c28459..a8ba7db 100644 --- a/src/env/tock/storage.rs +++ b/src/env/tock/storage.rs @@ -16,12 +16,19 @@ use crate::api::upgrade_storage::helper::{find_slice, is_aligned, ModRange}; use crate::api::upgrade_storage::UpgradeStorage; use alloc::borrow::Cow; use alloc::vec::Vec; +use arrayref::array_ref; +use byteorder::{ByteOrder, LittleEndian}; use core::cell::Cell; +use crypto::sha256::Sha256; +use crypto::{ecdsa, Hash256}; use libtock_core::{callback, syscalls}; use persistent_store::{Storage, StorageError, StorageIndex, StorageResult}; const DRIVER_NUMBER: usize = 0x50003; +const UPGRADE_PUBLIC_KEY: &[u8; 65] = + include_bytes!(concat!(env!("OUT_DIR"), "/opensk_upgrade_pubkey.bin")); + mod subscribe_nr { pub const DONE: usize = 0; } @@ -331,14 +338,192 @@ impl UpgradeStorage for TockUpgradeStorage { } fn write_metadata(&mut self, data: &[u8]) -> StorageResult<()> { - // If less data is passed in than is reserved, assume the rest is 0xFF. - if data.len() <= self.metadata.length() { - for address in self.metadata.aligned_iter(self.page_size) { - erase_page(address, self.page_size)?; - } - write_slice(self.metadata.start(), data) - } else { - Err(StorageError::OutOfBounds) + if data.len() != self.metadata.length() { + return Err(StorageError::CustomError); } + // Compares the hash inside the metadata to the actual hash. + parse_metadata(self, UPGRADE_PUBLIC_KEY, &data)?; + for address in self.metadata.aligned_iter(self.page_size) { + erase_page(address, self.page_size)?; + } + write_slice(self.metadata.start(), data) + } +} + +/// Parses the metadata of an upgrade, and checks its correctness. +/// +/// The metadata is a page starting with: +/// - 32 B upgrade hash (SHA256) +/// - 64 B signature, +/// that are not signed over. The second part is included in the signature with +/// - 8 B version and +/// - 4 B partition address in little endian encoding +/// written at METADATA_SIGN_OFFSET. +/// +/// Checks hash and signature correctness, and whether the partition offset matches. +fn parse_metadata( + upgrade_locations: &impl UpgradeStorage, + public_key_bytes: &[u8], + metadata: &[u8], +) -> StorageResult<()> { + const METADATA_LEN: usize = 0x1000; + const METADATA_SIGN_OFFSET: usize = 0x800; + if metadata.len() != METADATA_LEN { + return Err(StorageError::CustomError); + } + + let metadata_address = LittleEndian::read_u32(&metadata[METADATA_SIGN_OFFSET + 8..][..4]); + if metadata_address as usize != upgrade_locations.partition_address() { + return Err(StorageError::CustomError); + } + + // The hash implementation handles this in chunks, so no memory issues. + let partition_slice = + upgrade_locations.read_partition(0, upgrade_locations.partition_length())?; + let mut hasher = Sha256::new(); + hasher.update(&metadata[METADATA_SIGN_OFFSET..]); + hasher.update(partition_slice); + let computed_hash = hasher.finalize(); + if &computed_hash != array_ref!(metadata, 0, 32) { + return Err(StorageError::CustomError); + } + + verify_signature( + array_ref!(metadata, 32, 64), + public_key_bytes, + &computed_hash, + )?; + Ok(()) +} + +/// Verifies the signature over the given hash. +/// +/// The public key is COSE encoded, and the hash is a SHA256. +fn verify_signature( + signature_bytes: &[u8; 64], + public_key_bytes: &[u8], + signed_hash: &[u8; 32], +) -> StorageResult<()> { + let signature = + ecdsa::Signature::from_bytes(signature_bytes).ok_or(StorageError::CustomError)?; + let public_key = ecdsa::PubKey::from_bytes_uncompressed(public_key_bytes) + .ok_or(StorageError::CustomError)?; + if !public_key.verify_hash_vartime(signed_hash, &signature) { + return Err(StorageError::CustomError); + } + Ok(()) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::env::test::TestEnv; + use crate::env::Env; + + #[test] + fn test_parse_metadata() { + let mut env = TestEnv::new(); + let private_key = crypto::ecdsa::SecKey::gensk(env.rng()); + let upgrade_locations = env.upgrade_storage().unwrap(); + + const METADATA_LEN: usize = 0x1000; + const METADATA_SIGN_OFFSET: usize = 0x800; + let mut metadata = vec![0xFF; METADATA_LEN]; + LittleEndian::write_u32(&mut metadata[METADATA_SIGN_OFFSET + 8..][..4], 0x60000); + + let partition_length = upgrade_locations.partition_length(); + let mut signed_over_data = metadata[METADATA_SIGN_OFFSET..].to_vec(); + signed_over_data.extend( + upgrade_locations + .read_partition(0, partition_length) + .unwrap(), + ); + let signed_hash = Sha256::hash(&signed_over_data); + + metadata[..32].copy_from_slice(&signed_hash); + let signature = private_key.sign_rfc6979::(&signed_over_data); + let mut signature_bytes = [0; ecdsa::Signature::BYTES_LENGTH]; + signature.to_bytes(&mut signature_bytes); + metadata[32..96].copy_from_slice(&signature_bytes); + + let public_key = private_key.genpk(); + let mut public_key_bytes = [0; 65]; + public_key.to_bytes_uncompressed(&mut public_key_bytes); + + assert_eq!( + parse_metadata(upgrade_locations, &public_key_bytes, &metadata), + Ok(()) + ); + + // Manipulating the partition address fails. + metadata[METADATA_SIGN_OFFSET] = 0x88; + assert_eq!( + parse_metadata(upgrade_locations, &public_key_bytes, &metadata), + Err(StorageError::CustomError) + ); + metadata[METADATA_SIGN_OFFSET] = 0x00; + // Any manipulation of signed data fails. + metadata[METADATA_LEN - 1] = 0x88; + assert_eq!( + parse_metadata(upgrade_locations, &public_key_bytes, &metadata), + Err(StorageError::CustomError) + ); + metadata[METADATA_LEN - 1] = 0xFF; + // Manipulating the hash fails. + metadata[0] ^= 0x01; + assert_eq!( + parse_metadata(upgrade_locations, &public_key_bytes, &metadata), + Err(StorageError::CustomError) + ); + metadata[0] ^= 0x01; + // Manipulating the signature fails. + metadata[32] ^= 0x01; + assert_eq!( + parse_metadata(upgrade_locations, &public_key_bytes, &metadata), + Err(StorageError::CustomError) + ); + metadata[32] ^= 0x01; + // Manipulating the partition data fails. + upgrade_locations.write_partition(0, &[0x88; 1]).unwrap(); + assert_eq!( + parse_metadata(upgrade_locations, &public_key_bytes, &metadata), + Err(StorageError::CustomError) + ); + } + + #[test] + fn test_verify_signature() { + let mut env = TestEnv::new(); + let private_key = crypto::ecdsa::SecKey::gensk(env.rng()); + let message = [0x44; 64]; + let signed_hash = Sha256::hash(&message); + let signature = private_key.sign_rfc6979::(&message); + + let mut signature_bytes = [0; ecdsa::Signature::BYTES_LENGTH]; + signature.to_bytes(&mut signature_bytes); + + let public_key = private_key.genpk(); + let mut public_key_bytes = [0; 65]; + public_key.to_bytes_uncompressed(&mut public_key_bytes); + + assert_eq!( + verify_signature(&signature_bytes, &public_key_bytes, &signed_hash), + Ok(()) + ); + assert_eq!( + verify_signature(&signature_bytes, &public_key_bytes, &[0x55; 32]), + Err(StorageError::CustomError) + ); + public_key_bytes[0] ^= 0x01; + assert_eq!( + verify_signature(&signature_bytes, &public_key_bytes, &signed_hash), + Err(StorageError::CustomError) + ); + public_key_bytes[0] ^= 0x01; + signature_bytes[0] ^= 0x01; + assert_eq!( + verify_signature(&signature_bytes, &public_key_bytes, &signed_hash), + Err(StorageError::CustomError) + ); } }