New key wrapping API (#642)
* New key wrapping API Allows key wrapping to be different between persistent and server-side storage. To accomplish this, the PrivateKey now always stores the fully reconstructed key, and has different methods to serialize it for the respective use case. * Cleans up legacy credential parsing This is a backwards incompatible change. This PR already introduces backwards incompatible new credential parsing, and therefore we can also remove all other legacy parsing. * Correct credential minimum size * wider public interface to allow custom PrivateKey construction
This commit is contained in:
@@ -13,7 +13,6 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use crate::api::crypto::aes256::Aes256;
|
use crate::api::crypto::aes256::Aes256;
|
||||||
use crate::api::crypto::ecdsa::SecretKey as _;
|
|
||||||
use crate::api::crypto::hmac256::Hmac256;
|
use crate::api::crypto::hmac256::Hmac256;
|
||||||
use crate::api::crypto::HASH_SIZE;
|
use crate::api::crypto::HASH_SIZE;
|
||||||
use crate::api::private_key::PrivateKey;
|
use crate::api::private_key::PrivateKey;
|
||||||
@@ -21,7 +20,7 @@ use crate::ctap::crypto_wrapper::{aes256_cbc_decrypt, aes256_cbc_encrypt};
|
|||||||
use crate::ctap::data_formats::CredentialProtectionPolicy;
|
use crate::ctap::data_formats::CredentialProtectionPolicy;
|
||||||
use crate::ctap::secret::Secret;
|
use crate::ctap::secret::Secret;
|
||||||
use crate::ctap::{cbor_read, cbor_write};
|
use crate::ctap::{cbor_read, cbor_write};
|
||||||
use crate::env::{AesKey, EcdsaSk, Env, Hmac};
|
use crate::env::{AesKey, Env, Hmac};
|
||||||
use alloc::vec;
|
use alloc::vec;
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use core::convert::{TryFrom, TryInto};
|
use core::convert::{TryFrom, TryInto};
|
||||||
@@ -30,14 +29,13 @@ use rand_core::RngCore;
|
|||||||
use sk_cbor as cbor;
|
use sk_cbor as cbor;
|
||||||
use sk_cbor::{cbor_map_options, destructure_cbor_map};
|
use sk_cbor::{cbor_map_options, destructure_cbor_map};
|
||||||
|
|
||||||
const LEGACY_CREDENTIAL_ID_SIZE: usize = 112;
|
|
||||||
// CBOR credential IDs consist of
|
// CBOR credential IDs consist of
|
||||||
// - 1 byte : version number
|
// - 1 byte : version number
|
||||||
// - 16 bytes: initialization vector for AES-256,
|
// - 16 bytes: initialization vector for AES-256,
|
||||||
// - 192 bytes: encrypted block of the key handle cbor,
|
// - 192 bytes: encrypted block of the key handle cbor,
|
||||||
// - 32 bytes: HMAC-SHA256 over everything else.
|
// - 32 bytes: HMAC-SHA256 over everything else.
|
||||||
pub const CBOR_CREDENTIAL_ID_SIZE: usize = 241;
|
pub const CBOR_CREDENTIAL_ID_SIZE: usize = 241;
|
||||||
const MIN_CREDENTIAL_ID_SIZE: usize = LEGACY_CREDENTIAL_ID_SIZE;
|
const MIN_CREDENTIAL_ID_SIZE: usize = CBOR_CREDENTIAL_ID_SIZE;
|
||||||
pub(crate) const MAX_CREDENTIAL_ID_SIZE: usize = CBOR_CREDENTIAL_ID_SIZE;
|
pub(crate) const MAX_CREDENTIAL_ID_SIZE: usize = CBOR_CREDENTIAL_ID_SIZE;
|
||||||
|
|
||||||
pub const CBOR_CREDENTIAL_ID_VERSION: u8 = 0x01;
|
pub const CBOR_CREDENTIAL_ID_VERSION: u8 = 0x01;
|
||||||
@@ -76,6 +74,14 @@ pub trait KeyStore {
|
|||||||
/// This function should be a no-op if the key store is already initialized.
|
/// This function should be a no-op if the key store is already initialized.
|
||||||
fn init(&mut self) -> Result<(), Error>;
|
fn init(&mut self) -> Result<(), Error>;
|
||||||
|
|
||||||
|
/// Key to wrap (secret) data.
|
||||||
|
///
|
||||||
|
/// Useful for encrypting data before
|
||||||
|
/// - writing it to persistent storage,
|
||||||
|
/// - CBOR encoding it,
|
||||||
|
/// - doing anything that does not support [`Secret`].
|
||||||
|
fn wrap_key<E: Env>(&mut self) -> Result<AesKey<E>, Error>;
|
||||||
|
|
||||||
/// Encodes a credential as a binary strings.
|
/// Encodes a credential as a binary strings.
|
||||||
///
|
///
|
||||||
/// The output is encrypted and authenticated. Since the wrapped credentials are passed to the
|
/// The output is encrypted and authenticated. Since the wrapped credentials are passed to the
|
||||||
@@ -109,14 +115,6 @@ pub trait KeyStore {
|
|||||||
/// Decrypts a PIN hash.
|
/// Decrypts a PIN hash.
|
||||||
fn decrypt_pin_hash(&mut self, cipher: &[u8; 16]) -> Result<Secret<[u8; 16]>, Error>;
|
fn decrypt_pin_hash(&mut self, cipher: &[u8; 16]) -> Result<Secret<[u8; 16]>, Error>;
|
||||||
|
|
||||||
/// Derives an ECDSA private key from a seed.
|
|
||||||
///
|
|
||||||
/// The result is big-endian.
|
|
||||||
fn derive_ecdsa(&mut self, seed: &[u8; 32]) -> Result<Secret<[u8; 32]>, Error>;
|
|
||||||
|
|
||||||
/// Generates a seed to derive an ECDSA private key.
|
|
||||||
fn generate_ecdsa_seed(&mut self) -> Result<Secret<[u8; 32]>, Error>;
|
|
||||||
|
|
||||||
/// Resets the key store.
|
/// Resets the key store.
|
||||||
fn reset(&mut self) -> Result<(), Error>;
|
fn reset(&mut self) -> Result<(), Error>;
|
||||||
}
|
}
|
||||||
@@ -138,14 +136,23 @@ impl<T: Helper> KeyStore for T {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn wrap_key<E: Env>(&mut self) -> Result<AesKey<E>, Error> {
|
||||||
|
Ok(AesKey::<E>::new(&get_master_keys(self)?.encryption))
|
||||||
|
}
|
||||||
|
|
||||||
/// Encrypts the given credential source data into a credential ID.
|
/// Encrypts the given credential source data into a credential ID.
|
||||||
///
|
///
|
||||||
/// Other information, such as a user name, are not stored. Since encrypted credential IDs are
|
/// Other information, such as a user name, are not stored. Since encrypted credential IDs are
|
||||||
/// stored server-side, this information is already available (unencrypted).
|
/// stored server-side, this information is already available (unencrypted).
|
||||||
fn wrap_credential(&mut self, credential: CredentialSource) -> Result<Vec<u8>, Error> {
|
fn wrap_credential(&mut self, credential: CredentialSource) -> Result<Vec<u8>, Error> {
|
||||||
let mut payload = Vec::new();
|
let mut payload = Vec::new();
|
||||||
|
let wrap_key = self.wrap_key::<T>()?;
|
||||||
|
let private_key_cbor = credential
|
||||||
|
.private_key
|
||||||
|
.to_cbor::<T>(self.rng(), &wrap_key)
|
||||||
|
.map_err(|_| Error)?;
|
||||||
let cbor = cbor_map_options! {
|
let cbor = cbor_map_options! {
|
||||||
CredentialSourceField::PrivateKey => credential.private_key,
|
CredentialSourceField::PrivateKey => private_key_cbor,
|
||||||
CredentialSourceField::RpIdHash => credential.rp_id_hash,
|
CredentialSourceField::RpIdHash => credential.rp_id_hash,
|
||||||
CredentialSourceField::CredProtectPolicy => credential.cred_protect_policy,
|
CredentialSourceField::CredProtectPolicy => credential.cred_protect_policy,
|
||||||
CredentialSourceField::CredBlob => credential.cred_blob,
|
CredentialSourceField::CredBlob => credential.cred_blob,
|
||||||
@@ -155,7 +162,7 @@ impl<T: Helper> KeyStore for T {
|
|||||||
let master_keys = get_master_keys(self)?;
|
let master_keys = get_master_keys(self)?;
|
||||||
let aes_key = AesKey::<T>::new(&master_keys.encryption);
|
let aes_key = AesKey::<T>::new(&master_keys.encryption);
|
||||||
let encrypted_payload =
|
let encrypted_payload =
|
||||||
aes256_cbc_encrypt(self, &aes_key, &payload, true).map_err(|_| Error)?;
|
aes256_cbc_encrypt::<T>(self.rng(), &aes_key, &payload, true).map_err(|_| Error)?;
|
||||||
let mut credential_id = encrypted_payload;
|
let mut credential_id = encrypted_payload;
|
||||||
credential_id.insert(0, CBOR_CREDENTIAL_ID_VERSION);
|
credential_id.insert(0, CBOR_CREDENTIAL_ID_VERSION);
|
||||||
|
|
||||||
@@ -175,12 +182,6 @@ impl<T: Helper> KeyStore for T {
|
|||||||
/// - the format does not match any known versions, or
|
/// - the format does not match any known versions, or
|
||||||
/// - the HMAC test fails.
|
/// - the HMAC test fails.
|
||||||
///
|
///
|
||||||
/// For v0 (legacy U2F) the credential ID consists of:
|
|
||||||
/// - 16 bytes: initialization vector for AES-256,
|
|
||||||
/// - 32 bytes: encrypted ECDSA private key for the credential,
|
|
||||||
/// - 32 bytes: encrypted relying party ID hashed with SHA256,
|
|
||||||
/// - 32 bytes: HMAC-SHA256 over everything else.
|
|
||||||
///
|
|
||||||
/// For v1 (CBOR) the credential ID consists of:
|
/// For v1 (CBOR) the credential ID consists of:
|
||||||
/// - 1 byte : version number,
|
/// - 1 byte : version number,
|
||||||
/// - 16 bytes: initialization vector for AES-256,
|
/// - 16 bytes: initialization vector for AES-256,
|
||||||
@@ -204,21 +205,18 @@ impl<T: Helper> KeyStore for T {
|
|||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let credential_source = if bytes.len() == LEGACY_CREDENTIAL_ID_SIZE {
|
let credential_source = match bytes[0] {
|
||||||
decrypt_legacy_credential_id::<T>(&master_keys.encryption, &bytes[..hmac_message_size])?
|
|
||||||
} else {
|
|
||||||
match bytes[0] {
|
|
||||||
CBOR_CREDENTIAL_ID_VERSION => {
|
CBOR_CREDENTIAL_ID_VERSION => {
|
||||||
if bytes.len() != CBOR_CREDENTIAL_ID_SIZE {
|
if bytes.len() != CBOR_CREDENTIAL_ID_SIZE {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
decrypt_cbor_credential_id::<T>(
|
decrypt_cbor_credential_id::<T>(
|
||||||
|
self,
|
||||||
&master_keys.encryption,
|
&master_keys.encryption,
|
||||||
&bytes[1..hmac_message_size],
|
&bytes[1..hmac_message_size],
|
||||||
)?
|
)?
|
||||||
}
|
}
|
||||||
_ => return Ok(None),
|
_ => return Ok(None),
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(credential_source) = &credential_source {
|
if let Some(credential_source) = &credential_source {
|
||||||
@@ -241,24 +239,8 @@ impl<T: Helper> KeyStore for T {
|
|||||||
Ok(Secret::from_exposed_secret(*cipher))
|
Ok(Secret::from_exposed_secret(*cipher))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn derive_ecdsa(&mut self, seed: &[u8; 32]) -> Result<Secret<[u8; 32]>, Error> {
|
|
||||||
match EcdsaSk::<T>::from_slice(seed) {
|
|
||||||
None => Err(Error),
|
|
||||||
Some(_) => {
|
|
||||||
let mut derived: Secret<[u8; 32]> = Secret::default();
|
|
||||||
derived.copy_from_slice(seed);
|
|
||||||
Ok(derived)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_ecdsa_seed(&mut self) -> Result<Secret<[u8; 32]>, Error> {
|
|
||||||
let mut seed = Secret::default();
|
|
||||||
EcdsaSk::<T>::random(self.rng()).to_slice(&mut seed);
|
|
||||||
Ok(seed)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reset(&mut self) -> Result<(), Error> {
|
fn reset(&mut self) -> Result<(), Error> {
|
||||||
|
// The storage also removes `STORAGE_KEY`, but this makes KeyStore more self-sufficient.
|
||||||
Ok(self.store().remove(STORAGE_KEY)?)
|
Ok(self.store().remove(STORAGE_KEY)?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -333,31 +315,8 @@ fn remove_padding(data: &[u8]) -> Result<&[u8], Error> {
|
|||||||
Ok(&data[..data.len() - pad_length as usize])
|
Ok(&data[..data.len() - pad_length as usize])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decrypt_legacy_credential_id<E: Env>(
|
|
||||||
encryption_key_bytes: &[u8; 32],
|
|
||||||
bytes: &[u8],
|
|
||||||
) -> Result<Option<CredentialSource>, Error> {
|
|
||||||
let aes_key = AesKey::<E>::new(encryption_key_bytes);
|
|
||||||
let plaintext = aes256_cbc_decrypt::<E>(&aes_key, bytes, true)
|
|
||||||
.map_err(|_| Error)?
|
|
||||||
.expose_secret_to_vec();
|
|
||||||
if plaintext.len() != 64 {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
let private_key = if let Some(key) = PrivateKey::new_ecdsa_from_bytes(&plaintext[..32]) {
|
|
||||||
key
|
|
||||||
} else {
|
|
||||||
return Ok(None);
|
|
||||||
};
|
|
||||||
Ok(Some(CredentialSource {
|
|
||||||
private_key,
|
|
||||||
rp_id_hash: plaintext[32..64].try_into().unwrap(),
|
|
||||||
cred_protect_policy: None,
|
|
||||||
cred_blob: None,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn decrypt_cbor_credential_id<E: Env>(
|
fn decrypt_cbor_credential_id<E: Env>(
|
||||||
|
env: &mut E,
|
||||||
encryption_key_bytes: &[u8; 32],
|
encryption_key_bytes: &[u8; 32],
|
||||||
bytes: &[u8],
|
bytes: &[u8],
|
||||||
) -> Result<Option<CredentialSource>, Error> {
|
) -> Result<Option<CredentialSource>, Error> {
|
||||||
@@ -376,7 +335,9 @@ fn decrypt_cbor_credential_id<E: Env>(
|
|||||||
}
|
}
|
||||||
Ok(match (private_key, rp_id_hash) {
|
Ok(match (private_key, rp_id_hash) {
|
||||||
(Some(private_key), Some(rp_id_hash)) => {
|
(Some(private_key), Some(rp_id_hash)) => {
|
||||||
let private_key = PrivateKey::try_from(private_key).map_err(|_| Error)?;
|
let wrap_key = env.key_store().wrap_key::<E>()?;
|
||||||
|
let private_key =
|
||||||
|
PrivateKey::from_cbor::<E>(&wrap_key, private_key).map_err(|_| Error)?;
|
||||||
let rp_id_hash = extract_byte_string(rp_id_hash)?;
|
let rp_id_hash = extract_byte_string(rp_id_hash)?;
|
||||||
if rp_id_hash.len() != 32 {
|
if rp_id_hash.len() != 32 {
|
||||||
return Err(Error);
|
return Err(Error);
|
||||||
@@ -420,9 +381,11 @@ fn extract_map(cbor_value: cbor::Value) -> Result<Vec<(cbor::Value, cbor::Value)
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::api::crypto::ecdsa::SecretKey;
|
||||||
use crate::api::customization::Customization;
|
use crate::api::customization::Customization;
|
||||||
use crate::ctap::data_formats::SignatureAlgorithm;
|
use crate::ctap::data_formats::SignatureAlgorithm;
|
||||||
use crate::env::test::TestEnv;
|
use crate::env::test::TestEnv;
|
||||||
|
use crate::env::EcdsaSk;
|
||||||
|
|
||||||
const UNSUPPORTED_CREDENTIAL_ID_VERSION: u8 = 0x80;
|
const UNSUPPORTED_CREDENTIAL_ID_VERSION: u8 = 0x80;
|
||||||
|
|
||||||
@@ -437,16 +400,24 @@ mod test {
|
|||||||
assert_eq!(&key_store.cred_random(false).unwrap(), &cred_random_no_uv);
|
assert_eq!(&key_store.cred_random(false).unwrap(), &cred_random_no_uv);
|
||||||
assert_eq!(&key_store.cred_random(true).unwrap(), &cred_random_with_uv);
|
assert_eq!(&key_store.cred_random(true).unwrap(), &cred_random_with_uv);
|
||||||
|
|
||||||
// ECDSA seeds are well-defined and stable.
|
// Same for wrap key.
|
||||||
let ecdsa_seed = key_store.generate_ecdsa_seed().unwrap();
|
let wrap_key = key_store.wrap_key::<TestEnv>().unwrap();
|
||||||
let ecdsa_key = key_store.derive_ecdsa(&ecdsa_seed).unwrap();
|
let mut test_block = [0x33; 16];
|
||||||
assert_eq!(key_store.derive_ecdsa(&ecdsa_seed), Ok(ecdsa_key));
|
wrap_key.encrypt_block(&mut test_block);
|
||||||
|
let new_wrap_key = key_store.wrap_key::<TestEnv>().unwrap();
|
||||||
|
let mut new_test_block = [0x33; 16];
|
||||||
|
new_wrap_key.encrypt_block(&mut new_test_block);
|
||||||
|
assert_eq!(&new_test_block, &test_block);
|
||||||
|
|
||||||
// Master keys change after reset. We don't require this for ECDSA seeds because it's not
|
// Master keys change after reset. We don't require this for ECDSA seeds because it's not
|
||||||
// the case, but it might be better.
|
// the case, but it might be better.
|
||||||
key_store.reset().unwrap();
|
key_store.reset().unwrap();
|
||||||
assert_ne!(&key_store.cred_random(false).unwrap(), &cred_random_no_uv);
|
assert_ne!(&key_store.cred_random(false).unwrap(), &cred_random_no_uv);
|
||||||
assert_ne!(&key_store.cred_random(true).unwrap(), &cred_random_with_uv);
|
assert_ne!(&key_store.cred_random(true).unwrap(), &cred_random_with_uv);
|
||||||
|
let new_wrap_key = key_store.wrap_key::<TestEnv>().unwrap();
|
||||||
|
let mut new_test_block = [0x33; 16];
|
||||||
|
new_wrap_key.encrypt_block(&mut new_test_block);
|
||||||
|
assert_ne!(&new_test_block, &test_block);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -585,49 +556,6 @@ mod test {
|
|||||||
test_wrap_unwrap_credential_missing_blocks(SignatureAlgorithm::Eddsa);
|
test_wrap_unwrap_credential_missing_blocks(SignatureAlgorithm::Eddsa);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This is a copy of the function that genereated deprecated key handles.
|
|
||||||
fn legacy_encrypt_to_credential_id(
|
|
||||||
env: &mut TestEnv,
|
|
||||||
private_key: EcdsaSk<TestEnv>,
|
|
||||||
application: &[u8; 32],
|
|
||||||
) -> Result<Vec<u8>, Error> {
|
|
||||||
let master_keys = get_master_keys(env).unwrap();
|
|
||||||
let aes_key = AesKey::<TestEnv>::new(&master_keys.encryption);
|
|
||||||
let hmac_key = master_keys.authentication;
|
|
||||||
let mut plaintext = [0; 64];
|
|
||||||
private_key.to_slice(array_mut_ref!(plaintext, 0, 32));
|
|
||||||
plaintext[32..64].copy_from_slice(application);
|
|
||||||
|
|
||||||
let mut encrypted_id =
|
|
||||||
aes256_cbc_encrypt(env, &aes_key, &plaintext, true).map_err(|_| Error)?;
|
|
||||||
let mut id_hmac = [0; HASH_SIZE];
|
|
||||||
Hmac::<TestEnv>::mac(&hmac_key, &encrypted_id[..], &mut id_hmac);
|
|
||||||
encrypted_id.extend(&id_hmac);
|
|
||||||
Ok(encrypted_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_encrypt_decrypt_credential_legacy() {
|
|
||||||
let mut env = TestEnv::default();
|
|
||||||
let private_key = PrivateKey::new(&mut env, SignatureAlgorithm::Es256);
|
|
||||||
let rp_id_hash = [0x55; 32];
|
|
||||||
let credential_source = CredentialSource {
|
|
||||||
private_key,
|
|
||||||
rp_id_hash,
|
|
||||||
cred_protect_policy: None,
|
|
||||||
cred_blob: None,
|
|
||||||
};
|
|
||||||
let ecdsa_key = credential_source.private_key.ecdsa_key(&mut env).unwrap();
|
|
||||||
let credential_id =
|
|
||||||
legacy_encrypt_to_credential_id(&mut env, ecdsa_key, &rp_id_hash).unwrap();
|
|
||||||
let unwrapped = env
|
|
||||||
.key_store()
|
|
||||||
.unwrap_credential(&credential_id, &rp_id_hash)
|
|
||||||
.unwrap()
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(credential_source, unwrapped);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_wrap_credential_size() {
|
fn test_wrap_credential_size() {
|
||||||
let mut env = TestEnv::default();
|
let mut env = TestEnv::default();
|
||||||
|
|||||||
@@ -13,11 +13,11 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use crate::api::crypto::ecdsa::{SecretKey as _, Signature};
|
use crate::api::crypto::ecdsa::{SecretKey as _, Signature};
|
||||||
use crate::api::key_store::KeyStore;
|
use crate::ctap::crypto_wrapper::{aes256_cbc_decrypt, aes256_cbc_encrypt};
|
||||||
use crate::ctap::data_formats::{extract_array, extract_byte_string, CoseKey, SignatureAlgorithm};
|
use crate::ctap::data_formats::{extract_array, extract_byte_string, CoseKey, SignatureAlgorithm};
|
||||||
use crate::ctap::secret::Secret;
|
use crate::ctap::secret::Secret;
|
||||||
use crate::ctap::status_code::Ctap2StatusCode;
|
use crate::ctap::status_code::Ctap2StatusCode;
|
||||||
use crate::env::{EcdsaSk, Env};
|
use crate::env::{AesKey, EcdsaSk, Env};
|
||||||
use alloc::vec;
|
use alloc::vec;
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use core::convert::TryFrom;
|
use core::convert::TryFrom;
|
||||||
@@ -34,8 +34,7 @@ use sk_cbor::{cbor_array, cbor_bytes, cbor_int};
|
|||||||
// We shouldn't compare private keys in prod without constant-time operations.
|
// We shouldn't compare private keys in prod without constant-time operations.
|
||||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||||
pub enum PrivateKey {
|
pub enum PrivateKey {
|
||||||
// We store the seed instead of the key since we can't get the seed back from the key. We could
|
// We store the key bytes instead of the env type. They can be converted into each other.
|
||||||
// store both if we believe deriving the key is done more than once and costly.
|
|
||||||
Ecdsa(Secret<[u8; 32]>),
|
Ecdsa(Secret<[u8; 32]>),
|
||||||
#[cfg(feature = "ed25519")]
|
#[cfg(feature = "ed25519")]
|
||||||
Ed25519(ed25519_compact::SecretKey),
|
Ed25519(ed25519_compact::SecretKey),
|
||||||
@@ -47,10 +46,12 @@ impl PrivateKey {
|
|||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// Panics if the algorithm is [`SignatureAlgorithm::Unknown`].
|
/// Panics if the algorithm is [`SignatureAlgorithm::Unknown`].
|
||||||
pub fn new(env: &mut impl Env, alg: SignatureAlgorithm) -> Self {
|
pub fn new<E: Env>(env: &mut E, alg: SignatureAlgorithm) -> Self {
|
||||||
match alg {
|
match alg {
|
||||||
SignatureAlgorithm::Es256 => {
|
SignatureAlgorithm::Es256 => {
|
||||||
PrivateKey::Ecdsa(env.key_store().generate_ecdsa_seed().unwrap())
|
let mut bytes: Secret<[u8; 32]> = Secret::default();
|
||||||
|
EcdsaSk::<E>::random(env.rng()).to_slice(&mut bytes);
|
||||||
|
PrivateKey::Ecdsa(bytes)
|
||||||
}
|
}
|
||||||
#[cfg(feature = "ed25519")]
|
#[cfg(feature = "ed25519")]
|
||||||
SignatureAlgorithm::Eddsa => {
|
SignatureAlgorithm::Eddsa => {
|
||||||
@@ -68,8 +69,6 @@ impl PrivateKey {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Helper function that creates a private key of type ECDSA.
|
/// 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<Self> {
|
pub fn new_ecdsa_from_bytes(bytes: &[u8]) -> Option<Self> {
|
||||||
if bytes.len() != 32 {
|
if bytes.len() != 32 {
|
||||||
return None;
|
return None;
|
||||||
@@ -79,6 +78,7 @@ impl PrivateKey {
|
|||||||
Some(PrivateKey::Ecdsa(seed))
|
Some(PrivateKey::Ecdsa(seed))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper function that creates a private key of type Ed25519.
|
||||||
#[cfg(feature = "ed25519")]
|
#[cfg(feature = "ed25519")]
|
||||||
pub fn new_ed25519_from_bytes(bytes: &[u8]) -> Option<Self> {
|
pub fn new_ed25519_from_bytes(bytes: &[u8]) -> Option<Self> {
|
||||||
if bytes.len() != 32 {
|
if bytes.len() != 32 {
|
||||||
@@ -89,19 +89,19 @@ impl PrivateKey {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the ECDSA private key.
|
/// Returns the ECDSA private key.
|
||||||
pub fn ecdsa_key<E: Env>(&self, env: &mut E) -> Result<EcdsaSk<E>, Ctap2StatusCode> {
|
pub fn ecdsa_key<E: Env>(&self) -> Result<EcdsaSk<E>, Ctap2StatusCode> {
|
||||||
match self {
|
match self {
|
||||||
PrivateKey::Ecdsa(seed) => ecdsa_key_from_seed(env, seed),
|
PrivateKey::Ecdsa(bytes) => ecdsa_key_from_bytes::<E>(bytes),
|
||||||
#[allow(unreachable_patterns)]
|
#[allow(unreachable_patterns)]
|
||||||
_ => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR),
|
_ => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the corresponding public key.
|
/// Returns the corresponding public key.
|
||||||
pub fn get_pub_key(&self, env: &mut impl Env) -> Result<CoseKey, Ctap2StatusCode> {
|
pub fn get_pub_key<E: Env>(&self) -> Result<CoseKey, Ctap2StatusCode> {
|
||||||
Ok(match self {
|
Ok(match self {
|
||||||
PrivateKey::Ecdsa(ecdsa_seed) => {
|
PrivateKey::Ecdsa(bytes) => {
|
||||||
CoseKey::from_ecdsa_public_key(ecdsa_key_from_seed(env, ecdsa_seed)?.public_key())
|
CoseKey::from_ecdsa_public_key(ecdsa_key_from_bytes::<E>(bytes)?.public_key())
|
||||||
}
|
}
|
||||||
#[cfg(feature = "ed25519")]
|
#[cfg(feature = "ed25519")]
|
||||||
PrivateKey::Ed25519(ed25519_key) => CoseKey::from(ed25519_key.public_key()),
|
PrivateKey::Ed25519(ed25519_key) => CoseKey::from(ed25519_key.public_key()),
|
||||||
@@ -109,15 +109,9 @@ impl PrivateKey {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the encoded signature for a given message.
|
/// Returns the encoded signature for a given message.
|
||||||
pub(crate) fn sign_and_encode(
|
pub fn sign_and_encode<E: Env>(&self, message: &[u8]) -> Result<Vec<u8>, Ctap2StatusCode> {
|
||||||
&self,
|
|
||||||
env: &mut impl Env,
|
|
||||||
message: &[u8],
|
|
||||||
) -> Result<Vec<u8>, Ctap2StatusCode> {
|
|
||||||
Ok(match self {
|
Ok(match self {
|
||||||
PrivateKey::Ecdsa(ecdsa_seed) => {
|
PrivateKey::Ecdsa(bytes) => ecdsa_key_from_bytes::<E>(bytes)?.sign(message).to_der(),
|
||||||
ecdsa_key_from_seed(env, ecdsa_seed)?.sign(message).to_der()
|
|
||||||
}
|
|
||||||
#[cfg(feature = "ed25519")]
|
#[cfg(feature = "ed25519")]
|
||||||
PrivateKey::Ed25519(ed25519_key) => ed25519_key.sign(message, None).to_vec(),
|
PrivateKey::Ed25519(ed25519_key) => ed25519_key.sign(message, None).to_vec(),
|
||||||
})
|
})
|
||||||
@@ -136,57 +130,55 @@ impl PrivateKey {
|
|||||||
pub fn to_bytes(&self) -> Secret<[u8]> {
|
pub fn to_bytes(&self) -> Secret<[u8]> {
|
||||||
let mut bytes = Secret::new(32);
|
let mut bytes = Secret::new(32);
|
||||||
match self {
|
match self {
|
||||||
PrivateKey::Ecdsa(ecdsa_seed) => bytes.copy_from_slice(ecdsa_seed.deref()),
|
PrivateKey::Ecdsa(key_bytes) => bytes.copy_from_slice(key_bytes.deref()),
|
||||||
#[cfg(feature = "ed25519")]
|
#[cfg(feature = "ed25519")]
|
||||||
PrivateKey::Ed25519(ed25519_key) => bytes.copy_from_slice(ed25519_key.seed().deref()),
|
PrivateKey::Ed25519(ed25519_key) => bytes.copy_from_slice(ed25519_key.seed().deref()),
|
||||||
}
|
}
|
||||||
bytes
|
bytes
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn ecdsa_key_from_seed<E: Env>(
|
pub fn to_cbor<E: Env>(
|
||||||
env: &mut E,
|
&self,
|
||||||
seed: &[u8; 32],
|
rng: &mut E::Rng,
|
||||||
) -> Result<EcdsaSk<E>, Ctap2StatusCode> {
|
wrap_key: &AesKey<E>,
|
||||||
let ecdsa_bytes = env.key_store().derive_ecdsa(seed)?;
|
) -> Result<cbor::Value, Ctap2StatusCode> {
|
||||||
Ok(EcdsaSk::<E>::from_slice(&ecdsa_bytes).unwrap())
|
let bytes = self.to_bytes();
|
||||||
}
|
let wrapped_bytes = aes256_cbc_encrypt::<E>(rng, wrap_key, &bytes, true)?;
|
||||||
|
Ok(cbor_array![
|
||||||
impl From<&PrivateKey> for cbor::Value {
|
cbor_int!(self.signature_algorithm() as i64),
|
||||||
/// Writes a private key into CBOR format. This exposes the cryptographic secret.
|
cbor_bytes!(wrapped_bytes),
|
||||||
// TODO needs zeroization if seed is secret
|
])
|
||||||
// called in wrap_credential and PublicKeyCredentialSource
|
|
||||||
fn from(private_key: &PrivateKey) -> Self {
|
|
||||||
cbor_array![
|
|
||||||
cbor_int!(private_key.signature_algorithm() as i64),
|
|
||||||
cbor_bytes!(private_key.to_bytes().expose_secret_to_vec()),
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<cbor::Value> for PrivateKey {
|
pub fn from_cbor<E: Env>(
|
||||||
type Error = Ctap2StatusCode;
|
wrap_key: &AesKey<E>,
|
||||||
|
cbor_value: cbor::Value,
|
||||||
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
|
) -> Result<Self, Ctap2StatusCode> {
|
||||||
let mut array = extract_array(cbor_value)?;
|
let mut array = extract_array(cbor_value)?;
|
||||||
if array.len() != 2 {
|
if array.len() != 2 {
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR);
|
return Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR);
|
||||||
}
|
}
|
||||||
let key_bytes = extract_byte_string(array.pop().unwrap())?;
|
let wrapped_bytes = extract_byte_string(array.pop().unwrap())?;
|
||||||
|
let key_bytes = aes256_cbc_decrypt::<E>(wrap_key, &wrapped_bytes, true)?;
|
||||||
match SignatureAlgorithm::try_from(array.pop().unwrap())? {
|
match SignatureAlgorithm::try_from(array.pop().unwrap())? {
|
||||||
SignatureAlgorithm::Es256 => PrivateKey::new_ecdsa_from_bytes(&key_bytes)
|
SignatureAlgorithm::Es256 => PrivateKey::new_ecdsa_from_bytes(&*key_bytes)
|
||||||
.ok_or(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR),
|
.ok_or(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR),
|
||||||
#[cfg(feature = "ed25519")]
|
#[cfg(feature = "ed25519")]
|
||||||
SignatureAlgorithm::Eddsa => PrivateKey::new_ed25519_from_bytes(&key_bytes)
|
SignatureAlgorithm::Eddsa => PrivateKey::new_ed25519_from_bytes(&*key_bytes)
|
||||||
.ok_or(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR),
|
.ok_or(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR),
|
||||||
_ => Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR),
|
_ => Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn ecdsa_key_from_bytes<E: Env>(bytes: &[u8; 32]) -> Result<EcdsaSk<E>, Ctap2StatusCode> {
|
||||||
|
EcdsaSk::<E>::from_slice(bytes).ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::api::key_store::KeyStore;
|
||||||
use crate::env::test::TestEnv;
|
use crate::env::test::TestEnv;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -233,10 +225,10 @@ mod test {
|
|||||||
fn test_private_key_get_pub_key() {
|
fn test_private_key_get_pub_key() {
|
||||||
let mut env = TestEnv::default();
|
let mut env = TestEnv::default();
|
||||||
let private_key = PrivateKey::new_ecdsa(&mut env);
|
let private_key = PrivateKey::new_ecdsa(&mut env);
|
||||||
let ecdsa_key = private_key.ecdsa_key(&mut env).unwrap();
|
let ecdsa_key = private_key.ecdsa_key::<TestEnv>().unwrap();
|
||||||
let public_key = ecdsa_key.public_key();
|
let public_key = ecdsa_key.public_key();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
private_key.get_pub_key(&mut env),
|
private_key.get_pub_key::<TestEnv>(),
|
||||||
Ok(CoseKey::from_ecdsa_public_key(public_key))
|
Ok(CoseKey::from_ecdsa_public_key(public_key))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -246,10 +238,10 @@ mod test {
|
|||||||
let mut env = TestEnv::default();
|
let mut env = TestEnv::default();
|
||||||
let message = [0x5A; 32];
|
let message = [0x5A; 32];
|
||||||
let private_key = PrivateKey::new_ecdsa(&mut env);
|
let private_key = PrivateKey::new_ecdsa(&mut env);
|
||||||
let ecdsa_key = private_key.ecdsa_key(&mut env).unwrap();
|
let ecdsa_key = private_key.ecdsa_key::<TestEnv>().unwrap();
|
||||||
let signature = ecdsa_key.sign(&message).to_der();
|
let signature = ecdsa_key.sign(&message).to_der();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
private_key.sign_and_encode(&mut env, &message),
|
private_key.sign_and_encode::<TestEnv>(&message),
|
||||||
Ok(signature)
|
Ok(signature)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -273,9 +265,15 @@ mod test {
|
|||||||
|
|
||||||
fn test_private_key_from_to_cbor(signature_algorithm: SignatureAlgorithm) {
|
fn test_private_key_from_to_cbor(signature_algorithm: SignatureAlgorithm) {
|
||||||
let mut env = TestEnv::default();
|
let mut env = TestEnv::default();
|
||||||
|
let wrap_key = env.key_store().wrap_key::<TestEnv>().unwrap();
|
||||||
let private_key = PrivateKey::new(&mut env, signature_algorithm);
|
let private_key = PrivateKey::new(&mut env, signature_algorithm);
|
||||||
let cbor = cbor::Value::from(&private_key);
|
let cbor = private_key
|
||||||
assert_eq!(PrivateKey::try_from(cbor), Ok(private_key),);
|
.to_cbor::<TestEnv>(env.rng(), &wrap_key)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
PrivateKey::from_cbor::<TestEnv>(&wrap_key, cbor),
|
||||||
|
Ok(private_key)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -290,6 +288,8 @@ mod test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn test_private_key_from_bad_cbor(signature_algorithm: SignatureAlgorithm) {
|
fn test_private_key_from_bad_cbor(signature_algorithm: SignatureAlgorithm) {
|
||||||
|
let mut env = TestEnv::default();
|
||||||
|
let wrap_key = env.key_store().wrap_key::<TestEnv>().unwrap();
|
||||||
let cbor = cbor_array![
|
let cbor = cbor_array![
|
||||||
cbor_int!(signature_algorithm as i64),
|
cbor_int!(signature_algorithm as i64),
|
||||||
cbor_bytes!(vec![0x88; 32]),
|
cbor_bytes!(vec![0x88; 32]),
|
||||||
@@ -297,7 +297,7 @@ mod test {
|
|||||||
cbor_int!(0),
|
cbor_int!(0),
|
||||||
];
|
];
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
PrivateKey::try_from(cbor),
|
PrivateKey::from_cbor::<TestEnv>(&wrap_key, cbor),
|
||||||
Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR),
|
Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -315,13 +315,15 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_private_key_from_bad_cbor_unsupported_algo() {
|
fn test_private_key_from_bad_cbor_unsupported_algo() {
|
||||||
|
let mut env = TestEnv::default();
|
||||||
|
let wrap_key = env.key_store().wrap_key::<TestEnv>().unwrap();
|
||||||
let cbor = cbor_array![
|
let cbor = cbor_array![
|
||||||
// This algorithms doesn't exist.
|
// This algorithms doesn't exist.
|
||||||
cbor_int!(-1),
|
cbor_int!(-1),
|
||||||
cbor_bytes!(vec![0x88; 32]),
|
cbor_bytes!(vec![0x88; 32]),
|
||||||
];
|
];
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
PrivateKey::try_from(cbor),
|
PrivateKey::from_cbor::<TestEnv>(&wrap_key, cbor),
|
||||||
Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR),
|
Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,8 +61,7 @@ fn enumerate_rps_response<E: Env>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Generates the response for subcommands enumerating credentials.
|
/// Generates the response for subcommands enumerating credentials.
|
||||||
fn enumerate_credentials_response(
|
fn enumerate_credentials_response<E: Env>(
|
||||||
env: &mut impl Env,
|
|
||||||
credential: PublicKeyCredentialSource,
|
credential: PublicKeyCredentialSource,
|
||||||
total_credentials: Option<u64>,
|
total_credentials: Option<u64>,
|
||||||
) -> Result<AuthenticatorCredentialManagementResponse, Ctap2StatusCode> {
|
) -> Result<AuthenticatorCredentialManagementResponse, Ctap2StatusCode> {
|
||||||
@@ -91,7 +90,7 @@ fn enumerate_credentials_response(
|
|||||||
key_id: credential_id,
|
key_id: credential_id,
|
||||||
transports: None, // You can set USB as a hint here.
|
transports: None, // You can set USB as a hint here.
|
||||||
};
|
};
|
||||||
let public_key = private_key.get_pub_key(env)?;
|
let public_key = private_key.get_pub_key::<E>()?;
|
||||||
Ok(AuthenticatorCredentialManagementResponse {
|
Ok(AuthenticatorCredentialManagementResponse {
|
||||||
user: Some(user),
|
user: Some(user),
|
||||||
credential_id: Some(credential_id),
|
credential_id: Some(credential_id),
|
||||||
@@ -202,7 +201,7 @@ fn process_enumerate_credentials_begin<E: Env>(
|
|||||||
channel,
|
channel,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
enumerate_credentials_response(env, credential, Some(total_credentials as u64))
|
enumerate_credentials_response::<E>(credential, Some(total_credentials as u64))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Processes the subcommand enumerateCredentialsGetNextCredential for CredentialManagement.
|
/// Processes the subcommand enumerateCredentialsGetNextCredential for CredentialManagement.
|
||||||
@@ -212,7 +211,7 @@ fn process_enumerate_credentials_get_next_credential<E: Env>(
|
|||||||
) -> Result<AuthenticatorCredentialManagementResponse, Ctap2StatusCode> {
|
) -> Result<AuthenticatorCredentialManagementResponse, Ctap2StatusCode> {
|
||||||
let credential_key = stateful_command_permission.next_enumerate_credential(env)?;
|
let credential_key = stateful_command_permission.next_enumerate_credential(env)?;
|
||||||
let credential = storage::get_credential(env, credential_key)?;
|
let credential = storage::get_credential(env, credential_key)?;
|
||||||
enumerate_credentials_response(env, credential, None)
|
enumerate_credentials_response::<E>(credential, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Processes the subcommand deleteCredential for CredentialManagement.
|
/// Processes the subcommand deleteCredential for CredentialManagement.
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ use rand_core::RngCore;
|
|||||||
|
|
||||||
/// Wraps the AES256-CBC encryption to match what we need in CTAP.
|
/// Wraps the AES256-CBC encryption to match what we need in CTAP.
|
||||||
pub fn aes256_cbc_encrypt<E: Env>(
|
pub fn aes256_cbc_encrypt<E: Env>(
|
||||||
env: &mut E,
|
rng: &mut E::Rng,
|
||||||
aes_key: &AesKey<E>,
|
aes_key: &AesKey<E>,
|
||||||
plaintext: &[u8],
|
plaintext: &[u8],
|
||||||
embeds_iv: bool,
|
embeds_iv: bool,
|
||||||
@@ -32,7 +32,7 @@ pub fn aes256_cbc_encrypt<E: Env>(
|
|||||||
let mut ciphertext = Vec::with_capacity(plaintext.len() + 16 * embeds_iv as usize);
|
let mut ciphertext = Vec::with_capacity(plaintext.len() + 16 * embeds_iv as usize);
|
||||||
let iv = if embeds_iv {
|
let iv = if embeds_iv {
|
||||||
ciphertext.resize(16, 0);
|
ciphertext.resize(16, 0);
|
||||||
env.rng().fill_bytes(&mut ciphertext[..16]);
|
rng.fill_bytes(&mut ciphertext[..16]);
|
||||||
*array_ref!(ciphertext, 0, 16)
|
*array_ref!(ciphertext, 0, 16)
|
||||||
} else {
|
} else {
|
||||||
[0u8; 16]
|
[0u8; 16]
|
||||||
@@ -74,7 +74,8 @@ mod test {
|
|||||||
let mut env = TestEnv::default();
|
let mut env = TestEnv::default();
|
||||||
let aes_key = AesKey::<TestEnv>::new(&[0xC2; 32]);
|
let aes_key = AesKey::<TestEnv>::new(&[0xC2; 32]);
|
||||||
let plaintext = vec![0xAA; 64];
|
let plaintext = vec![0xAA; 64];
|
||||||
let ciphertext = aes256_cbc_encrypt(&mut env, &aes_key, &plaintext, true).unwrap();
|
let ciphertext =
|
||||||
|
aes256_cbc_encrypt::<TestEnv>(env.rng(), &aes_key, &plaintext, true).unwrap();
|
||||||
let decrypted = aes256_cbc_decrypt::<TestEnv>(&aes_key, &ciphertext, true).unwrap();
|
let decrypted = aes256_cbc_decrypt::<TestEnv>(&aes_key, &ciphertext, true).unwrap();
|
||||||
assert_eq!(*decrypted, plaintext);
|
assert_eq!(*decrypted, plaintext);
|
||||||
}
|
}
|
||||||
@@ -84,7 +85,8 @@ mod test {
|
|||||||
let mut env = TestEnv::default();
|
let mut env = TestEnv::default();
|
||||||
let aes_key = AesKey::<TestEnv>::new(&[0xC2; 32]);
|
let aes_key = AesKey::<TestEnv>::new(&[0xC2; 32]);
|
||||||
let plaintext = vec![0xAA; 64];
|
let plaintext = vec![0xAA; 64];
|
||||||
let ciphertext = aes256_cbc_encrypt(&mut env, &aes_key, &plaintext, false).unwrap();
|
let ciphertext =
|
||||||
|
aes256_cbc_encrypt::<TestEnv>(env.rng(), &aes_key, &plaintext, false).unwrap();
|
||||||
let decrypted = aes256_cbc_decrypt::<TestEnv>(&aes_key, &ciphertext, false).unwrap();
|
let decrypted = aes256_cbc_decrypt::<TestEnv>(&aes_key, &ciphertext, false).unwrap();
|
||||||
assert_eq!(*decrypted, plaintext);
|
assert_eq!(*decrypted, plaintext);
|
||||||
}
|
}
|
||||||
@@ -95,7 +97,7 @@ mod test {
|
|||||||
let aes_key = AesKey::<TestEnv>::new(&[0xC2; 32]);
|
let aes_key = AesKey::<TestEnv>::new(&[0xC2; 32]);
|
||||||
let plaintext = vec![0xAA; 64];
|
let plaintext = vec![0xAA; 64];
|
||||||
let mut ciphertext_no_iv =
|
let mut ciphertext_no_iv =
|
||||||
aes256_cbc_encrypt(&mut env, &aes_key, &plaintext, false).unwrap();
|
aes256_cbc_encrypt::<TestEnv>(env.rng(), &aes_key, &plaintext, false).unwrap();
|
||||||
let mut ciphertext_with_iv = vec![0u8; 16];
|
let mut ciphertext_with_iv = vec![0u8; 16];
|
||||||
ciphertext_with_iv.append(&mut ciphertext_no_iv);
|
ciphertext_with_iv.append(&mut ciphertext_no_iv);
|
||||||
let decrypted = aes256_cbc_decrypt::<TestEnv>(&aes_key, &ciphertext_with_iv, true).unwrap();
|
let decrypted = aes256_cbc_decrypt::<TestEnv>(&aes_key, &ciphertext_with_iv, true).unwrap();
|
||||||
@@ -107,7 +109,8 @@ mod test {
|
|||||||
let mut env = TestEnv::default();
|
let mut env = TestEnv::default();
|
||||||
let aes_key = AesKey::<TestEnv>::new(&[0xC2; 32]);
|
let aes_key = AesKey::<TestEnv>::new(&[0xC2; 32]);
|
||||||
let plaintext = vec![0xAA; 64];
|
let plaintext = vec![0xAA; 64];
|
||||||
let mut ciphertext = aes256_cbc_encrypt(&mut env, &aes_key, &plaintext, true).unwrap();
|
let mut ciphertext =
|
||||||
|
aes256_cbc_encrypt::<TestEnv>(env.rng(), &aes_key, &plaintext, true).unwrap();
|
||||||
let mut expected_plaintext = plaintext;
|
let mut expected_plaintext = plaintext;
|
||||||
for i in 0..16 {
|
for i in 0..16 {
|
||||||
ciphertext[i] ^= 0xBB;
|
ciphertext[i] ^= 0xBB;
|
||||||
@@ -122,8 +125,10 @@ mod test {
|
|||||||
let mut env = TestEnv::default();
|
let mut env = TestEnv::default();
|
||||||
let aes_key = AesKey::<TestEnv>::new(&[0xC2; 32]);
|
let aes_key = AesKey::<TestEnv>::new(&[0xC2; 32]);
|
||||||
let plaintext = vec![0xAA; 64];
|
let plaintext = vec![0xAA; 64];
|
||||||
let ciphertext1 = aes256_cbc_encrypt(&mut env, &aes_key, &plaintext, true).unwrap();
|
let ciphertext1 =
|
||||||
let ciphertext2 = aes256_cbc_encrypt(&mut env, &aes_key, &plaintext, true).unwrap();
|
aes256_cbc_encrypt::<TestEnv>(env.rng(), &aes_key, &plaintext, true).unwrap();
|
||||||
|
let ciphertext2 =
|
||||||
|
aes256_cbc_encrypt::<TestEnv>(env.rng(), &aes_key, &plaintext, true).unwrap();
|
||||||
assert_eq!(ciphertext1.len(), 80);
|
assert_eq!(ciphertext1.len(), 80);
|
||||||
assert_eq!(ciphertext2.len(), 80);
|
assert_eq!(ciphertext2.len(), 80);
|
||||||
// The ciphertext should mutate in all blocks with a different IV.
|
// The ciphertext should mutate in all blocks with a different IV.
|
||||||
|
|||||||
@@ -256,7 +256,7 @@ impl Ctap1Command {
|
|||||||
) -> Result<Vec<u8>, Ctap1StatusCode> {
|
) -> Result<Vec<u8>, Ctap1StatusCode> {
|
||||||
let private_key = PrivateKey::new_ecdsa(env);
|
let private_key = PrivateKey::new_ecdsa(env);
|
||||||
let sk = private_key
|
let sk = private_key
|
||||||
.ecdsa_key(env)
|
.ecdsa_key::<E>()
|
||||||
.map_err(|_| Ctap1StatusCode::SW_INTERNAL_EXCEPTION)?;
|
.map_err(|_| Ctap1StatusCode::SW_INTERNAL_EXCEPTION)?;
|
||||||
let pk = sk.public_key();
|
let pk = sk.public_key();
|
||||||
let credential_source = CredentialSource {
|
let credential_source = CredentialSource {
|
||||||
@@ -333,10 +333,6 @@ impl Ctap1Command {
|
|||||||
.map_err(|_| Ctap1StatusCode::SW_WRONG_DATA)?;
|
.map_err(|_| Ctap1StatusCode::SW_WRONG_DATA)?;
|
||||||
let credential_source = filter_listed_credential(credential_source, false)
|
let credential_source = filter_listed_credential(credential_source, false)
|
||||||
.ok_or(Ctap1StatusCode::SW_WRONG_DATA)?;
|
.ok_or(Ctap1StatusCode::SW_WRONG_DATA)?;
|
||||||
let ecdsa_key = credential_source
|
|
||||||
.private_key
|
|
||||||
.ecdsa_key(env)
|
|
||||||
.map_err(|_| Ctap1StatusCode::SW_WRONG_DATA)?;
|
|
||||||
if flags == Ctap1Flags::CheckOnly {
|
if flags == Ctap1Flags::CheckOnly {
|
||||||
return Err(Ctap1StatusCode::SW_COND_USE_NOT_SATISFIED);
|
return Err(Ctap1StatusCode::SW_COND_USE_NOT_SATISFIED);
|
||||||
}
|
}
|
||||||
@@ -351,10 +347,13 @@ impl Ctap1Command {
|
|||||||
)
|
)
|
||||||
.map_err(|_| Ctap1StatusCode::SW_WRONG_DATA)?;
|
.map_err(|_| Ctap1StatusCode::SW_WRONG_DATA)?;
|
||||||
signature_data.extend(&challenge);
|
signature_data.extend(&challenge);
|
||||||
let signature = ecdsa_key.sign(&signature_data);
|
let signature = credential_source
|
||||||
|
.private_key
|
||||||
|
.sign_and_encode::<E>(&signature_data)
|
||||||
|
.map_err(|_| Ctap1StatusCode::SW_INTERNAL_EXCEPTION)?;
|
||||||
|
|
||||||
let mut response = signature_data[application.len()..application.len() + 5].to_vec();
|
let mut response = signature_data[application.len()..application.len() + 5].to_vec();
|
||||||
response.extend(signature.to_der());
|
response.extend(&signature);
|
||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
use super::status_code::Ctap2StatusCode;
|
use super::status_code::Ctap2StatusCode;
|
||||||
use crate::api::crypto::{ecdh, ecdsa, EC_FIELD_SIZE};
|
use crate::api::crypto::{ecdh, ecdsa, EC_FIELD_SIZE};
|
||||||
use crate::api::private_key::PrivateKey;
|
use crate::api::private_key::PrivateKey;
|
||||||
|
use crate::env::{AesKey, Env};
|
||||||
use alloc::string::String;
|
use alloc::string::String;
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
#[cfg(feature = "fuzz")]
|
#[cfg(feature = "fuzz")]
|
||||||
@@ -601,8 +602,6 @@ pub struct PublicKeyCredentialSource {
|
|||||||
// is associated with a unique tag, implemented with a CBOR unsigned key.
|
// is associated with a unique tag, implemented with a CBOR unsigned key.
|
||||||
enum PublicKeyCredentialSourceField {
|
enum PublicKeyCredentialSourceField {
|
||||||
CredentialId = 0,
|
CredentialId = 0,
|
||||||
// Deprecated, we still read this field for backwards compatibility.
|
|
||||||
EcdsaPrivateKey = 1,
|
|
||||||
RpId = 2,
|
RpId = 2,
|
||||||
UserHandle = 3,
|
UserHandle = 3,
|
||||||
UserDisplayName = 4,
|
UserDisplayName = 4,
|
||||||
@@ -616,6 +615,7 @@ enum PublicKeyCredentialSourceField {
|
|||||||
// When a field is removed, its tag should be reserved and not used for new fields. We document
|
// When a field is removed, its tag should be reserved and not used for new fields. We document
|
||||||
// those reserved tags below.
|
// those reserved tags below.
|
||||||
// Reserved tags:
|
// Reserved tags:
|
||||||
|
// - EcdsaPrivateKey = 1,
|
||||||
// - CredRandom = 5,
|
// - CredRandom = 5,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -625,32 +625,41 @@ impl From<PublicKeyCredentialSourceField> for cbor::Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<PublicKeyCredentialSource> for cbor::Value {
|
impl PublicKeyCredentialSource {
|
||||||
fn from(credential: PublicKeyCredentialSource) -> cbor::Value {
|
// Relying parties do not need to provide the credential ID in an allow_list if true.
|
||||||
cbor_map_options! {
|
pub fn is_discoverable(&self) -> bool {
|
||||||
PublicKeyCredentialSourceField::CredentialId => Some(credential.credential_id),
|
self.cred_protect_policy.is_none()
|
||||||
PublicKeyCredentialSourceField::RpId => Some(credential.rp_id),
|
|| self.cred_protect_policy
|
||||||
PublicKeyCredentialSourceField::UserHandle => Some(credential.user_handle),
|
== Some(CredentialProtectionPolicy::UserVerificationOptional)
|
||||||
PublicKeyCredentialSourceField::UserDisplayName => credential.user_display_name,
|
|
||||||
PublicKeyCredentialSourceField::CredProtectPolicy => credential.cred_protect_policy,
|
|
||||||
PublicKeyCredentialSourceField::CreationOrder => credential.creation_order,
|
|
||||||
PublicKeyCredentialSourceField::UserName => credential.user_name,
|
|
||||||
PublicKeyCredentialSourceField::UserIcon => credential.user_icon,
|
|
||||||
PublicKeyCredentialSourceField::CredBlob => credential.cred_blob,
|
|
||||||
PublicKeyCredentialSourceField::LargeBlobKey => credential.large_blob_key,
|
|
||||||
PublicKeyCredentialSourceField::PrivateKey => credential.private_key,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn to_cbor<E: Env>(
|
||||||
|
self,
|
||||||
|
rng: &mut E::Rng,
|
||||||
|
wrap_key: &AesKey<E>,
|
||||||
|
) -> Result<cbor::Value, Ctap2StatusCode> {
|
||||||
|
Ok(cbor_map_options! {
|
||||||
|
PublicKeyCredentialSourceField::CredentialId => Some(self.credential_id),
|
||||||
|
PublicKeyCredentialSourceField::RpId => Some(self.rp_id),
|
||||||
|
PublicKeyCredentialSourceField::UserHandle => Some(self.user_handle),
|
||||||
|
PublicKeyCredentialSourceField::UserDisplayName => self.user_display_name,
|
||||||
|
PublicKeyCredentialSourceField::CredProtectPolicy => self.cred_protect_policy,
|
||||||
|
PublicKeyCredentialSourceField::CreationOrder => self.creation_order,
|
||||||
|
PublicKeyCredentialSourceField::UserName => self.user_name,
|
||||||
|
PublicKeyCredentialSourceField::UserIcon => self.user_icon,
|
||||||
|
PublicKeyCredentialSourceField::CredBlob => self.cred_blob,
|
||||||
|
PublicKeyCredentialSourceField::LargeBlobKey => self.large_blob_key,
|
||||||
|
PublicKeyCredentialSourceField::PrivateKey => self.private_key.to_cbor::<E>(rng, wrap_key)?,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<cbor::Value> for PublicKeyCredentialSource {
|
pub fn from_cbor<E: Env>(
|
||||||
type Error = Ctap2StatusCode;
|
wrap_key: &AesKey<E>,
|
||||||
|
cbor_value: cbor::Value,
|
||||||
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
|
) -> Result<Self, Ctap2StatusCode> {
|
||||||
destructure_cbor_map! {
|
destructure_cbor_map! {
|
||||||
let {
|
let {
|
||||||
PublicKeyCredentialSourceField::CredentialId => credential_id,
|
PublicKeyCredentialSourceField::CredentialId => credential_id,
|
||||||
PublicKeyCredentialSourceField::EcdsaPrivateKey => ecdsa_private_key,
|
|
||||||
PublicKeyCredentialSourceField::RpId => rp_id,
|
PublicKeyCredentialSourceField::RpId => rp_id,
|
||||||
PublicKeyCredentialSourceField::UserHandle => user_handle,
|
PublicKeyCredentialSourceField::UserHandle => user_handle,
|
||||||
PublicKeyCredentialSourceField::UserDisplayName => user_display_name,
|
PublicKeyCredentialSourceField::UserDisplayName => user_display_name,
|
||||||
@@ -676,17 +685,7 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialSource {
|
|||||||
let user_icon = user_icon.map(extract_text_string).transpose()?;
|
let user_icon = user_icon.map(extract_text_string).transpose()?;
|
||||||
let cred_blob = cred_blob.map(extract_byte_string).transpose()?;
|
let cred_blob = cred_blob.map(extract_byte_string).transpose()?;
|
||||||
let large_blob_key = large_blob_key.map(extract_byte_string).transpose()?;
|
let large_blob_key = large_blob_key.map(extract_byte_string).transpose()?;
|
||||||
|
let private_key = PrivateKey::from_cbor::<E>(wrap_key, ok_or_missing(private_key)?)?;
|
||||||
// Parse the private key from the deprecated field if necessary.
|
|
||||||
let ecdsa_private_key = ecdsa_private_key.map(extract_byte_string).transpose()?;
|
|
||||||
let private_key = private_key.map(PrivateKey::try_from).transpose()?;
|
|
||||||
let private_key = match (ecdsa_private_key, private_key) {
|
|
||||||
(None, None) => return Err(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER),
|
|
||||||
(Some(_), Some(_)) => return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR),
|
|
||||||
(Some(k), None) => PrivateKey::new_ecdsa_from_bytes(&k)
|
|
||||||
.ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?,
|
|
||||||
(None, Some(k)) => k,
|
|
||||||
};
|
|
||||||
|
|
||||||
// We don't return whether there were unknown fields in the CBOR value. This means that
|
// We don't return whether there were unknown fields in the CBOR value. This means that
|
||||||
// deserialization is not injective. In particular deserialization is only an inverse of
|
// deserialization is not injective. In particular deserialization is only an inverse of
|
||||||
@@ -715,15 +714,6 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialSource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PublicKeyCredentialSource {
|
|
||||||
// Relying parties do not need to provide the credential ID in an allow_list if true.
|
|
||||||
pub fn is_discoverable(&self) -> bool {
|
|
||||||
self.cred_protect_policy.is_none()
|
|
||||||
|| self.cred_protect_policy
|
|
||||||
== Some(CredentialProtectionPolicy::UserVerificationOptional)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The COSE key is used for both ECDH and ECDSA public keys for transmission.
|
// The COSE key is used for both ECDH and ECDSA public keys for transmission.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
#[cfg_attr(feature = "fuzz", derive(Arbitrary))]
|
#[cfg_attr(feature = "fuzz", derive(Arbitrary))]
|
||||||
@@ -1232,6 +1222,7 @@ mod test {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::api::crypto::ecdh::PublicKey as _;
|
use crate::api::crypto::ecdh::PublicKey as _;
|
||||||
use crate::api::crypto::ecdsa::PublicKey as _;
|
use crate::api::crypto::ecdsa::PublicKey as _;
|
||||||
|
use crate::api::key_store::KeyStore;
|
||||||
use crate::api::rng::Rng;
|
use crate::api::rng::Rng;
|
||||||
use crate::env::test::TestEnv;
|
use crate::env::test::TestEnv;
|
||||||
use crate::env::{EcdhPk, EcdsaPk, Env};
|
use crate::env::{EcdhPk, EcdsaPk, Env};
|
||||||
@@ -2105,6 +2096,7 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_credential_source_cbor_round_trip() {
|
fn test_credential_source_cbor_round_trip() {
|
||||||
let mut env = TestEnv::default();
|
let mut env = TestEnv::default();
|
||||||
|
let wrap_key = env.key_store().wrap_key::<TestEnv>().unwrap();
|
||||||
let private_key = PrivateKey::new_ecdsa(&mut env);
|
let private_key = PrivateKey::new_ecdsa(&mut env);
|
||||||
let credential = PublicKeyCredentialSource {
|
let credential = PublicKeyCredentialSource {
|
||||||
key_type: PublicKeyCredentialType::PublicKey,
|
key_type: PublicKeyCredentialType::PublicKey,
|
||||||
@@ -2121,8 +2113,12 @@ mod test {
|
|||||||
large_blob_key: None,
|
large_blob_key: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let cbor_value = credential
|
||||||
|
.clone()
|
||||||
|
.to_cbor::<TestEnv>(env.rng(), &wrap_key)
|
||||||
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
PublicKeyCredentialSource::try_from(cbor::Value::from(credential.clone())),
|
PublicKeyCredentialSource::from_cbor::<TestEnv>(&wrap_key, cbor_value),
|
||||||
Ok(credential.clone())
|
Ok(credential.clone())
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -2131,8 +2127,12 @@ mod test {
|
|||||||
..credential
|
..credential
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let cbor_value = credential
|
||||||
|
.clone()
|
||||||
|
.to_cbor::<TestEnv>(env.rng(), &wrap_key)
|
||||||
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
PublicKeyCredentialSource::try_from(cbor::Value::from(credential.clone())),
|
PublicKeyCredentialSource::from_cbor::<TestEnv>(&wrap_key, cbor_value),
|
||||||
Ok(credential.clone())
|
Ok(credential.clone())
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -2141,8 +2141,12 @@ mod test {
|
|||||||
..credential
|
..credential
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let cbor_value = credential
|
||||||
|
.clone()
|
||||||
|
.to_cbor::<TestEnv>(env.rng(), &wrap_key)
|
||||||
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
PublicKeyCredentialSource::try_from(cbor::Value::from(credential.clone())),
|
PublicKeyCredentialSource::from_cbor::<TestEnv>(&wrap_key, cbor_value),
|
||||||
Ok(credential.clone())
|
Ok(credential.clone())
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -2151,8 +2155,12 @@ mod test {
|
|||||||
..credential
|
..credential
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let cbor_value = credential
|
||||||
|
.clone()
|
||||||
|
.to_cbor::<TestEnv>(env.rng(), &wrap_key)
|
||||||
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
PublicKeyCredentialSource::try_from(cbor::Value::from(credential.clone())),
|
PublicKeyCredentialSource::from_cbor::<TestEnv>(&wrap_key, cbor_value),
|
||||||
Ok(credential.clone())
|
Ok(credential.clone())
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -2161,8 +2169,12 @@ mod test {
|
|||||||
..credential
|
..credential
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let cbor_value = credential
|
||||||
|
.clone()
|
||||||
|
.to_cbor::<TestEnv>(env.rng(), &wrap_key)
|
||||||
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
PublicKeyCredentialSource::try_from(cbor::Value::from(credential.clone())),
|
PublicKeyCredentialSource::from_cbor::<TestEnv>(&wrap_key, cbor_value),
|
||||||
Ok(credential.clone())
|
Ok(credential.clone())
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -2171,8 +2183,12 @@ mod test {
|
|||||||
..credential
|
..credential
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let cbor_value = credential
|
||||||
|
.clone()
|
||||||
|
.to_cbor::<TestEnv>(env.rng(), &wrap_key)
|
||||||
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
PublicKeyCredentialSource::try_from(cbor::Value::from(credential.clone())),
|
PublicKeyCredentialSource::from_cbor::<TestEnv>(&wrap_key, cbor_value),
|
||||||
Ok(credential.clone())
|
Ok(credential.clone())
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -2181,91 +2197,28 @@ mod test {
|
|||||||
..credential
|
..credential
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let cbor_value = credential
|
||||||
|
.clone()
|
||||||
|
.to_cbor::<TestEnv>(env.rng(), &wrap_key)
|
||||||
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
PublicKeyCredentialSource::try_from(cbor::Value::from(credential.clone())),
|
PublicKeyCredentialSource::from_cbor::<TestEnv>(&wrap_key, cbor_value),
|
||||||
Ok(credential)
|
Ok(credential)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_credential_source_cbor_read_legacy() {
|
|
||||||
let mut env = TestEnv::default();
|
|
||||||
let private_key = PrivateKey::new_ecdsa(&mut env);
|
|
||||||
let key_bytes = private_key.to_bytes();
|
|
||||||
let credential = PublicKeyCredentialSource {
|
|
||||||
key_type: PublicKeyCredentialType::PublicKey,
|
|
||||||
credential_id: env.rng().gen_uniform_u8x32().to_vec(),
|
|
||||||
private_key,
|
|
||||||
rp_id: "example.com".to_string(),
|
|
||||||
user_handle: b"foo".to_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,
|
|
||||||
};
|
|
||||||
|
|
||||||
let source_cbor = cbor_map! {
|
|
||||||
PublicKeyCredentialSourceField::CredentialId => credential.credential_id.clone(),
|
|
||||||
PublicKeyCredentialSourceField::EcdsaPrivateKey => key_bytes,
|
|
||||||
PublicKeyCredentialSourceField::RpId => credential.rp_id.clone(),
|
|
||||||
PublicKeyCredentialSourceField::UserHandle => credential.user_handle.clone(),
|
|
||||||
};
|
|
||||||
assert_eq!(
|
|
||||||
PublicKeyCredentialSource::try_from(source_cbor),
|
|
||||||
Ok(credential)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_credential_source_cbor_legacy_error() {
|
|
||||||
let mut env = TestEnv::default();
|
|
||||||
let private_key = PrivateKey::new_ecdsa(&mut env);
|
|
||||||
let key_bytes = private_key.to_bytes();
|
|
||||||
let credential = PublicKeyCredentialSource {
|
|
||||||
key_type: PublicKeyCredentialType::PublicKey,
|
|
||||||
credential_id: env.rng().gen_uniform_u8x32().to_vec(),
|
|
||||||
private_key: private_key.clone(),
|
|
||||||
rp_id: "example.com".to_string(),
|
|
||||||
user_handle: b"foo".to_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,
|
|
||||||
};
|
|
||||||
|
|
||||||
let source_cbor = cbor_map! {
|
|
||||||
PublicKeyCredentialSourceField::CredentialId => credential.credential_id.clone(),
|
|
||||||
PublicKeyCredentialSourceField::RpId => credential.rp_id.clone(),
|
|
||||||
PublicKeyCredentialSourceField::UserHandle => credential.user_handle.clone(),
|
|
||||||
};
|
|
||||||
assert_eq!(
|
|
||||||
PublicKeyCredentialSource::try_from(source_cbor),
|
|
||||||
Err(Ctap2StatusCode::CTAP2_ERR_MISSING_PARAMETER)
|
|
||||||
);
|
|
||||||
|
|
||||||
let source_cbor = cbor_map! {
|
|
||||||
PublicKeyCredentialSourceField::CredentialId => credential.credential_id,
|
|
||||||
PublicKeyCredentialSourceField::EcdsaPrivateKey => key_bytes,
|
|
||||||
PublicKeyCredentialSourceField::RpId => credential.rp_id,
|
|
||||||
PublicKeyCredentialSourceField::UserHandle => credential.user_handle,
|
|
||||||
PublicKeyCredentialSourceField::PrivateKey => private_key,
|
|
||||||
};
|
|
||||||
assert_eq!(
|
|
||||||
PublicKeyCredentialSource::try_from(source_cbor),
|
|
||||||
Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_credential_source_invalid_cbor() {
|
fn test_credential_source_invalid_cbor() {
|
||||||
assert!(PublicKeyCredentialSource::try_from(cbor_false!()).is_err());
|
let mut env = TestEnv::default();
|
||||||
assert!(PublicKeyCredentialSource::try_from(cbor_array!(false)).is_err());
|
let wrap_key = env.key_store().wrap_key::<TestEnv>().unwrap();
|
||||||
assert!(PublicKeyCredentialSource::try_from(cbor_array!(b"foo".to_vec())).is_err());
|
assert!(PublicKeyCredentialSource::from_cbor::<TestEnv>(&wrap_key, cbor_false!()).is_err());
|
||||||
|
assert!(
|
||||||
|
PublicKeyCredentialSource::from_cbor::<TestEnv>(&wrap_key, cbor_array!(false)).is_err()
|
||||||
|
);
|
||||||
|
assert!(PublicKeyCredentialSource::from_cbor::<TestEnv>(
|
||||||
|
&wrap_key,
|
||||||
|
cbor_array!(b"foo".to_vec())
|
||||||
|
)
|
||||||
|
.is_err());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -917,7 +917,7 @@ impl<E: Env> CtapState<E> {
|
|||||||
}
|
}
|
||||||
auth_data.extend(vec![0x00, credential_id.len() as u8]);
|
auth_data.extend(vec![0x00, credential_id.len() as u8]);
|
||||||
auth_data.extend(&credential_id);
|
auth_data.extend(&credential_id);
|
||||||
let public_cose_key = private_key.get_pub_key(env)?;
|
let public_cose_key = private_key.get_pub_key::<E>()?;
|
||||||
cbor_write(cbor::Value::from(public_cose_key), &mut auth_data)?;
|
cbor_write(cbor::Value::from(public_cose_key), &mut auth_data)?;
|
||||||
if has_extension_output {
|
if has_extension_output {
|
||||||
let hmac_secret_output = if extensions.hmac_secret {
|
let hmac_secret_output = if extensions.hmac_secret {
|
||||||
@@ -965,7 +965,7 @@ impl<E: Env> CtapState<E> {
|
|||||||
Some(vec![certificate]),
|
Some(vec![certificate]),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
None => (private_key.sign_and_encode(env, &signature_data)?, None),
|
None => (private_key.sign_and_encode::<E>(&signature_data)?, None),
|
||||||
};
|
};
|
||||||
let attestation_statement = PackedAttestationStatement {
|
let attestation_statement = PackedAttestationStatement {
|
||||||
alg: SignatureAlgorithm::Es256 as i64,
|
alg: SignatureAlgorithm::Es256 as i64,
|
||||||
@@ -1051,7 +1051,7 @@ impl<E: Env> CtapState<E> {
|
|||||||
signature_data.extend(client_data_hash);
|
signature_data.extend(client_data_hash);
|
||||||
let signature = credential
|
let signature = credential
|
||||||
.private_key
|
.private_key
|
||||||
.sign_and_encode(env, &signature_data)?;
|
.sign_and_encode::<E>(&signature_data)?;
|
||||||
|
|
||||||
let cred_desc = PublicKeyCredentialDescriptor {
|
let cred_desc = PublicKeyCredentialDescriptor {
|
||||||
key_type: PublicKeyCredentialType::PublicKey,
|
key_type: PublicKeyCredentialType::PublicKey,
|
||||||
|
|||||||
@@ -225,7 +225,7 @@ impl<E: Env> SharedSecretV1<E> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn encrypt(&self, env: &mut E, plaintext: &[u8]) -> Result<Vec<u8>, Ctap2StatusCode> {
|
fn encrypt(&self, env: &mut E, plaintext: &[u8]) -> Result<Vec<u8>, Ctap2StatusCode> {
|
||||||
aes256_cbc_encrypt(env, &self.aes_key, plaintext, false)
|
aes256_cbc_encrypt::<E>(env.rng(), &self.aes_key, plaintext, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decrypt(&self, ciphertext: &[u8]) -> Result<Secret<[u8]>, Ctap2StatusCode> {
|
fn decrypt(&self, ciphertext: &[u8]) -> Result<Secret<[u8]>, Ctap2StatusCode> {
|
||||||
@@ -263,7 +263,7 @@ impl<E: Env> SharedSecretV2<E> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn encrypt(&self, env: &mut E, plaintext: &[u8]) -> Result<Vec<u8>, Ctap2StatusCode> {
|
fn encrypt(&self, env: &mut E, plaintext: &[u8]) -> Result<Vec<u8>, Ctap2StatusCode> {
|
||||||
aes256_cbc_encrypt(env, &self.aes_key, plaintext, true)
|
aes256_cbc_encrypt::<E>(env.rng(), &self.aes_key, plaintext, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decrypt(&self, ciphertext: &[u8]) -> Result<Secret<[u8]>, Ctap2StatusCode> {
|
fn decrypt(&self, ciphertext: &[u8]) -> Result<Secret<[u8]>, Ctap2StatusCode> {
|
||||||
|
|||||||
@@ -24,13 +24,12 @@ use crate::ctap::data_formats::{
|
|||||||
};
|
};
|
||||||
use crate::ctap::status_code::Ctap2StatusCode;
|
use crate::ctap::status_code::Ctap2StatusCode;
|
||||||
use crate::ctap::INITIAL_SIGNATURE_COUNTER;
|
use crate::ctap::INITIAL_SIGNATURE_COUNTER;
|
||||||
use crate::env::Env;
|
use crate::env::{AesKey, Env};
|
||||||
use alloc::string::String;
|
use alloc::string::String;
|
||||||
use alloc::vec;
|
use alloc::vec;
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use arrayref::array_ref;
|
use arrayref::array_ref;
|
||||||
use core::cmp;
|
use core::cmp;
|
||||||
use core::convert::TryInto;
|
|
||||||
use persistent_store::{fragment, StoreUpdate};
|
use persistent_store::{fragment, StoreUpdate};
|
||||||
#[cfg(feature = "config_command")]
|
#[cfg(feature = "config_command")]
|
||||||
use sk_cbor::cbor_array_vec;
|
use sk_cbor::cbor_array_vec;
|
||||||
@@ -56,8 +55,8 @@ pub fn init(env: &mut impl Env) -> Result<(), Ctap2StatusCode> {
|
|||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// Returns `CTAP2_ERR_VENDOR_INTERNAL_ERROR` if the key does not hold a valid credential.
|
/// Returns `CTAP2_ERR_VENDOR_INTERNAL_ERROR` if the key does not hold a valid credential.
|
||||||
pub fn get_credential(
|
pub fn get_credential<E: Env>(
|
||||||
env: &mut impl Env,
|
env: &mut E,
|
||||||
key: usize,
|
key: usize,
|
||||||
) -> Result<PublicKeyCredentialSource, Ctap2StatusCode> {
|
) -> Result<PublicKeyCredentialSource, Ctap2StatusCode> {
|
||||||
let min_key = key::CREDENTIALS.start;
|
let min_key = key::CREDENTIALS.start;
|
||||||
@@ -68,7 +67,8 @@ pub fn get_credential(
|
|||||||
.store()
|
.store()
|
||||||
.find(key)?
|
.find(key)?
|
||||||
.ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?;
|
.ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?;
|
||||||
deserialize_credential(&credential_entry)
|
let wrap_key = env.key_store().wrap_key::<E>()?;
|
||||||
|
deserialize_credential::<E>(&wrap_key, &credential_entry)
|
||||||
.ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)
|
.ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,8 +118,8 @@ pub fn find_credential(
|
|||||||
/// Stores or updates a credential.
|
/// Stores or updates a credential.
|
||||||
///
|
///
|
||||||
/// If a credential with the same RP id and user handle already exists, it is replaced.
|
/// If a credential with the same RP id and user handle already exists, it is replaced.
|
||||||
pub fn store_credential(
|
pub fn store_credential<E: Env>(
|
||||||
env: &mut impl Env,
|
env: &mut E,
|
||||||
new_credential: PublicKeyCredentialSource,
|
new_credential: PublicKeyCredentialSource,
|
||||||
) -> Result<(), Ctap2StatusCode> {
|
) -> Result<(), Ctap2StatusCode> {
|
||||||
let max_supported_resident_keys = env.customization().max_supported_resident_keys();
|
let max_supported_resident_keys = env.customization().max_supported_resident_keys();
|
||||||
@@ -158,7 +158,8 @@ pub fn store_credential(
|
|||||||
// This is an existing credential being updated, we reuse its key.
|
// This is an existing credential being updated, we reuse its key.
|
||||||
Some(x) => x,
|
Some(x) => x,
|
||||||
};
|
};
|
||||||
let value = serialize_credential(new_credential)?;
|
let wrap_key = env.key_store().wrap_key::<E>()?;
|
||||||
|
let value = serialize_credential::<E>(env, &wrap_key, new_credential)?;
|
||||||
env.store().insert(key, &value)?;
|
env.store().insert(key, &value)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -178,8 +179,8 @@ pub fn delete_credential(env: &mut impl Env, credential_id: &[u8]) -> Result<(),
|
|||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// Returns `CTAP2_ERR_NO_CREDENTIALS` if the credential is not found.
|
/// Returns `CTAP2_ERR_NO_CREDENTIALS` if the credential is not found.
|
||||||
pub fn update_credential(
|
pub fn update_credential<E: Env>(
|
||||||
env: &mut impl Env,
|
env: &mut E,
|
||||||
credential_id: &[u8],
|
credential_id: &[u8],
|
||||||
user: PublicKeyCredentialUserEntity,
|
user: PublicKeyCredentialUserEntity,
|
||||||
) -> Result<(), Ctap2StatusCode> {
|
) -> Result<(), Ctap2StatusCode> {
|
||||||
@@ -187,7 +188,8 @@ pub fn update_credential(
|
|||||||
credential.user_name = user.user_name;
|
credential.user_name = user.user_name;
|
||||||
credential.user_display_name = user.user_display_name;
|
credential.user_display_name = user.user_display_name;
|
||||||
credential.user_icon = user.user_icon;
|
credential.user_icon = user.user_icon;
|
||||||
let value = serialize_credential(credential)?;
|
let wrap_key = env.key_store().wrap_key::<E>()?;
|
||||||
|
let value = serialize_credential::<E>(env, &wrap_key, credential)?;
|
||||||
Ok(env.store().insert(key, &value)?)
|
Ok(env.store().insert(key, &value)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,7 +217,7 @@ pub fn iter_credentials<'a, E: Env>(
|
|||||||
env: &'a mut E,
|
env: &'a mut E,
|
||||||
result: &'a mut Result<(), Ctap2StatusCode>,
|
result: &'a mut Result<(), Ctap2StatusCode>,
|
||||||
) -> Result<IterCredentials<'a, E>, Ctap2StatusCode> {
|
) -> Result<IterCredentials<'a, E>, Ctap2StatusCode> {
|
||||||
IterCredentials::new(env.store(), result)
|
IterCredentials::new(env, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the next creation order.
|
/// Returns the next creation order.
|
||||||
@@ -525,6 +527,9 @@ pub struct IterCredentials<'a, E: Env> {
|
|||||||
/// The store being iterated.
|
/// The store being iterated.
|
||||||
store: &'a persistent_store::Store<E::Storage>,
|
store: &'a persistent_store::Store<E::Storage>,
|
||||||
|
|
||||||
|
/// The key store for credential unwrapping.
|
||||||
|
wrap_key: AesKey<E>,
|
||||||
|
|
||||||
/// The store iterator.
|
/// The store iterator.
|
||||||
iter: persistent_store::StoreIter<'a>,
|
iter: persistent_store::StoreIter<'a>,
|
||||||
|
|
||||||
@@ -538,12 +543,15 @@ pub struct IterCredentials<'a, E: Env> {
|
|||||||
impl<'a, E: Env> IterCredentials<'a, E> {
|
impl<'a, E: Env> IterCredentials<'a, E> {
|
||||||
/// Creates a credential iterator.
|
/// Creates a credential iterator.
|
||||||
fn new(
|
fn new(
|
||||||
store: &'a persistent_store::Store<E::Storage>,
|
env: &'a mut E,
|
||||||
result: &'a mut Result<(), Ctap2StatusCode>,
|
result: &'a mut Result<(), Ctap2StatusCode>,
|
||||||
) -> Result<Self, Ctap2StatusCode> {
|
) -> Result<Self, Ctap2StatusCode> {
|
||||||
|
let wrap_key = env.key_store().wrap_key::<E>()?;
|
||||||
|
let store = env.store();
|
||||||
let iter = store.iter()?;
|
let iter = store.iter()?;
|
||||||
Ok(IterCredentials {
|
Ok(IterCredentials {
|
||||||
store,
|
store,
|
||||||
|
wrap_key,
|
||||||
iter,
|
iter,
|
||||||
result,
|
result,
|
||||||
})
|
})
|
||||||
@@ -576,7 +584,8 @@ impl<'a, E: Env> Iterator for IterCredentials<'a, E> {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let value = self.unwrap(handle.get_value(self.store).ok())?;
|
let value = self.unwrap(handle.get_value(self.store).ok())?;
|
||||||
let credential = self.unwrap(deserialize_credential(&value))?;
|
let deserialized = deserialize_credential::<E>(&self.wrap_key, &value);
|
||||||
|
let credential = self.unwrap(deserialized)?;
|
||||||
return Some((key, credential));
|
return Some((key, credential));
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
@@ -584,15 +593,22 @@ impl<'a, E: Env> Iterator for IterCredentials<'a, E> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Deserializes a credential from storage representation.
|
/// Deserializes a credential from storage representation.
|
||||||
fn deserialize_credential(data: &[u8]) -> Option<PublicKeyCredentialSource> {
|
fn deserialize_credential<E: Env>(
|
||||||
|
wrap_key: &AesKey<E>,
|
||||||
|
data: &[u8],
|
||||||
|
) -> Option<PublicKeyCredentialSource> {
|
||||||
let cbor = super::cbor_read(data).ok()?;
|
let cbor = super::cbor_read(data).ok()?;
|
||||||
cbor.try_into().ok()
|
PublicKeyCredentialSource::from_cbor::<E>(wrap_key, cbor).ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Serializes a credential to storage representation.
|
/// Serializes a credential to storage representation.
|
||||||
fn serialize_credential(credential: PublicKeyCredentialSource) -> Result<Vec<u8>, Ctap2StatusCode> {
|
fn serialize_credential<E: Env>(
|
||||||
|
env: &mut E,
|
||||||
|
wrap_key: &AesKey<E>,
|
||||||
|
credential: PublicKeyCredentialSource,
|
||||||
|
) -> Result<Vec<u8>, Ctap2StatusCode> {
|
||||||
let mut data = Vec::new();
|
let mut data = Vec::new();
|
||||||
super::cbor_write(credential.into(), &mut data)?;
|
super::cbor_write(credential.to_cbor::<E>(env.rng(), wrap_key)?, &mut data)?;
|
||||||
Ok(data)
|
Ok(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1098,6 +1114,7 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_serialize_deserialize_credential() {
|
fn test_serialize_deserialize_credential() {
|
||||||
let mut env = TestEnv::default();
|
let mut env = TestEnv::default();
|
||||||
|
let wrap_key = env.key_store().wrap_key::<TestEnv>().unwrap();
|
||||||
let private_key = PrivateKey::new_ecdsa(&mut env);
|
let private_key = PrivateKey::new_ecdsa(&mut env);
|
||||||
let credential = PublicKeyCredentialSource {
|
let credential = PublicKeyCredentialSource {
|
||||||
key_type: PublicKeyCredentialType::PublicKey,
|
key_type: PublicKeyCredentialType::PublicKey,
|
||||||
@@ -1113,8 +1130,9 @@ mod test {
|
|||||||
cred_blob: Some(vec![0xCB]),
|
cred_blob: Some(vec![0xCB]),
|
||||||
large_blob_key: Some(vec![0x1B]),
|
large_blob_key: Some(vec![0x1B]),
|
||||||
};
|
};
|
||||||
let serialized = serialize_credential(credential.clone()).unwrap();
|
let serialized =
|
||||||
let reconstructed = deserialize_credential(&serialized).unwrap();
|
serialize_credential::<TestEnv>(&mut env, &wrap_key, credential.clone()).unwrap();
|
||||||
|
let reconstructed = deserialize_credential::<TestEnv>(&wrap_key, &serialized).unwrap();
|
||||||
assert_eq!(credential, reconstructed);
|
assert_eq!(credential, reconstructed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user