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:
kaczmarczyck
2023-08-10 14:20:39 +02:00
committed by GitHub
parent 96af5e81a5
commit 87f0711284
9 changed files with 254 additions and 350 deletions

View File

@@ -13,7 +13,6 @@
// limitations under the License.
use crate::api::crypto::aes256::Aes256;
use crate::api::crypto::ecdsa::SecretKey as _;
use crate::api::crypto::hmac256::Hmac256;
use crate::api::crypto::HASH_SIZE;
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::secret::Secret;
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::Vec;
use core::convert::{TryFrom, TryInto};
@@ -30,14 +29,13 @@ use rand_core::RngCore;
use sk_cbor as cbor;
use sk_cbor::{cbor_map_options, destructure_cbor_map};
const LEGACY_CREDENTIAL_ID_SIZE: usize = 112;
// CBOR credential IDs consist of
// - 1 byte : version number
// - 16 bytes: initialization vector for AES-256,
// - 192 bytes: encrypted block of the key handle cbor,
// - 32 bytes: HMAC-SHA256 over everything else.
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 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.
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.
///
/// 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.
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.
fn reset(&mut self) -> Result<(), Error>;
}
@@ -138,14 +136,23 @@ impl<T: Helper> KeyStore for T {
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.
///
/// Other information, such as a user name, are not stored. Since encrypted credential IDs are
/// stored server-side, this information is already available (unencrypted).
fn wrap_credential(&mut self, credential: CredentialSource) -> Result<Vec<u8>, Error> {
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! {
CredentialSourceField::PrivateKey => credential.private_key,
CredentialSourceField::PrivateKey => private_key_cbor,
CredentialSourceField::RpIdHash => credential.rp_id_hash,
CredentialSourceField::CredProtectPolicy => credential.cred_protect_policy,
CredentialSourceField::CredBlob => credential.cred_blob,
@@ -155,7 +162,7 @@ impl<T: Helper> KeyStore for T {
let master_keys = get_master_keys(self)?;
let aes_key = AesKey::<T>::new(&master_keys.encryption);
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;
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 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:
/// - 1 byte : version number,
/// - 16 bytes: initialization vector for AES-256,
@@ -204,21 +205,18 @@ impl<T: Helper> KeyStore for T {
return Ok(None);
}
let credential_source = if bytes.len() == LEGACY_CREDENTIAL_ID_SIZE {
decrypt_legacy_credential_id::<T>(&master_keys.encryption, &bytes[..hmac_message_size])?
} else {
match bytes[0] {
CBOR_CREDENTIAL_ID_VERSION => {
if bytes.len() != CBOR_CREDENTIAL_ID_SIZE {
return Ok(None);
}
decrypt_cbor_credential_id::<T>(
&master_keys.encryption,
&bytes[1..hmac_message_size],
)?
let credential_source = match bytes[0] {
CBOR_CREDENTIAL_ID_VERSION => {
if bytes.len() != CBOR_CREDENTIAL_ID_SIZE {
return Ok(None);
}
_ => return Ok(None),
decrypt_cbor_credential_id::<T>(
self,
&master_keys.encryption,
&bytes[1..hmac_message_size],
)?
}
_ => return Ok(None),
};
if let Some(credential_source) = &credential_source {
@@ -241,24 +239,8 @@ impl<T: Helper> KeyStore for T {
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> {
// The storage also removes `STORAGE_KEY`, but this makes KeyStore more self-sufficient.
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])
}
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>(
env: &mut E,
encryption_key_bytes: &[u8; 32],
bytes: &[u8],
) -> Result<Option<CredentialSource>, Error> {
@@ -376,7 +335,9 @@ fn decrypt_cbor_credential_id<E: Env>(
}
Ok(match (private_key, 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)?;
if rp_id_hash.len() != 32 {
return Err(Error);
@@ -420,9 +381,11 @@ fn extract_map(cbor_value: cbor::Value) -> Result<Vec<(cbor::Value, cbor::Value)
#[cfg(test)]
mod test {
use super::*;
use crate::api::crypto::ecdsa::SecretKey;
use crate::api::customization::Customization;
use crate::ctap::data_formats::SignatureAlgorithm;
use crate::env::test::TestEnv;
use crate::env::EcdsaSk;
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(true).unwrap(), &cred_random_with_uv);
// ECDSA seeds are well-defined and stable.
let ecdsa_seed = key_store.generate_ecdsa_seed().unwrap();
let ecdsa_key = key_store.derive_ecdsa(&ecdsa_seed).unwrap();
assert_eq!(key_store.derive_ecdsa(&ecdsa_seed), Ok(ecdsa_key));
// Same for wrap key.
let wrap_key = key_store.wrap_key::<TestEnv>().unwrap();
let mut test_block = [0x33; 16];
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
// the case, but it might be better.
key_store.reset().unwrap();
assert_ne!(&key_store.cred_random(false).unwrap(), &cred_random_no_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]
@@ -585,49 +556,6 @@ mod test {
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]
fn test_wrap_credential_size() {
let mut env = TestEnv::default();

View File

@@ -13,11 +13,11 @@
// limitations under the License.
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::secret::Secret;
use crate::ctap::status_code::Ctap2StatusCode;
use crate::env::{EcdsaSk, Env};
use crate::env::{AesKey, EcdsaSk, Env};
use alloc::vec;
use alloc::vec::Vec;
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.
#[cfg_attr(test, derive(PartialEq, Eq))]
pub enum PrivateKey {
// We store the seed instead of the key since we can't get the seed back from the key. We could
// store both if we believe deriving the key is done more than once and costly.
// We store the key bytes instead of the env type. They can be converted into each other.
Ecdsa(Secret<[u8; 32]>),
#[cfg(feature = "ed25519")]
Ed25519(ed25519_compact::SecretKey),
@@ -47,10 +46,12 @@ impl PrivateKey {
/// # Panics
///
/// 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 {
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")]
SignatureAlgorithm::Eddsa => {
@@ -68,8 +69,6 @@ impl PrivateKey {
}
/// 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> {
if bytes.len() != 32 {
return None;
@@ -79,6 +78,7 @@ impl PrivateKey {
Some(PrivateKey::Ecdsa(seed))
}
/// Helper function that creates a private key of type Ed25519.
#[cfg(feature = "ed25519")]
pub fn new_ed25519_from_bytes(bytes: &[u8]) -> Option<Self> {
if bytes.len() != 32 {
@@ -89,19 +89,19 @@ impl PrivateKey {
}
/// 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 {
PrivateKey::Ecdsa(seed) => ecdsa_key_from_seed(env, seed),
PrivateKey::Ecdsa(bytes) => ecdsa_key_from_bytes::<E>(bytes),
#[allow(unreachable_patterns)]
_ => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR),
}
}
/// 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 {
PrivateKey::Ecdsa(ecdsa_seed) => {
CoseKey::from_ecdsa_public_key(ecdsa_key_from_seed(env, ecdsa_seed)?.public_key())
PrivateKey::Ecdsa(bytes) => {
CoseKey::from_ecdsa_public_key(ecdsa_key_from_bytes::<E>(bytes)?.public_key())
}
#[cfg(feature = "ed25519")]
PrivateKey::Ed25519(ed25519_key) => CoseKey::from(ed25519_key.public_key()),
@@ -109,15 +109,9 @@ impl PrivateKey {
}
/// Returns the encoded signature for a given message.
pub(crate) fn sign_and_encode(
&self,
env: &mut impl Env,
message: &[u8],
) -> Result<Vec<u8>, Ctap2StatusCode> {
pub fn sign_and_encode<E: Env>(&self, message: &[u8]) -> Result<Vec<u8>, Ctap2StatusCode> {
Ok(match self {
PrivateKey::Ecdsa(ecdsa_seed) => {
ecdsa_key_from_seed(env, ecdsa_seed)?.sign(message).to_der()
}
PrivateKey::Ecdsa(bytes) => ecdsa_key_from_bytes::<E>(bytes)?.sign(message).to_der(),
#[cfg(feature = "ed25519")]
PrivateKey::Ed25519(ed25519_key) => ed25519_key.sign(message, None).to_vec(),
})
@@ -136,57 +130,55 @@ impl PrivateKey {
pub fn to_bytes(&self) -> Secret<[u8]> {
let mut bytes = Secret::new(32);
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")]
PrivateKey::Ed25519(ed25519_key) => bytes.copy_from_slice(ed25519_key.seed().deref()),
}
bytes
}
}
fn ecdsa_key_from_seed<E: Env>(
env: &mut E,
seed: &[u8; 32],
) -> Result<EcdsaSk<E>, Ctap2StatusCode> {
let ecdsa_bytes = env.key_store().derive_ecdsa(seed)?;
Ok(EcdsaSk::<E>::from_slice(&ecdsa_bytes).unwrap())
}
impl From<&PrivateKey> for cbor::Value {
/// Writes a private key into CBOR format. This exposes the cryptographic secret.
// 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()),
]
pub fn to_cbor<E: Env>(
&self,
rng: &mut E::Rng,
wrap_key: &AesKey<E>,
) -> Result<cbor::Value, Ctap2StatusCode> {
let bytes = self.to_bytes();
let wrapped_bytes = aes256_cbc_encrypt::<E>(rng, wrap_key, &bytes, true)?;
Ok(cbor_array![
cbor_int!(self.signature_algorithm() as i64),
cbor_bytes!(wrapped_bytes),
])
}
}
impl TryFrom<cbor::Value> for PrivateKey {
type Error = Ctap2StatusCode;
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
pub fn from_cbor<E: Env>(
wrap_key: &AesKey<E>,
cbor_value: cbor::Value,
) -> Result<Self, Ctap2StatusCode> {
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())?;
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())? {
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),
#[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),
_ => 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)]
mod test {
use super::*;
use crate::api::key_store::KeyStore;
use crate::env::test::TestEnv;
#[test]
@@ -233,10 +225,10 @@ mod test {
fn test_private_key_get_pub_key() {
let mut env = TestEnv::default();
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();
assert_eq!(
private_key.get_pub_key(&mut env),
private_key.get_pub_key::<TestEnv>(),
Ok(CoseKey::from_ecdsa_public_key(public_key))
);
}
@@ -246,10 +238,10 @@ mod test {
let mut env = TestEnv::default();
let message = [0x5A; 32];
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();
assert_eq!(
private_key.sign_and_encode(&mut env, &message),
private_key.sign_and_encode::<TestEnv>(&message),
Ok(signature)
);
}
@@ -273,9 +265,15 @@ mod test {
fn test_private_key_from_to_cbor(signature_algorithm: SignatureAlgorithm) {
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 cbor = cbor::Value::from(&private_key);
assert_eq!(PrivateKey::try_from(cbor), Ok(private_key),);
let cbor = private_key
.to_cbor::<TestEnv>(env.rng(), &wrap_key)
.unwrap();
assert_eq!(
PrivateKey::from_cbor::<TestEnv>(&wrap_key, cbor),
Ok(private_key)
);
}
#[test]
@@ -290,6 +288,8 @@ mod test {
}
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![
cbor_int!(signature_algorithm as i64),
cbor_bytes!(vec![0x88; 32]),
@@ -297,7 +297,7 @@ mod test {
cbor_int!(0),
];
assert_eq!(
PrivateKey::try_from(cbor),
PrivateKey::from_cbor::<TestEnv>(&wrap_key, cbor),
Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR),
);
}
@@ -315,13 +315,15 @@ mod test {
#[test]
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![
// This algorithms doesn't exist.
cbor_int!(-1),
cbor_bytes!(vec![0x88; 32]),
];
assert_eq!(
PrivateKey::try_from(cbor),
PrivateKey::from_cbor::<TestEnv>(&wrap_key, cbor),
Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR),
);
}

View File

@@ -61,8 +61,7 @@ fn enumerate_rps_response<E: Env>(
}
/// Generates the response for subcommands enumerating credentials.
fn enumerate_credentials_response(
env: &mut impl Env,
fn enumerate_credentials_response<E: Env>(
credential: PublicKeyCredentialSource,
total_credentials: Option<u64>,
) -> Result<AuthenticatorCredentialManagementResponse, Ctap2StatusCode> {
@@ -91,7 +90,7 @@ fn enumerate_credentials_response(
key_id: credential_id,
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 {
user: Some(user),
credential_id: Some(credential_id),
@@ -202,7 +201,7 @@ fn process_enumerate_credentials_begin<E: Env>(
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.
@@ -212,7 +211,7 @@ fn process_enumerate_credentials_get_next_credential<E: Env>(
) -> Result<AuthenticatorCredentialManagementResponse, Ctap2StatusCode> {
let credential_key = stateful_command_permission.next_enumerate_credential(env)?;
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.

View File

@@ -21,7 +21,7 @@ use rand_core::RngCore;
/// Wraps the AES256-CBC encryption to match what we need in CTAP.
pub fn aes256_cbc_encrypt<E: Env>(
env: &mut E,
rng: &mut E::Rng,
aes_key: &AesKey<E>,
plaintext: &[u8],
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 iv = if embeds_iv {
ciphertext.resize(16, 0);
env.rng().fill_bytes(&mut ciphertext[..16]);
rng.fill_bytes(&mut ciphertext[..16]);
*array_ref!(ciphertext, 0, 16)
} else {
[0u8; 16]
@@ -74,7 +74,8 @@ mod test {
let mut env = TestEnv::default();
let aes_key = AesKey::<TestEnv>::new(&[0xC2; 32]);
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();
assert_eq!(*decrypted, plaintext);
}
@@ -84,7 +85,8 @@ mod test {
let mut env = TestEnv::default();
let aes_key = AesKey::<TestEnv>::new(&[0xC2; 32]);
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();
assert_eq!(*decrypted, plaintext);
}
@@ -95,7 +97,7 @@ mod test {
let aes_key = AesKey::<TestEnv>::new(&[0xC2; 32]);
let plaintext = vec![0xAA; 64];
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];
ciphertext_with_iv.append(&mut ciphertext_no_iv);
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 aes_key = AesKey::<TestEnv>::new(&[0xC2; 32]);
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;
for i in 0..16 {
ciphertext[i] ^= 0xBB;
@@ -122,8 +125,10 @@ mod test {
let mut env = TestEnv::default();
let aes_key = AesKey::<TestEnv>::new(&[0xC2; 32]);
let plaintext = vec![0xAA; 64];
let ciphertext1 = aes256_cbc_encrypt(&mut env, &aes_key, &plaintext, true).unwrap();
let ciphertext2 = aes256_cbc_encrypt(&mut env, &aes_key, &plaintext, true).unwrap();
let ciphertext1 =
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!(ciphertext2.len(), 80);
// The ciphertext should mutate in all blocks with a different IV.

View File

@@ -256,7 +256,7 @@ impl Ctap1Command {
) -> Result<Vec<u8>, Ctap1StatusCode> {
let private_key = PrivateKey::new_ecdsa(env);
let sk = private_key
.ecdsa_key(env)
.ecdsa_key::<E>()
.map_err(|_| Ctap1StatusCode::SW_INTERNAL_EXCEPTION)?;
let pk = sk.public_key();
let credential_source = CredentialSource {
@@ -333,10 +333,6 @@ impl Ctap1Command {
.map_err(|_| Ctap1StatusCode::SW_WRONG_DATA)?;
let credential_source = filter_listed_credential(credential_source, false)
.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 {
return Err(Ctap1StatusCode::SW_COND_USE_NOT_SATISFIED);
}
@@ -351,10 +347,13 @@ impl Ctap1Command {
)
.map_err(|_| Ctap1StatusCode::SW_WRONG_DATA)?;
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();
response.extend(signature.to_der());
response.extend(&signature);
Ok(response)
}
}

View File

@@ -15,6 +15,7 @@
use super::status_code::Ctap2StatusCode;
use crate::api::crypto::{ecdh, ecdsa, EC_FIELD_SIZE};
use crate::api::private_key::PrivateKey;
use crate::env::{AesKey, Env};
use alloc::string::String;
use alloc::vec::Vec;
#[cfg(feature = "fuzz")]
@@ -601,8 +602,6 @@ pub struct PublicKeyCredentialSource {
// is associated with a unique tag, implemented with a CBOR unsigned key.
enum PublicKeyCredentialSourceField {
CredentialId = 0,
// Deprecated, we still read this field for backwards compatibility.
EcdsaPrivateKey = 1,
RpId = 2,
UserHandle = 3,
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
// those reserved tags below.
// Reserved tags:
// - EcdsaPrivateKey = 1,
// - CredRandom = 5,
}
@@ -625,32 +625,41 @@ impl From<PublicKeyCredentialSourceField> for cbor::Value {
}
}
impl From<PublicKeyCredentialSource> for cbor::Value {
fn from(credential: PublicKeyCredentialSource) -> cbor::Value {
cbor_map_options! {
PublicKeyCredentialSourceField::CredentialId => Some(credential.credential_id),
PublicKeyCredentialSourceField::RpId => Some(credential.rp_id),
PublicKeyCredentialSourceField::UserHandle => Some(credential.user_handle),
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,
}
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)
}
}
impl TryFrom<cbor::Value> for PublicKeyCredentialSource {
type Error = Ctap2StatusCode;
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)?,
})
}
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
pub fn from_cbor<E: Env>(
wrap_key: &AesKey<E>,
cbor_value: cbor::Value,
) -> Result<Self, Ctap2StatusCode> {
destructure_cbor_map! {
let {
PublicKeyCredentialSourceField::CredentialId => credential_id,
PublicKeyCredentialSourceField::EcdsaPrivateKey => ecdsa_private_key,
PublicKeyCredentialSourceField::RpId => rp_id,
PublicKeyCredentialSourceField::UserHandle => user_handle,
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 cred_blob = cred_blob.map(extract_byte_string).transpose()?;
let large_blob_key = large_blob_key.map(extract_byte_string).transpose()?;
// 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,
};
let private_key = PrivateKey::from_cbor::<E>(wrap_key, ok_or_missing(private_key)?)?;
// 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
@@ -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.
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "fuzz", derive(Arbitrary))]
@@ -1232,6 +1222,7 @@ mod test {
use super::*;
use crate::api::crypto::ecdh::PublicKey as _;
use crate::api::crypto::ecdsa::PublicKey as _;
use crate::api::key_store::KeyStore;
use crate::api::rng::Rng;
use crate::env::test::TestEnv;
use crate::env::{EcdhPk, EcdsaPk, Env};
@@ -2105,6 +2096,7 @@ mod test {
#[test]
fn test_credential_source_cbor_round_trip() {
let mut env = TestEnv::default();
let wrap_key = env.key_store().wrap_key::<TestEnv>().unwrap();
let private_key = PrivateKey::new_ecdsa(&mut env);
let credential = PublicKeyCredentialSource {
key_type: PublicKeyCredentialType::PublicKey,
@@ -2121,8 +2113,12 @@ mod test {
large_blob_key: None,
};
let cbor_value = credential
.clone()
.to_cbor::<TestEnv>(env.rng(), &wrap_key)
.unwrap();
assert_eq!(
PublicKeyCredentialSource::try_from(cbor::Value::from(credential.clone())),
PublicKeyCredentialSource::from_cbor::<TestEnv>(&wrap_key, cbor_value),
Ok(credential.clone())
);
@@ -2131,8 +2127,12 @@ mod test {
..credential
};
let cbor_value = credential
.clone()
.to_cbor::<TestEnv>(env.rng(), &wrap_key)
.unwrap();
assert_eq!(
PublicKeyCredentialSource::try_from(cbor::Value::from(credential.clone())),
PublicKeyCredentialSource::from_cbor::<TestEnv>(&wrap_key, cbor_value),
Ok(credential.clone())
);
@@ -2141,8 +2141,12 @@ mod test {
..credential
};
let cbor_value = credential
.clone()
.to_cbor::<TestEnv>(env.rng(), &wrap_key)
.unwrap();
assert_eq!(
PublicKeyCredentialSource::try_from(cbor::Value::from(credential.clone())),
PublicKeyCredentialSource::from_cbor::<TestEnv>(&wrap_key, cbor_value),
Ok(credential.clone())
);
@@ -2151,8 +2155,12 @@ mod test {
..credential
};
let cbor_value = credential
.clone()
.to_cbor::<TestEnv>(env.rng(), &wrap_key)
.unwrap();
assert_eq!(
PublicKeyCredentialSource::try_from(cbor::Value::from(credential.clone())),
PublicKeyCredentialSource::from_cbor::<TestEnv>(&wrap_key, cbor_value),
Ok(credential.clone())
);
@@ -2161,8 +2169,12 @@ mod test {
..credential
};
let cbor_value = credential
.clone()
.to_cbor::<TestEnv>(env.rng(), &wrap_key)
.unwrap();
assert_eq!(
PublicKeyCredentialSource::try_from(cbor::Value::from(credential.clone())),
PublicKeyCredentialSource::from_cbor::<TestEnv>(&wrap_key, cbor_value),
Ok(credential.clone())
);
@@ -2171,8 +2183,12 @@ mod test {
..credential
};
let cbor_value = credential
.clone()
.to_cbor::<TestEnv>(env.rng(), &wrap_key)
.unwrap();
assert_eq!(
PublicKeyCredentialSource::try_from(cbor::Value::from(credential.clone())),
PublicKeyCredentialSource::from_cbor::<TestEnv>(&wrap_key, cbor_value),
Ok(credential.clone())
);
@@ -2181,91 +2197,28 @@ mod test {
..credential
};
let cbor_value = credential
.clone()
.to_cbor::<TestEnv>(env.rng(), &wrap_key)
.unwrap();
assert_eq!(
PublicKeyCredentialSource::try_from(cbor::Value::from(credential.clone())),
PublicKeyCredentialSource::from_cbor::<TestEnv>(&wrap_key, cbor_value),
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]
fn test_credential_source_invalid_cbor() {
assert!(PublicKeyCredentialSource::try_from(cbor_false!()).is_err());
assert!(PublicKeyCredentialSource::try_from(cbor_array!(false)).is_err());
assert!(PublicKeyCredentialSource::try_from(cbor_array!(b"foo".to_vec())).is_err());
let mut env = TestEnv::default();
let wrap_key = env.key_store().wrap_key::<TestEnv>().unwrap();
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());
}
}

View File

@@ -917,7 +917,7 @@ impl<E: Env> CtapState<E> {
}
auth_data.extend(vec![0x00, credential_id.len() as u8]);
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)?;
if has_extension_output {
let hmac_secret_output = if extensions.hmac_secret {
@@ -965,7 +965,7 @@ impl<E: Env> CtapState<E> {
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 {
alg: SignatureAlgorithm::Es256 as i64,
@@ -1051,7 +1051,7 @@ impl<E: Env> CtapState<E> {
signature_data.extend(client_data_hash);
let signature = credential
.private_key
.sign_and_encode(env, &signature_data)?;
.sign_and_encode::<E>(&signature_data)?;
let cred_desc = PublicKeyCredentialDescriptor {
key_type: PublicKeyCredentialType::PublicKey,

View File

@@ -225,7 +225,7 @@ impl<E: Env> SharedSecretV1<E> {
}
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> {
@@ -263,7 +263,7 @@ impl<E: Env> SharedSecretV2<E> {
}
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> {

View File

@@ -24,13 +24,12 @@ use crate::ctap::data_formats::{
};
use crate::ctap::status_code::Ctap2StatusCode;
use crate::ctap::INITIAL_SIGNATURE_COUNTER;
use crate::env::Env;
use crate::env::{AesKey, Env};
use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
use arrayref::array_ref;
use core::cmp;
use core::convert::TryInto;
use persistent_store::{fragment, StoreUpdate};
#[cfg(feature = "config_command")]
use sk_cbor::cbor_array_vec;
@@ -56,8 +55,8 @@ pub fn init(env: &mut impl Env) -> Result<(), Ctap2StatusCode> {
/// # Errors
///
/// Returns `CTAP2_ERR_VENDOR_INTERNAL_ERROR` if the key does not hold a valid credential.
pub fn get_credential(
env: &mut impl Env,
pub fn get_credential<E: Env>(
env: &mut E,
key: usize,
) -> Result<PublicKeyCredentialSource, Ctap2StatusCode> {
let min_key = key::CREDENTIALS.start;
@@ -68,7 +67,8 @@ pub fn get_credential(
.store()
.find(key)?
.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)
}
@@ -118,8 +118,8 @@ pub fn find_credential(
/// Stores or updates a credential.
///
/// If a credential with the same RP id and user handle already exists, it is replaced.
pub fn store_credential(
env: &mut impl Env,
pub fn store_credential<E: Env>(
env: &mut E,
new_credential: PublicKeyCredentialSource,
) -> Result<(), Ctap2StatusCode> {
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.
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)?;
Ok(())
}
@@ -178,8 +179,8 @@ pub fn delete_credential(env: &mut impl Env, credential_id: &[u8]) -> Result<(),
/// # Errors
///
/// Returns `CTAP2_ERR_NO_CREDENTIALS` if the credential is not found.
pub fn update_credential(
env: &mut impl Env,
pub fn update_credential<E: Env>(
env: &mut E,
credential_id: &[u8],
user: PublicKeyCredentialUserEntity,
) -> Result<(), Ctap2StatusCode> {
@@ -187,7 +188,8 @@ pub fn update_credential(
credential.user_name = user.user_name;
credential.user_display_name = user.user_display_name;
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)?)
}
@@ -215,7 +217,7 @@ pub fn iter_credentials<'a, E: Env>(
env: &'a mut E,
result: &'a mut Result<(), Ctap2StatusCode>,
) -> Result<IterCredentials<'a, E>, Ctap2StatusCode> {
IterCredentials::new(env.store(), result)
IterCredentials::new(env, result)
}
/// Returns the next creation order.
@@ -525,6 +527,9 @@ pub struct IterCredentials<'a, E: Env> {
/// The store being iterated.
store: &'a persistent_store::Store<E::Storage>,
/// The key store for credential unwrapping.
wrap_key: AesKey<E>,
/// The store iterator.
iter: persistent_store::StoreIter<'a>,
@@ -538,12 +543,15 @@ pub struct IterCredentials<'a, E: Env> {
impl<'a, E: Env> IterCredentials<'a, E> {
/// Creates a credential iterator.
fn new(
store: &'a persistent_store::Store<E::Storage>,
env: &'a mut E,
result: &'a mut Result<(), Ctap2StatusCode>,
) -> Result<Self, Ctap2StatusCode> {
let wrap_key = env.key_store().wrap_key::<E>()?;
let store = env.store();
let iter = store.iter()?;
Ok(IterCredentials {
store,
wrap_key,
iter,
result,
})
@@ -576,7 +584,8 @@ impl<'a, E: Env> Iterator for IterCredentials<'a, E> {
continue;
}
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));
}
None
@@ -584,15 +593,22 @@ impl<'a, E: Env> Iterator for IterCredentials<'a, E> {
}
/// 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()?;
cbor.try_into().ok()
PublicKeyCredentialSource::from_cbor::<E>(wrap_key, cbor).ok()
}
/// 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();
super::cbor_write(credential.into(), &mut data)?;
super::cbor_write(credential.to_cbor::<E>(env.rng(), wrap_key)?, &mut data)?;
Ok(data)
}
@@ -1098,6 +1114,7 @@ mod test {
#[test]
fn test_serialize_deserialize_credential() {
let mut env = TestEnv::default();
let wrap_key = env.key_store().wrap_key::<TestEnv>().unwrap();
let private_key = PrivateKey::new_ecdsa(&mut env);
let credential = PublicKeyCredentialSource {
key_type: PublicKeyCredentialType::PublicKey,
@@ -1113,8 +1130,9 @@ mod test {
cred_blob: Some(vec![0xCB]),
large_blob_key: Some(vec![0x1B]),
};
let serialized = serialize_credential(credential.clone()).unwrap();
let reconstructed = deserialize_credential(&serialized).unwrap();
let serialized =
serialize_credential::<TestEnv>(&mut env, &wrap_key, credential.clone()).unwrap();
let reconstructed = deserialize_credential::<TestEnv>(&wrap_key, &serialized).unwrap();
assert_eq!(credential, reconstructed);
}