// Copyright 2021 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 crate::ctap::data_formats::{ extract_array, extract_byte_string, CoseKey, PublicKeyCredentialSource, PublicKeyCredentialType, SignatureAlgorithm, ES256_ALGORITHM, EDDSA_ALGORITHM, }; use crate::ctap::status_code::Ctap2StatusCode; use crate::ctap::storage; use crate::env::Env; use alloc::string::String; use alloc::vec; use alloc::vec::Vec; use core::convert::TryFrom; use crypto::cbc::{cbc_decrypt, cbc_encrypt}; use crypto::ecdsa; use crypto::hmac::{hmac_256, verify_hmac_256}; use crypto::sha256::Sha256; use rng256::Rng256; use sk_cbor as cbor; use sk_cbor::{cbor_array, cbor_bytes, cbor_int}; #[cfg(feature = "ed25519")] use ed25519_dalek::Signer; // Legacy credential IDs consist of // - 16 bytes: initialization vector for AES-256, // - 32 bytes: ECDSA private key for the credential, // - 32 bytes: relying party ID hashed with SHA256, // - 32 bytes: HMAC-SHA256 over everything else. pub const LEGACY_CREDENTIAL_ID_SIZE: usize = 112; #[cfg(test)] pub const ECDSA_CREDENTIAL_ID_SIZE: usize = 113; // See encrypt_key_handle v1 documentation. pub const MAX_CREDENTIAL_ID_SIZE: usize = 113; const ECDSA_CREDENTIAL_ID_VERSION: u8 = 0x01; #[cfg(feature = "ed25519")] const ED25519_CREDENTIAL_ID_VERSION: u8 = 0x02; #[cfg(test)] #[cfg(feature = "ed25519")] const UNSUPPORTED_CREDENTIAL_ID_VERSION: u8 = 0x03; #[cfg(test)] #[cfg(not(feature = "ed25519"))] const UNSUPPORTED_CREDENTIAL_ID_VERSION: u8 = 0x02; /// Wraps the AES256-CBC encryption to match what we need in CTAP. pub fn aes256_cbc_encrypt( rng: &mut dyn Rng256, aes_enc_key: &crypto::aes256::EncryptionKey, plaintext: &[u8], embeds_iv: bool, ) -> Result, Ctap2StatusCode> { if plaintext.len() % 16 != 0 { return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER); } // The extra 1 capacity is because encrypt_key_handle adds a version number. let mut ciphertext = Vec::with_capacity(plaintext.len() + 16 * embeds_iv as usize + 1); let iv = if embeds_iv { let random_bytes = rng.gen_uniform_u8x32(); ciphertext.extend_from_slice(&random_bytes[..16]); *array_ref!(ciphertext, 0, 16) } else { [0u8; 16] }; let start = ciphertext.len(); ciphertext.extend_from_slice(plaintext); cbc_encrypt(aes_enc_key, iv, &mut ciphertext[start..]); Ok(ciphertext) } /// Wraps the AES256-CBC decryption to match what we need in CTAP. pub fn aes256_cbc_decrypt( aes_enc_key: &crypto::aes256::EncryptionKey, ciphertext: &[u8], embeds_iv: bool, ) -> Result, Ctap2StatusCode> { if ciphertext.len() % 16 != 0 || (embeds_iv && ciphertext.is_empty()) { return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER); } let (iv, ciphertext) = if embeds_iv { let (iv, ciphertext) = ciphertext.split_at(16); (*array_ref!(iv, 0, 16), ciphertext) } else { ([0u8; 16], ciphertext) }; let mut plaintext = ciphertext.to_vec(); let aes_dec_key = crypto::aes256::DecryptionKey::new(aes_enc_key); cbc_decrypt(&aes_dec_key, iv, &mut plaintext); Ok(plaintext) } /// An asymmetric private key that can sign messages. #[derive(Debug)] pub enum PrivateKey { Ecdsa(ecdsa::SecKey), #[cfg(feature = "ed25519")] Ed25519(ed25519_dalek::Keypair), } impl Clone for PrivateKey { fn clone(&self) -> Self { match self { Self::Ecdsa(sk) => Self::Ecdsa (sk.clone ()), #[cfg(feature = "ed25519")] Self::Ed25519(keypair) => Self::Ed25519 (ed25519_dalek::Keypair::from_bytes (&keypair.to_bytes()).unwrap()), } } } impl PartialEq for PrivateKey { fn eq(&self, other: &Self) -> bool { match (self, other) { (&Self::Ecdsa(ref a), &Self::Ecdsa(ref b)) => a == b, #[cfg(feature = "ed25519")] (&Self::Ed25519(ref a), &Self::Ed25519(ref b)) => a.to_bytes() == b.to_bytes(), #[cfg(feature = "ed25519")] _ => false, } } } impl Eq for PrivateKey {} impl PrivateKey { /// Creates a new private key for the given algorithm. /// /// # Panics /// /// Panics if the algorithm is [`SignatureAlgorithm::Unknown`]. pub fn new(rng: &mut impl Rng256, alg: SignatureAlgorithm) -> Self { match alg { SignatureAlgorithm::ES256 => PrivateKey::Ecdsa(crypto::ecdsa::SecKey::gensk(rng)), #[cfg(feature = "ed25519")] SignatureAlgorithm::EDDSA => { let bytes = rng.gen_uniform_u8x32(); Self::new_ed25519_from_bytes(&bytes).unwrap() }, SignatureAlgorithm::Unknown => unreachable!(), } } /// Helper function that creates a private key of type ECDSA. /// /// This function is public for legacy credential source parsing only. pub fn new_ecdsa_from_bytes(bytes: &[u8]) -> Option { if bytes.len() != 32 { return None; } ecdsa::SecKey::from_bytes(array_ref!(bytes, 0, 32)).map(PrivateKey::from) } #[cfg(feature = "ed25519")] pub fn new_ed25519_from_bytes(bytes: &[u8]) -> Option { if bytes.len() != 32 { return None; } if let Ok(secret) = ed25519_dalek::SecretKey::from_bytes(bytes) { let public = ed25519_dalek::PublicKey::from (&secret); Some(Self::Ed25519(ed25519_dalek::Keypair{secret, public})) } else { None } } /// Returns the corresponding public key. pub fn get_pub_key(&self) -> CoseKey { match self { PrivateKey::Ecdsa(ecdsa_key) => CoseKey::from(ecdsa_key.genpk()), #[cfg(feature = "ed25519")] PrivateKey::Ed25519(ed25519_keypair) => CoseKey::from(ed25519_keypair.public), } } /// Returns the encoded signature for a given message. pub fn sign_and_encode(&self, message: &[u8]) -> Vec { match self { PrivateKey::Ecdsa(ecdsa_key) => ecdsa_key.sign_rfc6979::(message).to_asn1_der(), #[cfg(feature = "ed25519")] PrivateKey::Ed25519(ed25519_keypair) => ed25519_keypair.try_sign(message).unwrap().to_bytes().to_vec(), } } /// The associated COSE signature algorithm identifier. pub fn signature_algorithm(&self) -> SignatureAlgorithm { match self { PrivateKey::Ecdsa(_) => SignatureAlgorithm::ES256, #[cfg(feature = "ed25519")] PrivateKey::Ed25519(_) => SignatureAlgorithm::EDDSA, } } /// Writes the key bytes. pub fn to_bytes(&self) -> Vec { match self { PrivateKey::Ecdsa(ecdsa_key) => { let mut key_bytes = vec![0u8; 32]; ecdsa_key.to_bytes(array_mut_ref!(key_bytes, 0, 32)); key_bytes } #[cfg(feature = "ed25519")] PrivateKey::Ed25519(ed25519_keypair) => ed25519_keypair.secret.to_bytes().to_vec(), } } } impl From for cbor::Value { fn from(private_key: PrivateKey) -> Self { cbor_array![ cbor_int!(private_key.signature_algorithm() as i64), cbor_bytes!(private_key.to_bytes()), ] } } impl TryFrom for PrivateKey { type Error = Ctap2StatusCode; fn try_from(cbor_value: cbor::Value) -> Result { let mut array = extract_array(cbor_value)?; if array.len() != 2 { return Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR); } let key_bytes = extract_byte_string(array.pop().unwrap())?; match SignatureAlgorithm::try_from(array.pop().unwrap())? { SignatureAlgorithm::ES256 => PrivateKey::new_ecdsa_from_bytes(&key_bytes) .ok_or(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR), #[cfg(feature = "ed25519")] SignatureAlgorithm::EDDSA => PrivateKey::new_ed25519_from_bytes(&key_bytes) .ok_or(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR), _ => Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR), } } } impl From for PrivateKey { fn from(ecdsa_key: ecdsa::SecKey) -> Self { PrivateKey::Ecdsa(ecdsa_key) } } /// Encrypts the given private key and relying party ID hash into a credential ID. /// /// Other information, such as a user name, are not stored. Since encrypted credential IDs are /// stored server-side, this information is already available (unencrypted). /// /// Also, by limiting ourselves to private key and RP ID hash, we are compatible with U2F for /// ECDSA private keys. /// /// For v1 we write the following data for ECDSA (algorithm -7): /// - 1 byte : version number /// - 16 bytes: initialization vector for AES-256, /// - 32 bytes: ECDSA private key for the credential, /// - 32 bytes: relying party ID hashed with SHA256, /// - 32 bytes: HMAC-SHA256 over everything else. /// /// For v2 we write the following data for EdDSA over curve Ed25519 (algorithm -8, curve 6): /// - 1 byte : version number /// - 16 bytes: initialization vector for AES-256, /// - 32 bytes: Ed25519 private key for the credential, /// - 32 bytes: relying party ID hashed with SHA256, /// - 32 bytes: HMAC-SHA256 over everything else. pub fn encrypt_key_handle( env: &mut impl Env, private_key: &PrivateKey, application: &[u8; 32], ) -> Result, Ctap2StatusCode> { let master_keys = storage::master_keys(env)?; let aes_enc_key = crypto::aes256::EncryptionKey::new(&master_keys.encryption); let mut plaintext = [0; 64]; let version = match private_key { PrivateKey::Ecdsa(ecdsa_key) => { ecdsa_key.to_bytes(array_mut_ref!(plaintext, 0, 32)); ECDSA_CREDENTIAL_ID_VERSION } #[cfg(feature = "ed25519")] PrivateKey::Ed25519(keypair) => { plaintext[0..32].copy_from_slice(&keypair.secret.to_bytes()); ED25519_CREDENTIAL_ID_VERSION } }; plaintext[32..64].copy_from_slice(application); let mut encrypted_id = aes256_cbc_encrypt(env.rng(), &aes_enc_key, &plaintext, true)?; encrypted_id.insert(0, version); let id_hmac = hmac_256::(&master_keys.hmac, &encrypted_id[..]); encrypted_id.extend(&id_hmac); Ok(encrypted_id) } /// Decrypts a credential ID and writes the private key into a PublicKeyCredentialSource. /// /// Returns None if /// - the format does not match any known versions, /// - the HMAC test fails or /// - the relying party does not match the decrypted relying party ID hash. /// /// This functions reads: /// - legacy credentials (no version number), /// - v1 (ECDSA) /// - v2 (EdDSA over curve Ed25519) pub fn decrypt_credential_source( env: &mut impl Env, credential_id: Vec, rp_id_hash: &[u8], ) -> Result, Ctap2StatusCode> { if credential_id.len() < LEGACY_CREDENTIAL_ID_SIZE { return Ok(None); } let master_keys = storage::master_keys(env)?; let hmac_message_size = credential_id.len() - 32; if !verify_hmac_256::( &master_keys.hmac, &credential_id[..hmac_message_size], array_ref![credential_id, hmac_message_size, 32], ) { return Ok(None); } let (payload, algorithm) = if credential_id.len() == LEGACY_CREDENTIAL_ID_SIZE { (&credential_id[..hmac_message_size], ES256_ALGORITHM) } else { // Version number check let algorithm = match credential_id[0] { ECDSA_CREDENTIAL_ID_VERSION => ES256_ALGORITHM, #[cfg(feature = "ed25519")] ED25519_CREDENTIAL_ID_VERSION => EDDSA_ALGORITHM, _ => return Ok(None), }; (&credential_id[1..hmac_message_size], algorithm) }; if payload.len() != 80 { // We shouldn't have HMAC'ed anything of different length. The check is cheap though. return Ok(None); } let aes_enc_key = crypto::aes256::EncryptionKey::new(&master_keys.encryption); let decrypted_id = aes256_cbc_decrypt(&aes_enc_key, payload, true)?; if rp_id_hash != &decrypted_id[32..] { return Ok(None); } let sk_option = match algorithm { ES256_ALGORITHM => PrivateKey::new_ecdsa_from_bytes(&decrypted_id[..32]), #[cfg(feature = "ed25519")] EDDSA_ALGORITHM => PrivateKey::new_ed25519_from_bytes(&decrypted_id[..32]), #[cfg(not(feature = "ed25519"))] EDDSA_ALGORITHM => return Ok(None), _ => return Ok(None), }; Ok(sk_option.map(|sk| PublicKeyCredentialSource { key_type: PublicKeyCredentialType::PublicKey, credential_id, private_key: sk, rp_id: String::from(""), user_handle: vec![], user_display_name: None, cred_protect_policy: None, creation_order: 0, user_name: None, user_icon: None, cred_blob: None, large_blob_key: None, })) } #[cfg(test)] mod test { use super::*; use crate::env::test::TestEnv; #[test] fn test_encrypt_decrypt_with_iv() { let mut env = TestEnv::new(); let aes_enc_key = crypto::aes256::EncryptionKey::new(&[0xC2; 32]); let plaintext = vec![0xAA; 64]; let ciphertext = aes256_cbc_encrypt(env.rng(), &aes_enc_key, &plaintext, true).unwrap(); let decrypted = aes256_cbc_decrypt(&aes_enc_key, &ciphertext, true).unwrap(); assert_eq!(decrypted, plaintext); } #[test] fn test_encrypt_decrypt_without_iv() { let mut env = TestEnv::new(); let aes_enc_key = crypto::aes256::EncryptionKey::new(&[0xC2; 32]); let plaintext = vec![0xAA; 64]; let ciphertext = aes256_cbc_encrypt(env.rng(), &aes_enc_key, &plaintext, false).unwrap(); let decrypted = aes256_cbc_decrypt(&aes_enc_key, &ciphertext, false).unwrap(); assert_eq!(decrypted, plaintext); } #[test] fn test_correct_iv_usage() { let mut env = TestEnv::new(); let aes_enc_key = crypto::aes256::EncryptionKey::new(&[0xC2; 32]); let plaintext = vec![0xAA; 64]; let mut ciphertext_no_iv = aes256_cbc_encrypt(env.rng(), &aes_enc_key, &plaintext, false).unwrap(); let mut ciphertext_with_iv = vec![0u8; 16]; ciphertext_with_iv.append(&mut ciphertext_no_iv); let decrypted = aes256_cbc_decrypt(&aes_enc_key, &ciphertext_with_iv, true).unwrap(); assert_eq!(decrypted, plaintext); } #[test] fn test_iv_manipulation_property() { let mut env = TestEnv::new(); let aes_enc_key = crypto::aes256::EncryptionKey::new(&[0xC2; 32]); let plaintext = vec![0xAA; 64]; let mut ciphertext = aes256_cbc_encrypt(env.rng(), &aes_enc_key, &plaintext, true).unwrap(); let mut expected_plaintext = plaintext; for i in 0..16 { ciphertext[i] ^= 0xBB; expected_plaintext[i] ^= 0xBB; } let decrypted = aes256_cbc_decrypt(&aes_enc_key, &ciphertext, true).unwrap(); assert_eq!(decrypted, expected_plaintext); } #[test] fn test_chaining() { let mut env = TestEnv::new(); let aes_enc_key = crypto::aes256::EncryptionKey::new(&[0xC2; 32]); let plaintext = vec![0xAA; 64]; let ciphertext1 = aes256_cbc_encrypt(env.rng(), &aes_enc_key, &plaintext, true).unwrap(); let ciphertext2 = aes256_cbc_encrypt(env.rng(), &aes_enc_key, &plaintext, true).unwrap(); assert_eq!(ciphertext1.len(), 80); assert_eq!(ciphertext2.len(), 80); // The ciphertext should mutate in all blocks with a different IV. let block_iter1 = ciphertext1.chunks_exact(16); let block_iter2 = ciphertext2.chunks_exact(16); for (block1, block2) in block_iter1.zip(block_iter2) { assert_ne!(block1, block2); } } #[test] fn test_new_ecdsa_from_bytes() { let mut env = TestEnv::new(); let private_key = PrivateKey::new(env.rng(), SignatureAlgorithm::ES256); let key_bytes = private_key.to_bytes(); assert_eq!( PrivateKey::new_ecdsa_from_bytes(&key_bytes), Some(private_key) ); } #[test] #[cfg(feature = "ed25519")] fn test_new_ed25519_from_bytes() { let mut env = TestEnv::new(); let private_key = PrivateKey::new(env.rng(), SignatureAlgorithm::EDDSA); let key_bytes = private_key.to_bytes(); assert_eq!( PrivateKey::new_ed25519_from_bytes(&key_bytes), Some(private_key) ); } #[test] fn test_new_ecdsa_from_bytes_wrong_length() { assert_eq!(PrivateKey::new_ecdsa_from_bytes(&[0x55; 16]), None); assert_eq!(PrivateKey::new_ecdsa_from_bytes(&[0x55; 31]), None); assert_eq!(PrivateKey::new_ecdsa_from_bytes(&[0x55; 33]), None); assert_eq!(PrivateKey::new_ecdsa_from_bytes(&[0x55; 64]), None); } #[test] #[cfg(feature = "ed25519")] fn test_new_ed25519_from_bytes_wrong_length() { assert_eq!(PrivateKey::new_ed25519_from_bytes(&[0x55; 16]), None); assert_eq!(PrivateKey::new_ed25519_from_bytes(&[0x55; 31]), None); assert_eq!(PrivateKey::new_ed25519_from_bytes(&[0x55; 33]), None); assert_eq!(PrivateKey::new_ed25519_from_bytes(&[0x55; 64]), None); } #[test] fn test_private_key_get_pub_key() { let mut env = TestEnv::new(); let ecdsa_key = crypto::ecdsa::SecKey::gensk(env.rng()); let public_key = ecdsa_key.genpk(); let private_key = PrivateKey::from(ecdsa_key); assert_eq!(private_key.get_pub_key(), CoseKey::from(public_key)); } #[test] fn test_private_key_sign_and_encode() { let mut env = TestEnv::new(); let message = [0x5A; 32]; let ecdsa_key = crypto::ecdsa::SecKey::gensk(env.rng()); let signature = ecdsa_key.sign_rfc6979::(&message).to_asn1_der(); let private_key = PrivateKey::from(ecdsa_key); assert_eq!(private_key.sign_and_encode(&message), signature); } fn test_private_key_signature_algorithm(signature_algorithm: SignatureAlgorithm) { let mut env = TestEnv::new(); let private_key = PrivateKey::new(env.rng(), signature_algorithm); assert_eq!(private_key.signature_algorithm(), signature_algorithm); } #[test] fn test_ecdsa_private_key_signature_algorithm() { test_private_key_signature_algorithm(SignatureAlgorithm::ES256); } #[test] #[cfg(feature = "ed25519")] fn test_ed25519_private_key_signature_algorithm() { test_private_key_signature_algorithm(SignatureAlgorithm::EDDSA); } fn test_private_key_from_to_cbor(signature_algorithm: SignatureAlgorithm) { let mut env = TestEnv::new(); let private_key = PrivateKey::new(env.rng(), signature_algorithm); let cbor = cbor::Value::from(private_key.clone()); assert_eq!(PrivateKey::try_from(cbor), Ok(private_key),); } #[test] fn test_ecdsa_private_key_from_to_cbor() { test_private_key_from_to_cbor(SignatureAlgorithm::ES256); } #[test] #[cfg(feature = "ed25519")] fn test_ed25519_private_key_from_to_cbor() { test_private_key_from_to_cbor(SignatureAlgorithm::EDDSA); } #[test] fn test_private_key_from_bad_cbor() { let cbor = cbor_array![ cbor_int!(SignatureAlgorithm::ES256 as i64), cbor_bytes!(vec![0x88; 32]), // The array is too long. cbor_int!(0), ]; assert_eq!( PrivateKey::try_from(cbor), Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR), ); #[cfg(feature = "ed25519")] { let cbor = cbor_array![ cbor_int!(SignatureAlgorithm::EDDSA as i64), cbor_bytes!(vec![0x88; 32]), // The array is too long. cbor_int!(0), ]; assert_eq!( PrivateKey::try_from(cbor), Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR), ); } let cbor = cbor_array![ // This algorithms doesn't exist. cbor_int!(-1), cbor_bytes!(vec![0x88; 32]), ]; assert_eq!( PrivateKey::try_from(cbor), Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR), ); } fn test_encrypt_decrypt_credential(signature_algorithm: SignatureAlgorithm) { let mut env = TestEnv::new(); storage::init(&mut env).ok().unwrap(); let private_key = PrivateKey::new(env.rng(), signature_algorithm); let rp_id_hash = [0x55; 32]; let encrypted_id = encrypt_key_handle(&mut env, &private_key, &rp_id_hash).unwrap(); let decrypted_source = decrypt_credential_source(&mut env, encrypted_id, &rp_id_hash) .unwrap() .unwrap(); assert_eq!(private_key, decrypted_source.private_key); } #[test] fn test_encrypt_decrypt_ecdsa_credential() { test_encrypt_decrypt_credential(SignatureAlgorithm::ES256); } #[test] #[cfg(feature = "ed25519")] fn test_encrypt_decrypt_ed25519_credential() { test_encrypt_decrypt_credential(SignatureAlgorithm::EDDSA); } #[test] fn test_encrypt_decrypt_bad_version() { let mut env = TestEnv::new(); storage::init(&mut env).ok().unwrap(); let private_key = PrivateKey::new(env.rng(), SignatureAlgorithm::ES256); let rp_id_hash = [0x55; 32]; let mut encrypted_id = encrypt_key_handle(&mut env, &private_key, &rp_id_hash).unwrap(); // Version 2 does not exist yet. encrypted_id[0] = UNSUPPORTED_CREDENTIAL_ID_VERSION; // Override the HMAC to pass the check. encrypted_id.truncate(&encrypted_id.len() - 32); let master_keys = storage::master_keys(&mut env).unwrap(); let id_hmac = hmac_256::(&master_keys.hmac, &encrypted_id[..]); encrypted_id.extend(&id_hmac); assert_eq!( decrypt_credential_source(&mut env, encrypted_id, &rp_id_hash), Ok(None) ); } fn test_encrypt_decrypt_bad_hmac(signature_algorithm: SignatureAlgorithm) { let mut env = TestEnv::new(); storage::init(&mut env).ok().unwrap(); let private_key = PrivateKey::new(env.rng(), signature_algorithm); let rp_id_hash = [0x55; 32]; let encrypted_id = encrypt_key_handle(&mut env, &private_key, &rp_id_hash).unwrap(); for i in 0..encrypted_id.len() { let mut modified_id = encrypted_id.clone(); modified_id[i] ^= 0x01; assert_eq!( decrypt_credential_source(&mut env, modified_id, &rp_id_hash), Ok(None) ); } } #[test] fn test_ecdsa_encrypt_decrypt_bad_hmac() { test_encrypt_decrypt_bad_hmac(SignatureAlgorithm::ES256); } #[test] #[cfg(feature = "ed25519")] fn test_ed25519_encrypt_decrypt_bad_hmac() { test_encrypt_decrypt_bad_hmac(SignatureAlgorithm::EDDSA); } fn test_decrypt_credential_missing_blocks(signature_algorithm: SignatureAlgorithm) { let mut env = TestEnv::new(); storage::init(&mut env).ok().unwrap(); let private_key = PrivateKey::new(env.rng(), signature_algorithm); let rp_id_hash = [0x55; 32]; let encrypted_id = encrypt_key_handle(&mut env, &private_key, &rp_id_hash).unwrap(); for length in (1..ECDSA_CREDENTIAL_ID_SIZE).step_by(16) { assert_eq!( decrypt_credential_source(&mut env, encrypted_id[..length].to_vec(), &rp_id_hash), Ok(None) ); } } #[test] fn test_ecdsa_decrypt_credential_missing_blocks() { test_decrypt_credential_missing_blocks(SignatureAlgorithm::ES256); } #[test] #[cfg(feature = "ed25519")] fn test_ed25519_decrypt_credential_missing_blocks() { test_decrypt_credential_missing_blocks(SignatureAlgorithm::EDDSA); } /// This is a copy of the function that genereated deprecated key handles. fn legacy_encrypt_key_handle( env: &mut impl Env, private_key: crypto::ecdsa::SecKey, application: &[u8; 32], ) -> Result, Ctap2StatusCode> { let master_keys = storage::master_keys(env)?; let aes_enc_key = crypto::aes256::EncryptionKey::new(&master_keys.encryption); let mut plaintext = [0; 64]; private_key.to_bytes(array_mut_ref!(plaintext, 0, 32)); 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::(&master_keys.hmac, &encrypted_id[..]); encrypted_id.extend(&id_hmac); Ok(encrypted_id) } #[test] fn test_encrypt_decrypt_credential_legacy() { let mut env = TestEnv::new(); storage::init(&mut env).ok().unwrap(); let ecdsa_key = crypto::ecdsa::SecKey::gensk(env.rng()); let private_key = PrivateKey::from(ecdsa_key.clone()); let rp_id_hash = [0x55; 32]; let encrypted_id = legacy_encrypt_key_handle(&mut env, ecdsa_key, &rp_id_hash).unwrap(); let decrypted_source = decrypt_credential_source(&mut env, encrypted_id, &rp_id_hash) .unwrap() .unwrap(); assert_eq!(private_key, decrypted_source.private_key); } #[test] fn test_encrypt_credential_size() { let mut env = TestEnv::new(); storage::init(&mut env).ok().unwrap(); let private_key = PrivateKey::new(env.rng(), SignatureAlgorithm::ES256); let rp_id_hash = [0x55; 32]; let encrypted_id = encrypt_key_handle(&mut env, &private_key, &rp_id_hash).unwrap(); assert_eq!(encrypted_id.len(), ECDSA_CREDENTIAL_ID_SIZE); } }