Credential wrapping in Env (#624)
* Moves credential wrapping to Env * visibility of constants * moves PrivateKey to api * fixes docs and imports
This commit is contained in:
@@ -12,12 +12,60 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
use crate::api::crypto::aes256::Aes256;
|
||||||
use crate::api::crypto::ecdsa::SecretKey as _;
|
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;
|
||||||
|
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::secret::Secret;
|
||||||
use crate::env::{EcdsaSk, Env};
|
use crate::ctap::{cbor_read, cbor_write};
|
||||||
|
use crate::env::{AesKey, EcdsaSk, Env, Hmac};
|
||||||
use alloc::vec;
|
use alloc::vec;
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
use core::convert::{TryFrom, TryInto};
|
||||||
use persistent_store::StoreError;
|
use persistent_store::StoreError;
|
||||||
use rand_core::RngCore;
|
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;
|
||||||
|
pub(crate) const MAX_CREDENTIAL_ID_SIZE: usize = CBOR_CREDENTIAL_ID_SIZE;
|
||||||
|
|
||||||
|
pub const CBOR_CREDENTIAL_ID_VERSION: u8 = 0x01;
|
||||||
|
const MAX_PADDING_LENGTH: u8 = 0xBF;
|
||||||
|
|
||||||
|
/// Stored data for credentials.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||||
|
pub struct CredentialSource {
|
||||||
|
pub private_key: PrivateKey,
|
||||||
|
pub rp_id_hash: [u8; 32],
|
||||||
|
pub cred_protect_policy: Option<CredentialProtectionPolicy>,
|
||||||
|
pub cred_blob: Option<Vec<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CBOR map keys for serialized credential IDs.
|
||||||
|
enum CredentialSourceField {
|
||||||
|
PrivateKey = 0,
|
||||||
|
RpIdHash = 1,
|
||||||
|
CredProtectPolicy = 2,
|
||||||
|
CredBlob = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<CredentialSourceField> for cbor::Value {
|
||||||
|
fn from(field: CredentialSourceField) -> cbor::Value {
|
||||||
|
(field as u64).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Provides storage for secret keys.
|
/// Provides storage for secret keys.
|
||||||
///
|
///
|
||||||
@@ -28,11 +76,29 @@ 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>;
|
||||||
|
|
||||||
/// Returns the AES key for key handles encryption.
|
/// Encodes a credential as a binary strings.
|
||||||
fn key_handle_encryption(&mut self) -> Result<Secret<[u8; 32]>, Error>;
|
///
|
||||||
|
/// The output is encrypted and authenticated. Since the wrapped credentials are passed to the
|
||||||
|
/// relying party, the choice for credential wrapping impacts privacy. Looking at their size and
|
||||||
|
/// structure, a relying party can guess the authenticator model that produced it.
|
||||||
|
///
|
||||||
|
/// A credential ID that imitates the default needs the following structure:
|
||||||
|
/// - The length is [`CBOR_CREDENTIAL_ID_SIZE`].
|
||||||
|
/// - The first byte is the version. The latest version is [`CBOR_CREDENTIAL_ID_VERSION`].
|
||||||
|
/// - All other bytes appear to be drawn from a uniform random distribution.
|
||||||
|
///
|
||||||
|
/// Without attestation, a relying party can't distinguish such credentials from other OpenSK
|
||||||
|
/// implementations.
|
||||||
|
fn wrap_credential(&mut self, credential: CredentialSource) -> Result<Vec<u8>, Error>;
|
||||||
|
|
||||||
/// Returns the key for key handles authentication.
|
/// Decodes the credential.
|
||||||
fn key_handle_authentication(&mut self) -> Result<Secret<[u8; 32]>, Error>;
|
///
|
||||||
|
/// Returns None if the data was not created by this authenticator.
|
||||||
|
fn unwrap_credential(
|
||||||
|
&mut self,
|
||||||
|
bytes: &[u8],
|
||||||
|
rp_id_hash: &[u8],
|
||||||
|
) -> Result<Option<CredentialSource>, Error>;
|
||||||
|
|
||||||
/// Returns the key for the CredRandom feature.
|
/// Returns the key for the CredRandom feature.
|
||||||
fn cred_random(&mut self, has_uv: bool) -> Result<Secret<[u8; 32]>, Error>;
|
fn cred_random(&mut self, has_uv: bool) -> Result<Secret<[u8; 32]>, Error>;
|
||||||
@@ -72,12 +138,98 @@ impl<T: Helper> KeyStore for T {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn key_handle_encryption(&mut self) -> Result<Secret<[u8; 32]>, Error> {
|
/// Encrypts the given credential source data into a credential ID.
|
||||||
Ok(get_master_keys(self)?.encryption.clone())
|
///
|
||||||
|
/// 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 cbor = cbor_map_options! {
|
||||||
|
CredentialSourceField::PrivateKey => credential.private_key,
|
||||||
|
CredentialSourceField::RpIdHash => credential.rp_id_hash,
|
||||||
|
CredentialSourceField::CredProtectPolicy => credential.cred_protect_policy,
|
||||||
|
CredentialSourceField::CredBlob => credential.cred_blob,
|
||||||
|
};
|
||||||
|
cbor_write(cbor, &mut payload).map_err(|_| Error)?;
|
||||||
|
add_padding(&mut payload)?;
|
||||||
|
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)?;
|
||||||
|
let mut credential_id = encrypted_payload;
|
||||||
|
credential_id.insert(0, CBOR_CREDENTIAL_ID_VERSION);
|
||||||
|
|
||||||
|
let mut id_hmac = [0; HASH_SIZE];
|
||||||
|
Hmac::<T>::mac(
|
||||||
|
&master_keys.authentication,
|
||||||
|
&credential_id[..],
|
||||||
|
&mut id_hmac,
|
||||||
|
);
|
||||||
|
credential_id.extend(&id_hmac);
|
||||||
|
Ok(credential_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn key_handle_authentication(&mut self) -> Result<Secret<[u8; 32]>, Error> {
|
/// Decrypts the given credential ID, populating only the recorded fields.
|
||||||
Ok(get_master_keys(self)?.authentication.clone())
|
///
|
||||||
|
/// Returns None if
|
||||||
|
/// - 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,
|
||||||
|
/// - 192 bytes: encrypted CBOR-encoded credential source fields,
|
||||||
|
/// - 32 bytes: HMAC-SHA256 over everything else.
|
||||||
|
fn unwrap_credential(
|
||||||
|
&mut self,
|
||||||
|
bytes: &[u8],
|
||||||
|
rp_id_hash: &[u8],
|
||||||
|
) -> Result<Option<CredentialSource>, Error> {
|
||||||
|
if bytes.len() < MIN_CREDENTIAL_ID_SIZE {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
let hmac_message_size = bytes.len() - 32;
|
||||||
|
let master_keys = get_master_keys(self)?;
|
||||||
|
if !Hmac::<T>::verify(
|
||||||
|
&master_keys.authentication,
|
||||||
|
&bytes[..hmac_message_size],
|
||||||
|
array_ref![bytes, hmac_message_size, 32],
|
||||||
|
) {
|
||||||
|
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],
|
||||||
|
)?
|
||||||
|
}
|
||||||
|
_ => return Ok(None),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(credential_source) = &credential_source {
|
||||||
|
if rp_id_hash != credential_source.rp_id_hash {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(credential_source)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cred_random(&mut self, has_uv: bool) -> Result<Secret<[u8; 32]>, Error> {
|
fn cred_random(&mut self, has_uv: bool) -> Result<Secret<[u8; 32]>, Error> {
|
||||||
@@ -152,32 +304,139 @@ fn get_master_keys(env: &mut impl Env) -> Result<MasterKeys, Error> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Pad data to MAX_PADDING_LENGTH+1 (192) bytes using PKCS padding scheme.
|
||||||
|
///
|
||||||
|
/// Let N = 192 - data.len(), the PKCS padding scheme would pad N bytes of N after the data.
|
||||||
|
fn add_padding(data: &mut Vec<u8>) -> Result<(), Error> {
|
||||||
|
// The data should be between 1 to MAX_PADDING_LENGTH bytes for the padding scheme to be valid.
|
||||||
|
if data.is_empty() || data.len() > MAX_PADDING_LENGTH as usize {
|
||||||
|
return Err(Error);
|
||||||
|
}
|
||||||
|
let pad_length = MAX_PADDING_LENGTH - (data.len() as u8 - 1);
|
||||||
|
data.extend(core::iter::repeat(pad_length).take(pad_length as usize));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_padding(data: &[u8]) -> Result<&[u8], Error> {
|
||||||
|
if data.len() != MAX_PADDING_LENGTH as usize + 1 {
|
||||||
|
// This is an internal error instead of corrupted credential ID which we should just ignore because
|
||||||
|
// we've already checked that the HMAC matched.
|
||||||
|
return Err(Error);
|
||||||
|
}
|
||||||
|
let pad_length = *data.last().unwrap();
|
||||||
|
if pad_length == 0 || pad_length > MAX_PADDING_LENGTH {
|
||||||
|
return Err(Error);
|
||||||
|
}
|
||||||
|
if !data[(data.len() - pad_length as usize)..]
|
||||||
|
.iter()
|
||||||
|
.all(|x| *x == pad_length)
|
||||||
|
{
|
||||||
|
return Err(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>(
|
||||||
|
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)?;
|
||||||
|
let unpadded = remove_padding(&plaintext)?;
|
||||||
|
|
||||||
|
let cbor_credential_source = cbor_read(unpadded).map_err(|_| Error)?;
|
||||||
|
destructure_cbor_map! {
|
||||||
|
let {
|
||||||
|
CredentialSourceField::PrivateKey => private_key,
|
||||||
|
CredentialSourceField::RpIdHash => rp_id_hash,
|
||||||
|
CredentialSourceField::CredProtectPolicy => cred_protect_policy,
|
||||||
|
CredentialSourceField::CredBlob => cred_blob,
|
||||||
|
} = extract_map(cbor_credential_source)?;
|
||||||
|
}
|
||||||
|
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 rp_id_hash = extract_byte_string(rp_id_hash)?;
|
||||||
|
if rp_id_hash.len() != 32 {
|
||||||
|
return Err(Error);
|
||||||
|
}
|
||||||
|
let cred_protect_policy = cred_protect_policy
|
||||||
|
.map(CredentialProtectionPolicy::try_from)
|
||||||
|
.transpose()
|
||||||
|
.map_err(|_| Error)?;
|
||||||
|
let cred_blob = cred_blob.map(extract_byte_string).transpose()?;
|
||||||
|
Some(CredentialSource {
|
||||||
|
private_key,
|
||||||
|
rp_id_hash: rp_id_hash.try_into().unwrap(),
|
||||||
|
cred_protect_policy,
|
||||||
|
cred_blob,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
impl From<StoreError> for Error {
|
impl From<StoreError> for Error {
|
||||||
fn from(_: StoreError) -> Self {
|
fn from(_: StoreError) -> Self {
|
||||||
Error
|
Error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extract_byte_string(cbor_value: cbor::Value) -> Result<Vec<u8>, Error> {
|
||||||
|
match cbor_value {
|
||||||
|
cbor::Value::ByteString(byte_string) => Ok(byte_string),
|
||||||
|
_ => Err(Error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_map(cbor_value: cbor::Value) -> Result<Vec<(cbor::Value, cbor::Value)>, Error> {
|
||||||
|
match cbor_value {
|
||||||
|
cbor::Value::Map(map) => Ok(map),
|
||||||
|
_ => Err(Error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::api::customization::Customization;
|
||||||
|
use crate::ctap::data_formats::SignatureAlgorithm;
|
||||||
use crate::env::test::TestEnv;
|
use crate::env::test::TestEnv;
|
||||||
|
|
||||||
|
const UNSUPPORTED_CREDENTIAL_ID_VERSION: u8 = 0x80;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_key_store() {
|
fn test_key_store() {
|
||||||
let mut env = TestEnv::default();
|
let mut env = TestEnv::default();
|
||||||
let key_store = env.key_store();
|
let key_store = env.key_store();
|
||||||
|
|
||||||
// Master keys are well-defined and stable.
|
// Master keys are well-defined and stable.
|
||||||
let encryption_key = key_store.key_handle_encryption().unwrap();
|
|
||||||
let authentication_key = key_store.key_handle_authentication().unwrap();
|
|
||||||
let cred_random_no_uv = key_store.cred_random(false).unwrap();
|
let cred_random_no_uv = key_store.cred_random(false).unwrap();
|
||||||
let cred_random_with_uv = key_store.cred_random(true).unwrap();
|
let cred_random_with_uv = key_store.cred_random(true).unwrap();
|
||||||
assert_eq!(&key_store.key_handle_encryption().unwrap(), &encryption_key);
|
|
||||||
assert_eq!(
|
|
||||||
&key_store.key_handle_authentication().unwrap(),
|
|
||||||
&authentication_key
|
|
||||||
);
|
|
||||||
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);
|
||||||
|
|
||||||
@@ -189,11 +448,6 @@ mod test {
|
|||||||
// 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.key_handle_encryption().unwrap(), encryption_key);
|
|
||||||
assert_ne!(
|
|
||||||
key_store.key_handle_authentication().unwrap(),
|
|
||||||
authentication_key
|
|
||||||
);
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
@@ -209,4 +463,204 @@ mod test {
|
|||||||
let decrypted = key_store.decrypt_pin_hash(&encrypted).unwrap();
|
let decrypted = key_store.decrypt_pin_hash(&encrypted).unwrap();
|
||||||
assert_eq!(pin_hash, *decrypted);
|
assert_eq!(pin_hash, *decrypted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn test_wrap_unwrap_credential(signature_algorithm: SignatureAlgorithm) {
|
||||||
|
let mut env = TestEnv::default();
|
||||||
|
let private_key = PrivateKey::new(&mut env, signature_algorithm);
|
||||||
|
let credential_source = CredentialSource {
|
||||||
|
private_key,
|
||||||
|
rp_id_hash: [0x55; 32],
|
||||||
|
cred_protect_policy: Some(CredentialProtectionPolicy::UserVerificationOptional),
|
||||||
|
cred_blob: Some(vec![0xAA; 32]),
|
||||||
|
};
|
||||||
|
let credential_id = env
|
||||||
|
.key_store()
|
||||||
|
.wrap_credential(credential_source.clone())
|
||||||
|
.unwrap();
|
||||||
|
let unwrapped = env
|
||||||
|
.key_store()
|
||||||
|
.unwrap_credential(&credential_id, &[0x55; 32])
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(credential_source, unwrapped);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_wrap_unwrap_credential_ecdsa() {
|
||||||
|
test_wrap_unwrap_credential(SignatureAlgorithm::Es256);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "ed25519")]
|
||||||
|
fn test_wrap_unwrap_credential_ed25519() {
|
||||||
|
test_wrap_unwrap_credential(SignatureAlgorithm::Eddsa);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_wrap_unwrap_credential_bad_version(signature_algorithm: SignatureAlgorithm) {
|
||||||
|
let mut env = TestEnv::default();
|
||||||
|
let private_key = PrivateKey::new(&mut env, signature_algorithm);
|
||||||
|
let credential_source = CredentialSource {
|
||||||
|
private_key,
|
||||||
|
rp_id_hash: [0x55; 32],
|
||||||
|
cred_protect_policy: Some(CredentialProtectionPolicy::UserVerificationOptional),
|
||||||
|
cred_blob: Some(vec![0xAA; 32]),
|
||||||
|
};
|
||||||
|
let mut credential_id = env.key_store().wrap_credential(credential_source).unwrap();
|
||||||
|
credential_id[0] = UNSUPPORTED_CREDENTIAL_ID_VERSION;
|
||||||
|
// Override the HMAC to pass the check.
|
||||||
|
credential_id.truncate(&credential_id.len() - 32);
|
||||||
|
let hmac_key = get_master_keys(&mut env).unwrap().authentication;
|
||||||
|
let mut id_hmac = [0; HASH_SIZE];
|
||||||
|
Hmac::<TestEnv>::mac(&hmac_key, &credential_id[..], &mut id_hmac);
|
||||||
|
credential_id.extend(&id_hmac);
|
||||||
|
let unwrapped = env
|
||||||
|
.key_store()
|
||||||
|
.unwrap_credential(&credential_id, &[0x55; 32]);
|
||||||
|
assert_eq!(unwrapped, Ok(None));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_wrap_unwrap_credential_bad_version_ecdsa() {
|
||||||
|
test_wrap_unwrap_credential_bad_version(SignatureAlgorithm::Es256);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "ed25519")]
|
||||||
|
fn test_wrap_unwrap_credential_bad_version_ed25519() {
|
||||||
|
test_wrap_unwrap_credential_bad_version(SignatureAlgorithm::Eddsa);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_wrap_unwrap_credential_bad_hmac(signature_algorithm: SignatureAlgorithm) {
|
||||||
|
let mut env = TestEnv::default();
|
||||||
|
let private_key = PrivateKey::new(&mut env, signature_algorithm);
|
||||||
|
let credential_source = CredentialSource {
|
||||||
|
private_key,
|
||||||
|
rp_id_hash: [0x55; 32],
|
||||||
|
cred_protect_policy: Some(CredentialProtectionPolicy::UserVerificationOptional),
|
||||||
|
cred_blob: Some(vec![0xAA; 32]),
|
||||||
|
};
|
||||||
|
let mut credential_id = env.key_store().wrap_credential(credential_source).unwrap();
|
||||||
|
let hmac_byte_index = credential_id.len() - 1;
|
||||||
|
credential_id[hmac_byte_index] ^= 0x01;
|
||||||
|
let unwrapped = env
|
||||||
|
.key_store()
|
||||||
|
.unwrap_credential(&credential_id, &[0x55; 32]);
|
||||||
|
assert_eq!(unwrapped, Ok(None));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_wrap_unwrap_credential_bad_hmac_ecdsa() {
|
||||||
|
test_wrap_unwrap_credential_bad_hmac(SignatureAlgorithm::Es256);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "ed25519")]
|
||||||
|
fn test_wrap_unwrap_credential_bad_hmac_ed25519() {
|
||||||
|
test_wrap_unwrap_credential_bad_hmac(SignatureAlgorithm::Eddsa);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_wrap_unwrap_credential_missing_blocks(signature_algorithm: SignatureAlgorithm) {
|
||||||
|
let mut env = TestEnv::default();
|
||||||
|
let private_key = PrivateKey::new(&mut env, signature_algorithm);
|
||||||
|
let credential_source = CredentialSource {
|
||||||
|
private_key,
|
||||||
|
rp_id_hash: [0x55; 32],
|
||||||
|
cred_protect_policy: Some(CredentialProtectionPolicy::UserVerificationOptional),
|
||||||
|
cred_blob: Some(vec![0xAA; 32]),
|
||||||
|
};
|
||||||
|
let credential_id = env.key_store().wrap_credential(credential_source).unwrap();
|
||||||
|
for length in (1..CBOR_CREDENTIAL_ID_SIZE).step_by(16) {
|
||||||
|
let unwrapped = env
|
||||||
|
.key_store()
|
||||||
|
.unwrap_credential(&credential_id[..length], &[0x55; 32]);
|
||||||
|
assert_eq!(unwrapped, Ok(None));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_wrap_unwrap_credential_missing_blocks_ecdsa() {
|
||||||
|
test_wrap_unwrap_credential_missing_blocks(SignatureAlgorithm::Es256);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "ed25519")]
|
||||||
|
fn test_wrap_unwrap_credential_missing_blocks_ed25519() {
|
||||||
|
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();
|
||||||
|
let private_key = PrivateKey::new(&mut env, SignatureAlgorithm::Es256);
|
||||||
|
let credential_source = CredentialSource {
|
||||||
|
private_key,
|
||||||
|
rp_id_hash: [0x55; 32],
|
||||||
|
cred_protect_policy: Some(CredentialProtectionPolicy::UserVerificationOptional),
|
||||||
|
cred_blob: Some(vec![0xAA; 32]),
|
||||||
|
};
|
||||||
|
let credential_id = env
|
||||||
|
.key_store()
|
||||||
|
.wrap_credential(credential_source.clone())
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(credential_id.len(), CBOR_CREDENTIAL_ID_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_wrap_credential_max_size() {
|
||||||
|
// The CBOR encoding length is variadic and depends on size of fields. Ensure that contents
|
||||||
|
// still fit into the padded size when we use maximum length entries.
|
||||||
|
let mut env = TestEnv::default();
|
||||||
|
let private_key = PrivateKey::new(&mut env, SignatureAlgorithm::Es256);
|
||||||
|
let credential_source = CredentialSource {
|
||||||
|
private_key,
|
||||||
|
rp_id_hash: [0x55; 32],
|
||||||
|
cred_protect_policy: Some(CredentialProtectionPolicy::UserVerificationOptional),
|
||||||
|
cred_blob: Some(vec![0xAA; env.customization().max_cred_blob_length()]),
|
||||||
|
};
|
||||||
|
let credential_id = env.key_store().wrap_credential(credential_source.clone());
|
||||||
|
assert!(credential_id.is_ok());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,5 +24,6 @@ pub mod crypto;
|
|||||||
pub mod customization;
|
pub mod customization;
|
||||||
pub mod firmware_protection;
|
pub mod firmware_protection;
|
||||||
pub mod key_store;
|
pub mod key_store;
|
||||||
|
pub mod private_key;
|
||||||
pub mod rng;
|
pub mod rng;
|
||||||
pub mod user_presence;
|
pub mod user_presence;
|
||||||
|
|||||||
328
libraries/opensk/src/api/private_key.rs
Normal file
328
libraries/opensk/src/api/private_key.rs
Normal file
@@ -0,0 +1,328 @@
|
|||||||
|
// Copyright 2021-2023 Google LLC
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
use crate::api::crypto::ecdsa::{SecretKey as _, Signature};
|
||||||
|
use crate::api::key_store::KeyStore;
|
||||||
|
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 alloc::vec;
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
use core::convert::TryFrom;
|
||||||
|
use core::ops::Deref;
|
||||||
|
#[cfg(feature = "ed25519")]
|
||||||
|
use core::ops::DerefMut;
|
||||||
|
#[cfg(feature = "ed25519")]
|
||||||
|
use rand_core::RngCore;
|
||||||
|
use sk_cbor as cbor;
|
||||||
|
use sk_cbor::{cbor_array, cbor_bytes, cbor_int};
|
||||||
|
|
||||||
|
/// An asymmetric private key that can sign messages.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
// 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.
|
||||||
|
Ecdsa(Secret<[u8; 32]>),
|
||||||
|
#[cfg(feature = "ed25519")]
|
||||||
|
Ed25519(ed25519_compact::SecretKey),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrivateKey {
|
||||||
|
/// Creates a new private key for the given algorithm.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if the algorithm is [`SignatureAlgorithm::Unknown`].
|
||||||
|
pub fn new(env: &mut impl Env, alg: SignatureAlgorithm) -> Self {
|
||||||
|
match alg {
|
||||||
|
SignatureAlgorithm::Es256 => {
|
||||||
|
PrivateKey::Ecdsa(env.key_store().generate_ecdsa_seed().unwrap())
|
||||||
|
}
|
||||||
|
#[cfg(feature = "ed25519")]
|
||||||
|
SignatureAlgorithm::Eddsa => {
|
||||||
|
let mut bytes: Secret<[u8; 32]> = Secret::default();
|
||||||
|
env.rng().fill_bytes(bytes.deref_mut());
|
||||||
|
Self::new_ed25519_from_bytes(&*bytes).unwrap()
|
||||||
|
}
|
||||||
|
SignatureAlgorithm::Unknown => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new ecdsa private key.
|
||||||
|
pub fn new_ecdsa(env: &mut impl Env) -> PrivateKey {
|
||||||
|
Self::new(env, SignatureAlgorithm::Es256)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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;
|
||||||
|
}
|
||||||
|
let mut seed: Secret<[u8; 32]> = Secret::default();
|
||||||
|
seed.copy_from_slice(bytes);
|
||||||
|
Some(PrivateKey::Ecdsa(seed))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ed25519")]
|
||||||
|
pub fn new_ed25519_from_bytes(bytes: &[u8]) -> Option<Self> {
|
||||||
|
if bytes.len() != 32 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let seed = ed25519_compact::Seed::from_slice(bytes).unwrap();
|
||||||
|
Some(Self::Ed25519(ed25519_compact::KeyPair::from_seed(seed).sk))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the ECDSA private key.
|
||||||
|
pub fn ecdsa_key<E: Env>(&self, env: &mut E) -> Result<EcdsaSk<E>, Ctap2StatusCode> {
|
||||||
|
match self {
|
||||||
|
PrivateKey::Ecdsa(seed) => ecdsa_key_from_seed(env, seed),
|
||||||
|
#[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> {
|
||||||
|
Ok(match self {
|
||||||
|
PrivateKey::Ecdsa(ecdsa_seed) => {
|
||||||
|
CoseKey::from_ecdsa_public_key(ecdsa_key_from_seed(env, ecdsa_seed)?.public_key())
|
||||||
|
}
|
||||||
|
#[cfg(feature = "ed25519")]
|
||||||
|
PrivateKey::Ed25519(ed25519_key) => CoseKey::from(ed25519_key.public_key()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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> {
|
||||||
|
Ok(match self {
|
||||||
|
PrivateKey::Ecdsa(ecdsa_seed) => {
|
||||||
|
ecdsa_key_from_seed(env, ecdsa_seed)?.sign(message).to_der()
|
||||||
|
}
|
||||||
|
#[cfg(feature = "ed25519")]
|
||||||
|
PrivateKey::Ed25519(ed25519_key) => ed25519_key.sign(message, None).to_vec(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The associated COSE signature algorithm identifier.
|
||||||
|
pub fn signature_algorithm(&self) -> SignatureAlgorithm {
|
||||||
|
match self {
|
||||||
|
PrivateKey::Ecdsa(_) => SignatureAlgorithm::Es256,
|
||||||
|
#[cfg(feature = "ed25519")]
|
||||||
|
PrivateKey::Ed25519(_) => SignatureAlgorithm::Eddsa,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes the key bytes.
|
||||||
|
pub fn to_bytes(&self) -> Secret<[u8]> {
|
||||||
|
let mut bytes = Secret::new(32);
|
||||||
|
match self {
|
||||||
|
PrivateKey::Ecdsa(ecdsa_seed) => bytes.copy_from_slice(ecdsa_seed.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()),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<cbor::Value> for PrivateKey {
|
||||||
|
type Error = Ctap2StatusCode;
|
||||||
|
|
||||||
|
fn try_from(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())?;
|
||||||
|
match SignatureAlgorithm::try_from(array.pop().unwrap())? {
|
||||||
|
SignatureAlgorithm::Es256 => PrivateKey::new_ecdsa_from_bytes(&key_bytes)
|
||||||
|
.ok_or(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR),
|
||||||
|
#[cfg(feature = "ed25519")]
|
||||||
|
SignatureAlgorithm::Eddsa => PrivateKey::new_ed25519_from_bytes(&key_bytes)
|
||||||
|
.ok_or(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR),
|
||||||
|
_ => Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use crate::env::test::TestEnv;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_new_ecdsa_from_bytes() {
|
||||||
|
let mut env = TestEnv::default();
|
||||||
|
let private_key = PrivateKey::new(&mut env, SignatureAlgorithm::Es256);
|
||||||
|
let key_bytes = private_key.to_bytes();
|
||||||
|
assert_eq!(
|
||||||
|
PrivateKey::new_ecdsa_from_bytes(&key_bytes),
|
||||||
|
Some(private_key)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "ed25519")]
|
||||||
|
fn test_new_ed25519_from_bytes() {
|
||||||
|
let mut env = TestEnv::default();
|
||||||
|
let private_key = PrivateKey::new(&mut env, SignatureAlgorithm::Eddsa);
|
||||||
|
let key_bytes = private_key.to_bytes();
|
||||||
|
assert_eq!(
|
||||||
|
PrivateKey::new_ed25519_from_bytes(&key_bytes),
|
||||||
|
Some(private_key)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_new_ecdsa_from_bytes_wrong_length() {
|
||||||
|
assert_eq!(PrivateKey::new_ecdsa_from_bytes(&[0x55; 16]), None);
|
||||||
|
assert_eq!(PrivateKey::new_ecdsa_from_bytes(&[0x55; 31]), None);
|
||||||
|
assert_eq!(PrivateKey::new_ecdsa_from_bytes(&[0x55; 33]), None);
|
||||||
|
assert_eq!(PrivateKey::new_ecdsa_from_bytes(&[0x55; 64]), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "ed25519")]
|
||||||
|
fn test_new_ed25519_from_bytes_wrong_length() {
|
||||||
|
assert_eq!(PrivateKey::new_ed25519_from_bytes(&[0x55; 16]), None);
|
||||||
|
assert_eq!(PrivateKey::new_ed25519_from_bytes(&[0x55; 31]), None);
|
||||||
|
assert_eq!(PrivateKey::new_ed25519_from_bytes(&[0x55; 33]), None);
|
||||||
|
assert_eq!(PrivateKey::new_ed25519_from_bytes(&[0x55; 64]), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_private_key_get_pub_key() {
|
||||||
|
let mut env = TestEnv::default();
|
||||||
|
let private_key = PrivateKey::new_ecdsa(&mut env);
|
||||||
|
let ecdsa_key = private_key.ecdsa_key(&mut env).unwrap();
|
||||||
|
let public_key = ecdsa_key.public_key();
|
||||||
|
assert_eq!(
|
||||||
|
private_key.get_pub_key(&mut env),
|
||||||
|
Ok(CoseKey::from_ecdsa_public_key(public_key))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_private_key_sign_and_encode() {
|
||||||
|
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 signature = ecdsa_key.sign(&message).to_der();
|
||||||
|
assert_eq!(
|
||||||
|
private_key.sign_and_encode(&mut env, &message),
|
||||||
|
Ok(signature)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_private_key_signature_algorithm(signature_algorithm: SignatureAlgorithm) {
|
||||||
|
let mut env = TestEnv::default();
|
||||||
|
let private_key = PrivateKey::new(&mut env, signature_algorithm);
|
||||||
|
assert_eq!(private_key.signature_algorithm(), signature_algorithm);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ecdsa_private_key_signature_algorithm() {
|
||||||
|
test_private_key_signature_algorithm(SignatureAlgorithm::Es256);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "ed25519")]
|
||||||
|
fn test_ed25519_private_key_signature_algorithm() {
|
||||||
|
test_private_key_signature_algorithm(SignatureAlgorithm::Eddsa);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_private_key_from_to_cbor(signature_algorithm: SignatureAlgorithm) {
|
||||||
|
let mut env = TestEnv::default();
|
||||||
|
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),);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ecdsa_private_key_from_to_cbor() {
|
||||||
|
test_private_key_from_to_cbor(SignatureAlgorithm::Es256);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "ed25519")]
|
||||||
|
fn test_ed25519_private_key_from_to_cbor() {
|
||||||
|
test_private_key_from_to_cbor(SignatureAlgorithm::Eddsa);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_private_key_from_bad_cbor(signature_algorithm: SignatureAlgorithm) {
|
||||||
|
let cbor = cbor_array![
|
||||||
|
cbor_int!(signature_algorithm as i64),
|
||||||
|
cbor_bytes!(vec![0x88; 32]),
|
||||||
|
// The array is too long.
|
||||||
|
cbor_int!(0),
|
||||||
|
];
|
||||||
|
assert_eq!(
|
||||||
|
PrivateKey::try_from(cbor),
|
||||||
|
Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ecdsa_private_key_from_bad_cbor() {
|
||||||
|
test_private_key_from_bad_cbor(SignatureAlgorithm::Es256);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "ed25519")]
|
||||||
|
fn test_ed25519_private_key_from_bad_cbor() {
|
||||||
|
test_private_key_from_bad_cbor(SignatureAlgorithm::Eddsa);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_private_key_from_bad_cbor_unsupported_algo() {
|
||||||
|
let cbor = cbor_array![
|
||||||
|
// This algorithms doesn't exist.
|
||||||
|
cbor_int!(-1),
|
||||||
|
cbor_bytes!(vec![0x88; 32]),
|
||||||
|
];
|
||||||
|
assert_eq!(
|
||||||
|
PrivateKey::try_from(cbor),
|
||||||
|
Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,500 +0,0 @@
|
|||||||
// Copyright 2022-2023 Google LLC
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
use super::crypto_wrapper::{aes256_cbc_decrypt, aes256_cbc_encrypt, PrivateKey};
|
|
||||||
use super::data_formats::{
|
|
||||||
CredentialProtectionPolicy, PublicKeyCredentialSource, PublicKeyCredentialType,
|
|
||||||
};
|
|
||||||
use super::status_code::Ctap2StatusCode;
|
|
||||||
use super::{cbor_read, cbor_write};
|
|
||||||
use crate::api::crypto::aes256::Aes256;
|
|
||||||
use crate::api::crypto::hmac256::Hmac256;
|
|
||||||
use crate::api::crypto::HASH_SIZE;
|
|
||||||
use crate::api::key_store::KeyStore;
|
|
||||||
use crate::ctap::data_formats::{extract_byte_string, extract_map};
|
|
||||||
use crate::env::{AesKey, Env, Hmac};
|
|
||||||
use alloc::string::String;
|
|
||||||
use alloc::vec::Vec;
|
|
||||||
use core::convert::{TryFrom, TryInto};
|
|
||||||
use sk_cbor::{cbor_map_options, destructure_cbor_map};
|
|
||||||
|
|
||||||
pub 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;
|
|
||||||
pub const MIN_CREDENTIAL_ID_SIZE: usize = LEGACY_CREDENTIAL_ID_SIZE;
|
|
||||||
pub const MAX_CREDENTIAL_ID_SIZE: usize = CBOR_CREDENTIAL_ID_SIZE;
|
|
||||||
|
|
||||||
pub const CBOR_CREDENTIAL_ID_VERSION: u8 = 0x01;
|
|
||||||
|
|
||||||
pub const MAX_PADDING_LENGTH: u8 = 0xBF;
|
|
||||||
|
|
||||||
// Data fields that are contained in the credential ID of non-discoverable credentials.
|
|
||||||
struct CredentialSource {
|
|
||||||
private_key: PrivateKey,
|
|
||||||
rp_id_hash: [u8; 32],
|
|
||||||
cred_protect_policy: Option<CredentialProtectionPolicy>,
|
|
||||||
cred_blob: Option<Vec<u8>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
// The data fields contained in the credential ID are serialized using CBOR maps.
|
|
||||||
// Each field is associated with a unique tag, implemented with a CBOR unsigned key.
|
|
||||||
enum CredentialSourceField {
|
|
||||||
PrivateKey = 0,
|
|
||||||
RpIdHash = 1,
|
|
||||||
CredProtectPolicy = 2,
|
|
||||||
CredBlob = 3,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<CredentialSourceField> for sk_cbor::Value {
|
|
||||||
fn from(field: CredentialSourceField) -> sk_cbor::Value {
|
|
||||||
(field as u64).into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn decrypt_legacy_credential_id<E: Env>(
|
|
||||||
env: &mut E,
|
|
||||||
bytes: &[u8],
|
|
||||||
) -> Result<Option<CredentialSource>, Ctap2StatusCode> {
|
|
||||||
let aes_key = AesKey::<E>::new(&*env.key_store().key_handle_encryption()?);
|
|
||||||
let plaintext = aes256_cbc_decrypt::<E>(&aes_key, bytes, true)?.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,
|
|
||||||
bytes: &[u8],
|
|
||||||
) -> Result<Option<CredentialSource>, Ctap2StatusCode> {
|
|
||||||
let aes_key = AesKey::<E>::new(&*env.key_store().key_handle_encryption()?);
|
|
||||||
let plaintext = aes256_cbc_decrypt::<E>(&aes_key, bytes, true)?;
|
|
||||||
let unpadded = remove_padding(&plaintext)?;
|
|
||||||
|
|
||||||
let cbor_credential_source = cbor_read(unpadded)?;
|
|
||||||
destructure_cbor_map! {
|
|
||||||
let {
|
|
||||||
CredentialSourceField::PrivateKey => private_key,
|
|
||||||
CredentialSourceField::RpIdHash=> rp_id_hash,
|
|
||||||
CredentialSourceField::CredProtectPolicy => cred_protect_policy,
|
|
||||||
CredentialSourceField::CredBlob => cred_blob,
|
|
||||||
} = extract_map(cbor_credential_source)?;
|
|
||||||
}
|
|
||||||
Ok(match (private_key, rp_id_hash) {
|
|
||||||
(Some(private_key), Some(rp_id_hash)) => {
|
|
||||||
let private_key = PrivateKey::try_from(private_key)?;
|
|
||||||
let rp_id_hash = extract_byte_string(rp_id_hash)?;
|
|
||||||
if rp_id_hash.len() != 32 {
|
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
|
|
||||||
}
|
|
||||||
let cred_protect_policy = cred_protect_policy
|
|
||||||
.map(CredentialProtectionPolicy::try_from)
|
|
||||||
.transpose()?;
|
|
||||||
let cred_blob = cred_blob.map(extract_byte_string).transpose()?;
|
|
||||||
Some(CredentialSource {
|
|
||||||
private_key,
|
|
||||||
rp_id_hash: rp_id_hash.try_into().unwrap(),
|
|
||||||
cred_protect_policy,
|
|
||||||
cred_blob,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Pad data to MAX_PADDING_LENGTH+1 (192) bytes using PKCS padding scheme.
|
|
||||||
/// Let N = 192 - data.len(), the PKCS padding scheme would pad N bytes of N after the data.
|
|
||||||
fn add_padding(data: &mut Vec<u8>) -> Result<(), Ctap2StatusCode> {
|
|
||||||
// The data should be between 1 to MAX_PADDING_LENGTH bytes for the padding scheme to be valid.
|
|
||||||
if data.is_empty() || data.len() > MAX_PADDING_LENGTH as usize {
|
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
|
|
||||||
}
|
|
||||||
let pad_length = MAX_PADDING_LENGTH - (data.len() as u8 - 1);
|
|
||||||
data.extend(core::iter::repeat(pad_length).take(pad_length as usize));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_padding(data: &[u8]) -> Result<&[u8], Ctap2StatusCode> {
|
|
||||||
if data.len() != MAX_PADDING_LENGTH as usize + 1 {
|
|
||||||
// This is an internal error instead of corrupted credential ID which we should just ignore because
|
|
||||||
// we've already checked that the HMAC matched.
|
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
|
|
||||||
}
|
|
||||||
let pad_length = *data.last().unwrap();
|
|
||||||
if pad_length == 0 || pad_length > MAX_PADDING_LENGTH {
|
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
|
|
||||||
}
|
|
||||||
if !data[(data.len() - pad_length as usize)..]
|
|
||||||
.iter()
|
|
||||||
.all(|x| *x == pad_length)
|
|
||||||
{
|
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
|
|
||||||
}
|
|
||||||
Ok(&data[..data.len() - pad_length as usize])
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Encrypts the given private key, relying party ID hash, and some other metadata 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).
|
|
||||||
pub fn encrypt_to_credential_id<E: Env>(
|
|
||||||
env: &mut E,
|
|
||||||
private_key: &PrivateKey,
|
|
||||||
rp_id_hash: &[u8; 32],
|
|
||||||
cred_protect_policy: Option<CredentialProtectionPolicy>,
|
|
||||||
cred_blob: Option<Vec<u8>>,
|
|
||||||
) -> Result<Vec<u8>, Ctap2StatusCode> {
|
|
||||||
let mut payload = Vec::new();
|
|
||||||
let cbor = cbor_map_options! {
|
|
||||||
CredentialSourceField::PrivateKey => private_key,
|
|
||||||
CredentialSourceField::RpIdHash => rp_id_hash,
|
|
||||||
CredentialSourceField::CredProtectPolicy => cred_protect_policy,
|
|
||||||
CredentialSourceField::CredBlob => cred_blob,
|
|
||||||
};
|
|
||||||
cbor_write(cbor, &mut payload)?;
|
|
||||||
add_padding(&mut payload)?;
|
|
||||||
|
|
||||||
let aes_key = AesKey::<E>::new(&*env.key_store().key_handle_encryption()?);
|
|
||||||
let encrypted_payload = aes256_cbc_encrypt(env, &aes_key, &payload, true)?;
|
|
||||||
let mut credential_id = encrypted_payload;
|
|
||||||
credential_id.insert(0, CBOR_CREDENTIAL_ID_VERSION);
|
|
||||||
|
|
||||||
let mut id_hmac = [0; HASH_SIZE];
|
|
||||||
Hmac::<E>::mac(
|
|
||||||
&*env.key_store().key_handle_authentication()?,
|
|
||||||
&credential_id[..],
|
|
||||||
&mut id_hmac,
|
|
||||||
);
|
|
||||||
credential_id.extend(&id_hmac);
|
|
||||||
Ok(credential_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Decrypts the given credential ID into a PublicKeyCredentialSource, populating only the recorded fields.
|
|
||||||
///
|
|
||||||
/// Returns None if
|
|
||||||
/// - 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,
|
|
||||||
/// - 192 bytes: encrypted CBOR-encoded credential source fields,
|
|
||||||
/// - 32 bytes: HMAC-SHA256 over everything else.
|
|
||||||
pub fn decrypt_credential_id<E: Env>(
|
|
||||||
env: &mut E,
|
|
||||||
credential_id: Vec<u8>,
|
|
||||||
rp_id_hash: &[u8],
|
|
||||||
) -> Result<Option<PublicKeyCredentialSource>, Ctap2StatusCode> {
|
|
||||||
if credential_id.len() < MIN_CREDENTIAL_ID_SIZE {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
let hmac_message_size = credential_id.len() - 32;
|
|
||||||
if !Hmac::<E>::verify(
|
|
||||||
&*env.key_store().key_handle_authentication()?,
|
|
||||||
&credential_id[..hmac_message_size],
|
|
||||||
array_ref![credential_id, hmac_message_size, 32],
|
|
||||||
) {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
let credential_source = if credential_id.len() == LEGACY_CREDENTIAL_ID_SIZE {
|
|
||||||
decrypt_legacy_credential_id(env, &credential_id[..hmac_message_size])?
|
|
||||||
} else {
|
|
||||||
match credential_id[0] {
|
|
||||||
CBOR_CREDENTIAL_ID_VERSION => {
|
|
||||||
if credential_id.len() != CBOR_CREDENTIAL_ID_SIZE {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
decrypt_cbor_credential_id(env, &credential_id[1..hmac_message_size])?
|
|
||||||
}
|
|
||||||
_ => return Ok(None),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let credential_source = if let Some(credential_source) = credential_source {
|
|
||||||
credential_source
|
|
||||||
} else {
|
|
||||||
return Ok(None);
|
|
||||||
};
|
|
||||||
|
|
||||||
if rp_id_hash != credential_source.rp_id_hash {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Some(PublicKeyCredentialSource {
|
|
||||||
key_type: PublicKeyCredentialType::PublicKey,
|
|
||||||
credential_id,
|
|
||||||
private_key: credential_source.private_key,
|
|
||||||
rp_id: String::new(),
|
|
||||||
user_handle: Vec::new(),
|
|
||||||
user_display_name: None,
|
|
||||||
cred_protect_policy: credential_source.cred_protect_policy,
|
|
||||||
creation_order: 0,
|
|
||||||
user_name: None,
|
|
||||||
user_icon: None,
|
|
||||||
cred_blob: credential_source.cred_blob,
|
|
||||||
large_blob_key: None,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
use crate::api::crypto::ecdsa::SecretKey as _;
|
|
||||||
use crate::api::customization::Customization;
|
|
||||||
use crate::ctap::credential_id::CBOR_CREDENTIAL_ID_SIZE;
|
|
||||||
use crate::ctap::SignatureAlgorithm;
|
|
||||||
use crate::env::test::TestEnv;
|
|
||||||
use crate::env::EcdsaSk;
|
|
||||||
|
|
||||||
const UNSUPPORTED_CREDENTIAL_ID_VERSION: u8 = 0x80;
|
|
||||||
|
|
||||||
fn test_encrypt_decrypt_credential(signature_algorithm: SignatureAlgorithm) {
|
|
||||||
let mut env = TestEnv::default();
|
|
||||||
let private_key = PrivateKey::new(&mut env, signature_algorithm);
|
|
||||||
|
|
||||||
let rp_id_hash = [0x55; 32];
|
|
||||||
let encrypted_id =
|
|
||||||
encrypt_to_credential_id(&mut env, &private_key, &rp_id_hash, None, None).unwrap();
|
|
||||||
let decrypted_source = decrypt_credential_id(&mut env, encrypted_id, &rp_id_hash)
|
|
||||||
.unwrap()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(private_key, decrypted_source.private_key);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_encrypt_decrypt_ecdsa_credential() {
|
|
||||||
test_encrypt_decrypt_credential(SignatureAlgorithm::Es256);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[cfg(feature = "ed25519")]
|
|
||||||
fn test_encrypt_decrypt_ed25519_credential() {
|
|
||||||
test_encrypt_decrypt_credential(SignatureAlgorithm::Eddsa);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_encrypt_decrypt_bad_version() {
|
|
||||||
let mut env = TestEnv::default();
|
|
||||||
let private_key = PrivateKey::new(&mut env, SignatureAlgorithm::Es256);
|
|
||||||
|
|
||||||
let rp_id_hash = [0x55; 32];
|
|
||||||
let mut encrypted_id =
|
|
||||||
encrypt_to_credential_id(&mut env, &private_key, &rp_id_hash, None, None).unwrap();
|
|
||||||
encrypted_id[0] = UNSUPPORTED_CREDENTIAL_ID_VERSION;
|
|
||||||
// Override the HMAC to pass the check.
|
|
||||||
encrypted_id.truncate(&encrypted_id.len() - 32);
|
|
||||||
let hmac_key = env.key_store().key_handle_authentication().unwrap();
|
|
||||||
let mut id_hmac = [0; HASH_SIZE];
|
|
||||||
Hmac::<TestEnv>::mac(&hmac_key, &encrypted_id[..], &mut id_hmac);
|
|
||||||
encrypted_id.extend(&id_hmac);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
decrypt_credential_id(&mut env, encrypted_id, &rp_id_hash),
|
|
||||||
Ok(None)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn test_encrypt_decrypt_bad_hmac(signature_algorithm: SignatureAlgorithm) {
|
|
||||||
let mut env = TestEnv::default();
|
|
||||||
let private_key = PrivateKey::new(&mut env, signature_algorithm);
|
|
||||||
|
|
||||||
let rp_id_hash = [0x55; 32];
|
|
||||||
let encrypted_id =
|
|
||||||
encrypt_to_credential_id(&mut env, &private_key, &rp_id_hash, None, None).unwrap();
|
|
||||||
for i in 0..encrypted_id.len() {
|
|
||||||
let mut modified_id = encrypted_id.clone();
|
|
||||||
modified_id[i] ^= 0x01;
|
|
||||||
assert_eq!(
|
|
||||||
decrypt_credential_id(&mut env, modified_id, &rp_id_hash),
|
|
||||||
Ok(None)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_ecdsa_encrypt_decrypt_bad_hmac() {
|
|
||||||
test_encrypt_decrypt_bad_hmac(SignatureAlgorithm::Es256);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[cfg(feature = "ed25519")]
|
|
||||||
fn test_ed25519_encrypt_decrypt_bad_hmac() {
|
|
||||||
test_encrypt_decrypt_bad_hmac(SignatureAlgorithm::Eddsa);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn test_decrypt_credential_missing_blocks(signature_algorithm: SignatureAlgorithm) {
|
|
||||||
let mut env = TestEnv::default();
|
|
||||||
let private_key = PrivateKey::new(&mut env, signature_algorithm);
|
|
||||||
|
|
||||||
let rp_id_hash = [0x55; 32];
|
|
||||||
let encrypted_id =
|
|
||||||
encrypt_to_credential_id(&mut env, &private_key, &rp_id_hash, None, None).unwrap();
|
|
||||||
|
|
||||||
for length in (1..CBOR_CREDENTIAL_ID_SIZE).step_by(16) {
|
|
||||||
assert_eq!(
|
|
||||||
decrypt_credential_id(&mut env, encrypted_id[..length].to_vec(), &rp_id_hash),
|
|
||||||
Ok(None)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_ecdsa_decrypt_credential_missing_blocks() {
|
|
||||||
test_decrypt_credential_missing_blocks(SignatureAlgorithm::Es256);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[cfg(feature = "ed25519")]
|
|
||||||
fn test_ed25519_decrypt_credential_missing_blocks() {
|
|
||||||
test_decrypt_credential_missing_blocks(SignatureAlgorithm::Eddsa);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This is a copy of the function that genereated deprecated key handles.
|
|
||||||
fn legacy_encrypt_to_credential_id(
|
|
||||||
env: &mut TestEnv,
|
|
||||||
private_key: EcdsaSk<TestEnv>,
|
|
||||||
application: &[u8; 32],
|
|
||||||
) -> Result<Vec<u8>, Ctap2StatusCode> {
|
|
||||||
let aes_key = AesKey::<TestEnv>::new(&*env.key_store().key_handle_encryption()?);
|
|
||||||
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)?;
|
|
||||||
let mut id_hmac = [0; HASH_SIZE];
|
|
||||||
Hmac::<TestEnv>::mac(
|
|
||||||
&*env.key_store().key_handle_authentication()?,
|
|
||||||
&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_ecdsa(&mut env);
|
|
||||||
let ecdsa_key = private_key.ecdsa_key(&mut env).unwrap();
|
|
||||||
|
|
||||||
let rp_id_hash = [0x55; 32];
|
|
||||||
let encrypted_id =
|
|
||||||
legacy_encrypt_to_credential_id(&mut env, ecdsa_key, &rp_id_hash).unwrap();
|
|
||||||
let decrypted_source = decrypt_credential_id(&mut env, encrypted_id, &rp_id_hash)
|
|
||||||
.unwrap()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(private_key, decrypted_source.private_key);
|
|
||||||
// Legacy credentials didn't persist credProtectPolicy info, so it should be treated as None.
|
|
||||||
assert!(decrypted_source.cred_protect_policy.is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_encrypt_credential_size() {
|
|
||||||
let mut env = TestEnv::default();
|
|
||||||
let private_key = PrivateKey::new(&mut env, SignatureAlgorithm::Es256);
|
|
||||||
|
|
||||||
let rp_id_hash = [0x55; 32];
|
|
||||||
let encrypted_id =
|
|
||||||
encrypt_to_credential_id(&mut env, &private_key, &rp_id_hash, None, None).unwrap();
|
|
||||||
assert_eq!(encrypted_id.len(), CBOR_CREDENTIAL_ID_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_encrypt_credential_max_cbor_size() {
|
|
||||||
// The cbor encoding length is variadic and depends on size of fields. Try to put maximum length
|
|
||||||
// for each encoded field and ensure that it doesn't go over the padding size.
|
|
||||||
let mut env = TestEnv::default();
|
|
||||||
// Currently all private key types have same length when transformed to bytes.
|
|
||||||
let private_key = PrivateKey::new(&mut env, SignatureAlgorithm::Es256);
|
|
||||||
let rp_id_hash = [0x55; 32];
|
|
||||||
let cred_protect_policy = Some(CredentialProtectionPolicy::UserVerificationOptional);
|
|
||||||
let cred_blob = Some(vec![0x55; env.customization().max_cred_blob_length()]);
|
|
||||||
|
|
||||||
let encrypted_id = encrypt_to_credential_id(
|
|
||||||
&mut env,
|
|
||||||
&private_key,
|
|
||||||
&rp_id_hash,
|
|
||||||
cred_protect_policy,
|
|
||||||
cred_blob,
|
|
||||||
);
|
|
||||||
|
|
||||||
assert!(encrypted_id.is_ok());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_cred_protect_persisted() {
|
|
||||||
let mut env = TestEnv::default();
|
|
||||||
let private_key = PrivateKey::new(&mut env, SignatureAlgorithm::Es256);
|
|
||||||
|
|
||||||
let rp_id_hash = [0x55; 32];
|
|
||||||
let encrypted_id = encrypt_to_credential_id(
|
|
||||||
&mut env,
|
|
||||||
&private_key,
|
|
||||||
&rp_id_hash,
|
|
||||||
Some(CredentialProtectionPolicy::UserVerificationRequired),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let decrypted_source = decrypt_credential_id(&mut env, encrypted_id, &rp_id_hash)
|
|
||||||
.unwrap()
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(decrypted_source.private_key, private_key);
|
|
||||||
assert_eq!(
|
|
||||||
decrypted_source.cred_protect_policy,
|
|
||||||
Some(CredentialProtectionPolicy::UserVerificationRequired)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_cred_blob_persisted() {
|
|
||||||
let mut env = TestEnv::default();
|
|
||||||
let private_key = PrivateKey::new(&mut env, SignatureAlgorithm::Es256);
|
|
||||||
|
|
||||||
let rp_id_hash = [0x55; 32];
|
|
||||||
let cred_blob = Some(vec![0x55; env.customization().max_cred_blob_length()]);
|
|
||||||
let encrypted_id =
|
|
||||||
encrypt_to_credential_id(&mut env, &private_key, &rp_id_hash, None, cred_blob.clone())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let decrypted_source = decrypt_credential_id(&mut env, encrypted_id, &rp_id_hash)
|
|
||||||
.unwrap()
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(decrypted_source.private_key, private_key);
|
|
||||||
assert_eq!(decrypted_source.cred_blob, cred_blob);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -353,12 +353,12 @@ pub fn process_credential_management<E: Env>(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::super::crypto_wrapper::PrivateKey;
|
|
||||||
use super::super::data_formats::{PinUvAuthProtocol, PublicKeyCredentialType};
|
use super::super::data_formats::{PinUvAuthProtocol, PublicKeyCredentialType};
|
||||||
use super::super::pin_protocol::authenticate_pin_uv_auth_token;
|
use super::super::pin_protocol::authenticate_pin_uv_auth_token;
|
||||||
use super::super::CtapState;
|
use super::super::CtapState;
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::api::crypto::ecdh::SecretKey as _;
|
use crate::api::crypto::ecdh::SecretKey as _;
|
||||||
|
use crate::api::private_key::PrivateKey;
|
||||||
use crate::api::rng::Rng;
|
use crate::api::rng::Rng;
|
||||||
use crate::env::test::TestEnv;
|
use crate::env::test::TestEnv;
|
||||||
use crate::env::EcdhSk;
|
use crate::env::EcdhSk;
|
||||||
|
|||||||
@@ -13,21 +13,11 @@
|
|||||||
// 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 _, Signature};
|
|
||||||
use crate::api::key_store::KeyStore;
|
|
||||||
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::{AesKey, EcdsaSk, Env};
|
use crate::env::{AesKey, Env};
|
||||||
use alloc::vec;
|
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use core::convert::TryFrom;
|
|
||||||
use core::ops::Deref;
|
|
||||||
#[cfg(feature = "ed25519")]
|
|
||||||
use core::ops::DerefMut;
|
|
||||||
use rand_core::RngCore;
|
use rand_core::RngCore;
|
||||||
use sk_cbor as cbor;
|
|
||||||
use sk_cbor::{cbor_array, cbor_bytes, cbor_int};
|
|
||||||
|
|
||||||
/// 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>(
|
||||||
@@ -74,160 +64,6 @@ pub fn aes256_cbc_decrypt<E: Env>(
|
|||||||
Ok(plaintext)
|
Ok(plaintext)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An asymmetric private key that can sign messages.
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
// 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.
|
|
||||||
Ecdsa(Secret<[u8; 32]>),
|
|
||||||
#[cfg(feature = "ed25519")]
|
|
||||||
Ed25519(ed25519_compact::SecretKey),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PrivateKey {
|
|
||||||
/// Creates a new private key for the given algorithm.
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// Panics if the algorithm is [`SignatureAlgorithm::Unknown`].
|
|
||||||
pub fn new(env: &mut impl Env, alg: SignatureAlgorithm) -> Self {
|
|
||||||
match alg {
|
|
||||||
SignatureAlgorithm::Es256 => {
|
|
||||||
PrivateKey::Ecdsa(env.key_store().generate_ecdsa_seed().unwrap())
|
|
||||||
}
|
|
||||||
#[cfg(feature = "ed25519")]
|
|
||||||
SignatureAlgorithm::Eddsa => {
|
|
||||||
let mut bytes: Secret<[u8; 32]> = Secret::default();
|
|
||||||
env.rng().fill_bytes(bytes.deref_mut());
|
|
||||||
Self::new_ed25519_from_bytes(&*bytes).unwrap()
|
|
||||||
}
|
|
||||||
SignatureAlgorithm::Unknown => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new ecdsa private key.
|
|
||||||
pub fn new_ecdsa(env: &mut impl Env) -> PrivateKey {
|
|
||||||
Self::new(env, SignatureAlgorithm::Es256)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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;
|
|
||||||
}
|
|
||||||
let mut seed: Secret<[u8; 32]> = Secret::default();
|
|
||||||
seed.copy_from_slice(bytes);
|
|
||||||
Some(PrivateKey::Ecdsa(seed))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "ed25519")]
|
|
||||||
pub fn new_ed25519_from_bytes(bytes: &[u8]) -> Option<Self> {
|
|
||||||
if bytes.len() != 32 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let seed = ed25519_compact::Seed::from_slice(bytes).unwrap();
|
|
||||||
Some(Self::Ed25519(ed25519_compact::KeyPair::from_seed(seed).sk))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the ECDSA private key.
|
|
||||||
pub fn ecdsa_key<E: Env>(&self, env: &mut E) -> Result<EcdsaSk<E>, Ctap2StatusCode> {
|
|
||||||
match self {
|
|
||||||
PrivateKey::Ecdsa(seed) => ecdsa_key_from_seed(env, seed),
|
|
||||||
#[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> {
|
|
||||||
Ok(match self {
|
|
||||||
PrivateKey::Ecdsa(ecdsa_seed) => {
|
|
||||||
CoseKey::from_ecdsa_public_key(ecdsa_key_from_seed(env, ecdsa_seed)?.public_key())
|
|
||||||
}
|
|
||||||
#[cfg(feature = "ed25519")]
|
|
||||||
PrivateKey::Ed25519(ed25519_key) => CoseKey::from(ed25519_key.public_key()),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the encoded signature for a given message.
|
|
||||||
pub fn sign_and_encode(
|
|
||||||
&self,
|
|
||||||
env: &mut impl Env,
|
|
||||||
message: &[u8],
|
|
||||||
) -> Result<Vec<u8>, Ctap2StatusCode> {
|
|
||||||
Ok(match self {
|
|
||||||
PrivateKey::Ecdsa(ecdsa_seed) => {
|
|
||||||
ecdsa_key_from_seed(env, ecdsa_seed)?.sign(message).to_der()
|
|
||||||
}
|
|
||||||
#[cfg(feature = "ed25519")]
|
|
||||||
PrivateKey::Ed25519(ed25519_key) => ed25519_key.sign(message, None).to_vec(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The associated COSE signature algorithm identifier.
|
|
||||||
pub fn signature_algorithm(&self) -> SignatureAlgorithm {
|
|
||||||
match self {
|
|
||||||
PrivateKey::Ecdsa(_) => SignatureAlgorithm::Es256,
|
|
||||||
#[cfg(feature = "ed25519")]
|
|
||||||
PrivateKey::Ed25519(_) => SignatureAlgorithm::Eddsa,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Writes the key bytes.
|
|
||||||
pub fn to_bytes(&self) -> Secret<[u8]> {
|
|
||||||
let mut bytes = Secret::new(32);
|
|
||||||
match self {
|
|
||||||
PrivateKey::Ecdsa(ecdsa_seed) => bytes.copy_from_slice(ecdsa_seed.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 called in encrypt_to_credential_id and PublicKeyCredentialSource, needs zeroization
|
|
||||||
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 {
|
|
||||||
type Error = Ctap2StatusCode;
|
|
||||||
|
|
||||||
fn try_from(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())?;
|
|
||||||
match SignatureAlgorithm::try_from(array.pop().unwrap())? {
|
|
||||||
SignatureAlgorithm::Es256 => PrivateKey::new_ecdsa_from_bytes(&key_bytes)
|
|
||||||
.ok_or(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR),
|
|
||||||
#[cfg(feature = "ed25519")]
|
|
||||||
SignatureAlgorithm::Eddsa => PrivateKey::new_ed25519_from_bytes(&key_bytes)
|
|
||||||
.ok_or(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR),
|
|
||||||
_ => Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -297,141 +133,4 @@ mod test {
|
|||||||
assert_ne!(block1, block2);
|
assert_ne!(block1, block2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_new_ecdsa_from_bytes() {
|
|
||||||
let mut env = TestEnv::default();
|
|
||||||
let private_key = PrivateKey::new(&mut env, SignatureAlgorithm::Es256);
|
|
||||||
let key_bytes = private_key.to_bytes();
|
|
||||||
assert_eq!(
|
|
||||||
PrivateKey::new_ecdsa_from_bytes(&key_bytes),
|
|
||||||
Some(private_key)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[cfg(feature = "ed25519")]
|
|
||||||
fn test_new_ed25519_from_bytes() {
|
|
||||||
let mut env = TestEnv::default();
|
|
||||||
let private_key = PrivateKey::new(&mut env, SignatureAlgorithm::Eddsa);
|
|
||||||
let key_bytes = private_key.to_bytes();
|
|
||||||
assert_eq!(
|
|
||||||
PrivateKey::new_ed25519_from_bytes(&key_bytes),
|
|
||||||
Some(private_key)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_new_ecdsa_from_bytes_wrong_length() {
|
|
||||||
assert_eq!(PrivateKey::new_ecdsa_from_bytes(&[0x55; 16]), None);
|
|
||||||
assert_eq!(PrivateKey::new_ecdsa_from_bytes(&[0x55; 31]), None);
|
|
||||||
assert_eq!(PrivateKey::new_ecdsa_from_bytes(&[0x55; 33]), None);
|
|
||||||
assert_eq!(PrivateKey::new_ecdsa_from_bytes(&[0x55; 64]), None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[cfg(feature = "ed25519")]
|
|
||||||
fn test_new_ed25519_from_bytes_wrong_length() {
|
|
||||||
assert_eq!(PrivateKey::new_ed25519_from_bytes(&[0x55; 16]), None);
|
|
||||||
assert_eq!(PrivateKey::new_ed25519_from_bytes(&[0x55; 31]), None);
|
|
||||||
assert_eq!(PrivateKey::new_ed25519_from_bytes(&[0x55; 33]), None);
|
|
||||||
assert_eq!(PrivateKey::new_ed25519_from_bytes(&[0x55; 64]), None);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_private_key_get_pub_key() {
|
|
||||||
let mut env = TestEnv::default();
|
|
||||||
let private_key = PrivateKey::new_ecdsa(&mut env);
|
|
||||||
let ecdsa_key = private_key.ecdsa_key(&mut env).unwrap();
|
|
||||||
let public_key = ecdsa_key.public_key();
|
|
||||||
assert_eq!(
|
|
||||||
private_key.get_pub_key(&mut env),
|
|
||||||
Ok(CoseKey::from_ecdsa_public_key(public_key))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_private_key_sign_and_encode() {
|
|
||||||
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 signature = ecdsa_key.sign(&message).to_der();
|
|
||||||
assert_eq!(
|
|
||||||
private_key.sign_and_encode(&mut env, &message),
|
|
||||||
Ok(signature)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn test_private_key_signature_algorithm(signature_algorithm: SignatureAlgorithm) {
|
|
||||||
let mut env = TestEnv::default();
|
|
||||||
let private_key = PrivateKey::new(&mut env, signature_algorithm);
|
|
||||||
assert_eq!(private_key.signature_algorithm(), signature_algorithm);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_ecdsa_private_key_signature_algorithm() {
|
|
||||||
test_private_key_signature_algorithm(SignatureAlgorithm::Es256);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[cfg(feature = "ed25519")]
|
|
||||||
fn test_ed25519_private_key_signature_algorithm() {
|
|
||||||
test_private_key_signature_algorithm(SignatureAlgorithm::Eddsa);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn test_private_key_from_to_cbor(signature_algorithm: SignatureAlgorithm) {
|
|
||||||
let mut env = TestEnv::default();
|
|
||||||
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),);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_ecdsa_private_key_from_to_cbor() {
|
|
||||||
test_private_key_from_to_cbor(SignatureAlgorithm::Es256);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[cfg(feature = "ed25519")]
|
|
||||||
fn test_ed25519_private_key_from_to_cbor() {
|
|
||||||
test_private_key_from_to_cbor(SignatureAlgorithm::Eddsa);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn test_private_key_from_bad_cbor(signature_algorithm: SignatureAlgorithm) {
|
|
||||||
let cbor = cbor_array![
|
|
||||||
cbor_int!(signature_algorithm as i64),
|
|
||||||
cbor_bytes!(vec![0x88; 32]),
|
|
||||||
// The array is too long.
|
|
||||||
cbor_int!(0),
|
|
||||||
];
|
|
||||||
assert_eq!(
|
|
||||||
PrivateKey::try_from(cbor),
|
|
||||||
Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_ecdsa_private_key_from_bad_cbor() {
|
|
||||||
test_private_key_from_bad_cbor(SignatureAlgorithm::Es256);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[cfg(feature = "ed25519")]
|
|
||||||
fn test_ed25519_private_key_from_bad_cbor() {
|
|
||||||
test_private_key_from_bad_cbor(SignatureAlgorithm::Eddsa);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_private_key_from_bad_cbor_unsupported_algo() {
|
|
||||||
let cbor = cbor_array![
|
|
||||||
// This algorithms doesn't exist.
|
|
||||||
cbor_int!(-1),
|
|
||||||
cbor_bytes!(vec![0x88; 32]),
|
|
||||||
];
|
|
||||||
assert_eq!(
|
|
||||||
PrivateKey::try_from(cbor),
|
|
||||||
Err(Ctap2StatusCode::CTAP2_ERR_INVALID_CBOR),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,12 +13,12 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use super::apdu::{Apdu, ApduStatusCode};
|
use super::apdu::{Apdu, ApduStatusCode};
|
||||||
use super::credential_id::{decrypt_credential_id, encrypt_to_credential_id};
|
|
||||||
use super::crypto_wrapper::PrivateKey;
|
|
||||||
use super::CtapState;
|
use super::CtapState;
|
||||||
use crate::api::attestation_store::{self, Attestation, AttestationStore};
|
use crate::api::attestation_store::{self, Attestation, AttestationStore};
|
||||||
use crate::api::crypto::ecdsa::{self, SecretKey as _, Signature};
|
use crate::api::crypto::ecdsa::{self, SecretKey as _, Signature};
|
||||||
use crate::api::crypto::EC_FIELD_SIZE;
|
use crate::api::crypto::EC_FIELD_SIZE;
|
||||||
|
use crate::api::key_store::{CredentialSource, KeyStore};
|
||||||
|
use crate::api::private_key::PrivateKey;
|
||||||
use crate::env::{EcdsaSk, Env};
|
use crate::env::{EcdsaSk, Env};
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use arrayref::{array_ref, mut_array_refs};
|
use arrayref::{array_ref, mut_array_refs};
|
||||||
@@ -259,7 +259,15 @@ impl Ctap1Command {
|
|||||||
.ecdsa_key(env)
|
.ecdsa_key(env)
|
||||||
.map_err(|_| Ctap1StatusCode::SW_INTERNAL_EXCEPTION)?;
|
.map_err(|_| Ctap1StatusCode::SW_INTERNAL_EXCEPTION)?;
|
||||||
let pk = sk.public_key();
|
let pk = sk.public_key();
|
||||||
let key_handle = encrypt_to_credential_id(env, &private_key, &application, None, None)
|
let credential_source = CredentialSource {
|
||||||
|
private_key,
|
||||||
|
rp_id_hash: application,
|
||||||
|
cred_protect_policy: None,
|
||||||
|
cred_blob: None,
|
||||||
|
};
|
||||||
|
let key_handle = env
|
||||||
|
.key_store()
|
||||||
|
.wrap_credential(credential_source)
|
||||||
.map_err(|_| Ctap1StatusCode::SW_INTERNAL_EXCEPTION)?;
|
.map_err(|_| Ctap1StatusCode::SW_INTERNAL_EXCEPTION)?;
|
||||||
if key_handle.len() > 0xFF {
|
if key_handle.len() > 0xFF {
|
||||||
// This is just being defensive with unreachable code.
|
// This is just being defensive with unreachable code.
|
||||||
@@ -319,7 +327,9 @@ impl Ctap1Command {
|
|||||||
flags: Ctap1Flags,
|
flags: Ctap1Flags,
|
||||||
ctap_state: &mut CtapState<E>,
|
ctap_state: &mut CtapState<E>,
|
||||||
) -> Result<Vec<u8>, Ctap1StatusCode> {
|
) -> Result<Vec<u8>, Ctap1StatusCode> {
|
||||||
let credential_source = decrypt_credential_id(env, key_handle, &application)
|
let credential_source = env
|
||||||
|
.key_store()
|
||||||
|
.unwrap_credential(&key_handle, &application)
|
||||||
.map_err(|_| Ctap1StatusCode::SW_WRONG_DATA)?;
|
.map_err(|_| Ctap1StatusCode::SW_WRONG_DATA)?;
|
||||||
if let Some(credential_source) = credential_source {
|
if let Some(credential_source) = credential_source {
|
||||||
let ecdsa_key = credential_source
|
let ecdsa_key = credential_source
|
||||||
@@ -353,12 +363,12 @@ impl Ctap1Command {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::super::credential_id::CBOR_CREDENTIAL_ID_SIZE;
|
|
||||||
use super::super::data_formats::SignatureAlgorithm;
|
use super::super::data_formats::SignatureAlgorithm;
|
||||||
use super::super::TOUCH_TIMEOUT_MS;
|
use super::super::TOUCH_TIMEOUT_MS;
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::api::crypto::sha256::Sha256;
|
use crate::api::crypto::sha256::Sha256;
|
||||||
use crate::api::customization::Customization;
|
use crate::api::customization::Customization;
|
||||||
|
use crate::api::key_store::CBOR_CREDENTIAL_ID_SIZE;
|
||||||
use crate::ctap::secret::Secret;
|
use crate::ctap::secret::Secret;
|
||||||
use crate::ctap::storage;
|
use crate::ctap::storage;
|
||||||
use crate::env::test::TestEnv;
|
use crate::env::test::TestEnv;
|
||||||
@@ -401,6 +411,20 @@ mod test {
|
|||||||
message
|
message
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates an example wrapped credential and RP ID hash.
|
||||||
|
fn create_wrapped_credential(env: &mut TestEnv) -> (Vec<u8>, [u8; 32]) {
|
||||||
|
let private_key = PrivateKey::new(env, SignatureAlgorithm::Es256);
|
||||||
|
let rp_id_hash = Sha::<TestEnv>::digest(b"example.com");
|
||||||
|
let credential_source = CredentialSource {
|
||||||
|
private_key,
|
||||||
|
rp_id_hash,
|
||||||
|
cred_protect_policy: None,
|
||||||
|
cred_blob: None,
|
||||||
|
};
|
||||||
|
let key_handle = env.key_store().wrap_credential(credential_source).unwrap();
|
||||||
|
(key_handle, rp_id_hash)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_process_allowed() {
|
fn test_process_allowed() {
|
||||||
let mut env = TestEnv::default();
|
let mut env = TestEnv::default();
|
||||||
@@ -444,13 +468,11 @@ mod test {
|
|||||||
let response = Ctap1Command::process_command(&mut env, &message, &mut ctap_state).unwrap();
|
let response = Ctap1Command::process_command(&mut env, &message, &mut ctap_state).unwrap();
|
||||||
assert_eq!(response[0], Ctap1Command::LEGACY_BYTE);
|
assert_eq!(response[0], Ctap1Command::LEGACY_BYTE);
|
||||||
assert_eq!(response[66], CBOR_CREDENTIAL_ID_SIZE as u8);
|
assert_eq!(response[66], CBOR_CREDENTIAL_ID_SIZE as u8);
|
||||||
assert!(decrypt_credential_id(
|
let credential_source = env
|
||||||
&mut env,
|
.key_store()
|
||||||
response[67..67 + CBOR_CREDENTIAL_ID_SIZE].to_vec(),
|
.unwrap_credential(&response[67..67 + CBOR_CREDENTIAL_ID_SIZE], &application)
|
||||||
&application,
|
.unwrap();
|
||||||
)
|
assert!(credential_source.is_some());
|
||||||
.unwrap()
|
|
||||||
.is_some());
|
|
||||||
const CERT_START: usize = 67 + CBOR_CREDENTIAL_ID_SIZE;
|
const CERT_START: usize = 67 + CBOR_CREDENTIAL_ID_SIZE;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&response[CERT_START..][..attestation.certificate.len()],
|
&response[CERT_START..][..attestation.certificate.len()],
|
||||||
@@ -495,12 +517,9 @@ mod test {
|
|||||||
let mut env = TestEnv::default();
|
let mut env = TestEnv::default();
|
||||||
env.user_presence()
|
env.user_presence()
|
||||||
.set(|| panic!("Unexpected user presence check in CTAP1"));
|
.set(|| panic!("Unexpected user presence check in CTAP1"));
|
||||||
let sk = PrivateKey::new(&mut env, SignatureAlgorithm::Es256);
|
|
||||||
let mut ctap_state = CtapState::new(&mut env);
|
let mut ctap_state = CtapState::new(&mut env);
|
||||||
|
|
||||||
let rp_id = "example.com";
|
let (key_handle, application) = create_wrapped_credential(&mut env);
|
||||||
let application = Sha::<TestEnv>::digest(rp_id.as_bytes());
|
|
||||||
let key_handle = encrypt_to_credential_id(&mut env, &sk, &application, None, None).unwrap();
|
|
||||||
let message = create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
let message = create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
||||||
|
|
||||||
let response = Ctap1Command::process_command(&mut env, &message, &mut ctap_state);
|
let response = Ctap1Command::process_command(&mut env, &message, &mut ctap_state);
|
||||||
@@ -512,13 +531,10 @@ mod test {
|
|||||||
let mut env = TestEnv::default();
|
let mut env = TestEnv::default();
|
||||||
env.user_presence()
|
env.user_presence()
|
||||||
.set(|| panic!("Unexpected user presence check in CTAP1"));
|
.set(|| panic!("Unexpected user presence check in CTAP1"));
|
||||||
let sk = PrivateKey::new(&mut env, SignatureAlgorithm::Es256);
|
|
||||||
let mut ctap_state = CtapState::new(&mut env);
|
let mut ctap_state = CtapState::new(&mut env);
|
||||||
|
|
||||||
let rp_id = "example.com";
|
let (key_handle, mut application) = create_wrapped_credential(&mut env);
|
||||||
let application = Sha::<TestEnv>::digest(rp_id.as_bytes());
|
application[0] ^= 0x01;
|
||||||
let key_handle = encrypt_to_credential_id(&mut env, &sk, &application, None, None).unwrap();
|
|
||||||
let application = [0x55; 32];
|
|
||||||
let message = create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
let message = create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
||||||
|
|
||||||
let response = Ctap1Command::process_command(&mut env, &message, &mut ctap_state);
|
let response = Ctap1Command::process_command(&mut env, &message, &mut ctap_state);
|
||||||
@@ -530,12 +546,9 @@ mod test {
|
|||||||
let mut env = TestEnv::default();
|
let mut env = TestEnv::default();
|
||||||
env.user_presence()
|
env.user_presence()
|
||||||
.set(|| panic!("Unexpected user presence check in CTAP1"));
|
.set(|| panic!("Unexpected user presence check in CTAP1"));
|
||||||
let sk = PrivateKey::new(&mut env, SignatureAlgorithm::Es256);
|
|
||||||
let mut ctap_state = CtapState::new(&mut env);
|
let mut ctap_state = CtapState::new(&mut env);
|
||||||
|
|
||||||
let rp_id = "example.com";
|
let (key_handle, application) = create_wrapped_credential(&mut env);
|
||||||
let application = Sha::<TestEnv>::digest(rp_id.as_bytes());
|
|
||||||
let key_handle = encrypt_to_credential_id(&mut env, &sk, &application, None, None).unwrap();
|
|
||||||
let mut message = create_authenticate_message(
|
let mut message = create_authenticate_message(
|
||||||
&application,
|
&application,
|
||||||
Ctap1Flags::DontEnforceUpAndSign,
|
Ctap1Flags::DontEnforceUpAndSign,
|
||||||
@@ -564,12 +577,9 @@ mod test {
|
|||||||
let mut env = TestEnv::default();
|
let mut env = TestEnv::default();
|
||||||
env.user_presence()
|
env.user_presence()
|
||||||
.set(|| panic!("Unexpected user presence check in CTAP1"));
|
.set(|| panic!("Unexpected user presence check in CTAP1"));
|
||||||
let sk = PrivateKey::new(&mut env, SignatureAlgorithm::Es256);
|
|
||||||
let mut ctap_state = CtapState::new(&mut env);
|
let mut ctap_state = CtapState::new(&mut env);
|
||||||
|
|
||||||
let rp_id = "example.com";
|
let (key_handle, application) = create_wrapped_credential(&mut env);
|
||||||
let application = Sha::<TestEnv>::digest(rp_id.as_bytes());
|
|
||||||
let key_handle = encrypt_to_credential_id(&mut env, &sk, &application, None, None).unwrap();
|
|
||||||
let mut message =
|
let mut message =
|
||||||
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
||||||
message[0] = 0xEE;
|
message[0] = 0xEE;
|
||||||
@@ -583,12 +593,9 @@ mod test {
|
|||||||
let mut env = TestEnv::default();
|
let mut env = TestEnv::default();
|
||||||
env.user_presence()
|
env.user_presence()
|
||||||
.set(|| panic!("Unexpected user presence check in CTAP1"));
|
.set(|| panic!("Unexpected user presence check in CTAP1"));
|
||||||
let sk = PrivateKey::new(&mut env, SignatureAlgorithm::Es256);
|
|
||||||
let mut ctap_state = CtapState::new(&mut env);
|
let mut ctap_state = CtapState::new(&mut env);
|
||||||
|
|
||||||
let rp_id = "example.com";
|
let (key_handle, application) = create_wrapped_credential(&mut env);
|
||||||
let application = Sha::<TestEnv>::digest(rp_id.as_bytes());
|
|
||||||
let key_handle = encrypt_to_credential_id(&mut env, &sk, &application, None, None).unwrap();
|
|
||||||
let mut message =
|
let mut message =
|
||||||
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
||||||
message[1] = 0xEE;
|
message[1] = 0xEE;
|
||||||
@@ -602,12 +609,9 @@ mod test {
|
|||||||
let mut env = TestEnv::default();
|
let mut env = TestEnv::default();
|
||||||
env.user_presence()
|
env.user_presence()
|
||||||
.set(|| panic!("Unexpected user presence check in CTAP1"));
|
.set(|| panic!("Unexpected user presence check in CTAP1"));
|
||||||
let sk = PrivateKey::new(&mut env, SignatureAlgorithm::Es256);
|
|
||||||
let mut ctap_state = CtapState::new(&mut env);
|
let mut ctap_state = CtapState::new(&mut env);
|
||||||
|
|
||||||
let rp_id = "example.com";
|
let (key_handle, application) = create_wrapped_credential(&mut env);
|
||||||
let application = Sha::<TestEnv>::digest(rp_id.as_bytes());
|
|
||||||
let key_handle = encrypt_to_credential_id(&mut env, &sk, &application, None, None).unwrap();
|
|
||||||
let mut message =
|
let mut message =
|
||||||
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
||||||
message[2] = 0xEE;
|
message[2] = 0xEE;
|
||||||
@@ -629,12 +633,9 @@ mod test {
|
|||||||
let mut env = TestEnv::default();
|
let mut env = TestEnv::default();
|
||||||
env.user_presence()
|
env.user_presence()
|
||||||
.set(|| panic!("Unexpected user presence check in CTAP1"));
|
.set(|| panic!("Unexpected user presence check in CTAP1"));
|
||||||
let sk = PrivateKey::new(&mut env, SignatureAlgorithm::Es256);
|
|
||||||
let mut ctap_state = CtapState::new(&mut env);
|
let mut ctap_state = CtapState::new(&mut env);
|
||||||
|
|
||||||
let rp_id = "example.com";
|
let (key_handle, application) = create_wrapped_credential(&mut env);
|
||||||
let application = Sha::<TestEnv>::digest(rp_id.as_bytes());
|
|
||||||
let key_handle = encrypt_to_credential_id(&mut env, &sk, &application, None, None).unwrap();
|
|
||||||
let message =
|
let message =
|
||||||
create_authenticate_message(&application, Ctap1Flags::EnforceUpAndSign, &key_handle);
|
create_authenticate_message(&application, Ctap1Flags::EnforceUpAndSign, &key_handle);
|
||||||
|
|
||||||
@@ -655,12 +656,9 @@ mod test {
|
|||||||
let mut env = TestEnv::default();
|
let mut env = TestEnv::default();
|
||||||
env.user_presence()
|
env.user_presence()
|
||||||
.set(|| panic!("Unexpected user presence check in CTAP1"));
|
.set(|| panic!("Unexpected user presence check in CTAP1"));
|
||||||
let sk = PrivateKey::new(&mut env, SignatureAlgorithm::Es256);
|
|
||||||
let mut ctap_state = CtapState::new(&mut env);
|
let mut ctap_state = CtapState::new(&mut env);
|
||||||
|
|
||||||
let rp_id = "example.com";
|
let (key_handle, application) = create_wrapped_credential(&mut env);
|
||||||
let application = Sha::<TestEnv>::digest(rp_id.as_bytes());
|
|
||||||
let key_handle = encrypt_to_credential_id(&mut env, &sk, &application, None, None).unwrap();
|
|
||||||
let message = create_authenticate_message(
|
let message = create_authenticate_message(
|
||||||
&application,
|
&application,
|
||||||
Ctap1Flags::DontEnforceUpAndSign,
|
Ctap1Flags::DontEnforceUpAndSign,
|
||||||
|
|||||||
@@ -12,9 +12,9 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use super::crypto_wrapper::PrivateKey;
|
|
||||||
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 alloc::string::String;
|
use alloc::string::String;
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
#[cfg(feature = "fuzz")]
|
#[cfg(feature = "fuzz")]
|
||||||
|
|||||||
@@ -16,9 +16,8 @@ pub mod apdu;
|
|||||||
mod client_pin;
|
mod client_pin;
|
||||||
pub mod command;
|
pub mod command;
|
||||||
mod config_command;
|
mod config_command;
|
||||||
mod credential_id;
|
|
||||||
mod credential_management;
|
mod credential_management;
|
||||||
mod crypto_wrapper;
|
pub mod crypto_wrapper;
|
||||||
#[cfg(feature = "with_ctap1")]
|
#[cfg(feature = "with_ctap1")]
|
||||||
mod ctap1;
|
mod ctap1;
|
||||||
pub mod data_formats;
|
pub mod data_formats;
|
||||||
@@ -41,11 +40,7 @@ use self::command::{
|
|||||||
AuthenticatorGetAssertionParameters, AuthenticatorMakeCredentialParameters, Command,
|
AuthenticatorGetAssertionParameters, AuthenticatorMakeCredentialParameters, Command,
|
||||||
};
|
};
|
||||||
use self::config_command::process_config;
|
use self::config_command::process_config;
|
||||||
use self::credential_id::{
|
|
||||||
decrypt_credential_id, encrypt_to_credential_id, MAX_CREDENTIAL_ID_SIZE,
|
|
||||||
};
|
|
||||||
use self::credential_management::process_credential_management;
|
use self::credential_management::process_credential_management;
|
||||||
use self::crypto_wrapper::PrivateKey;
|
|
||||||
use self::data_formats::{
|
use self::data_formats::{
|
||||||
AuthenticatorTransport, CredentialProtectionPolicy, EnterpriseAttestationMode,
|
AuthenticatorTransport, CredentialProtectionPolicy, EnterpriseAttestationMode,
|
||||||
GetAssertionExtensions, PackedAttestationStatement, PinUvAuthProtocol,
|
GetAssertionExtensions, PackedAttestationStatement, PinUvAuthProtocol,
|
||||||
@@ -70,7 +65,8 @@ use crate::api::crypto::hkdf256::Hkdf256;
|
|||||||
use crate::api::crypto::sha256::Sha256;
|
use crate::api::crypto::sha256::Sha256;
|
||||||
use crate::api::crypto::HASH_SIZE;
|
use crate::api::crypto::HASH_SIZE;
|
||||||
use crate::api::customization::Customization;
|
use crate::api::customization::Customization;
|
||||||
use crate::api::key_store::KeyStore;
|
use crate::api::key_store::{CredentialSource, KeyStore, MAX_CREDENTIAL_ID_SIZE};
|
||||||
|
use crate::api::private_key::PrivateKey;
|
||||||
use crate::api::rng::Rng;
|
use crate::api::rng::Rng;
|
||||||
use crate::api::user_presence::{UserPresence, UserPresenceError};
|
use crate::api::user_presence::{UserPresence, UserPresenceError};
|
||||||
use crate::env::{EcdsaSk, Env, Hkdf, Sha};
|
use crate::env::{EcdsaSk, Env, Hkdf, Sha};
|
||||||
@@ -188,6 +184,30 @@ pub fn cbor_write(value: cbor::Value, encoded_cbor: &mut Vec<u8>) -> Result<(),
|
|||||||
.map_err(|_e| Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)
|
.map_err(|_e| Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn decrypt_credential_id<E: Env>(
|
||||||
|
env: &mut E,
|
||||||
|
credential_id: Vec<u8>,
|
||||||
|
rp_id_hash: &[u8],
|
||||||
|
) -> Result<Option<PublicKeyCredentialSource>, Ctap2StatusCode> {
|
||||||
|
let credential_source = env
|
||||||
|
.key_store()
|
||||||
|
.unwrap_credential(&credential_id, rp_id_hash)?;
|
||||||
|
Ok(credential_source.map(|c| PublicKeyCredentialSource {
|
||||||
|
key_type: PublicKeyCredentialType::PublicKey,
|
||||||
|
credential_id,
|
||||||
|
private_key: c.private_key,
|
||||||
|
rp_id: String::new(),
|
||||||
|
user_handle: Vec::new(),
|
||||||
|
user_display_name: None,
|
||||||
|
cred_protect_policy: c.cred_protect_policy,
|
||||||
|
creation_order: 0,
|
||||||
|
user_name: None,
|
||||||
|
user_icon: None,
|
||||||
|
cred_blob: c.cred_blob,
|
||||||
|
large_blob_key: None,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
// This function is adapted from https://doc.rust-lang.org/nightly/src/core/str/mod.rs.html#2110
|
// This function is adapted from https://doc.rust-lang.org/nightly/src/core/str/mod.rs.html#2110
|
||||||
// (as of 2020-01-20) and truncates to "max" bytes, not breaking the encoding.
|
// (as of 2020-01-20) and truncates to "max" bytes, not breaking the encoding.
|
||||||
// We change the return value, since we don't need the bool.
|
// We change the return value, since we don't need the bool.
|
||||||
@@ -862,13 +882,15 @@ impl<E: Env> CtapState<E> {
|
|||||||
storage::store_credential(env, credential_source)?;
|
storage::store_credential(env, credential_source)?;
|
||||||
random_id
|
random_id
|
||||||
} else {
|
} else {
|
||||||
encrypt_to_credential_id(
|
let credential_source = CredentialSource {
|
||||||
env,
|
private_key: private_key.clone(),
|
||||||
&private_key,
|
rp_id_hash,
|
||||||
&rp_id_hash,
|
|
||||||
cred_protect_policy,
|
cred_protect_policy,
|
||||||
cred_blob,
|
cred_blob,
|
||||||
)?
|
};
|
||||||
|
env.key_store()
|
||||||
|
.wrap_credential(credential_source)
|
||||||
|
.map_err(|_| Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut auth_data = self.generate_auth_data(env, &rp_id_hash, flags)?;
|
let mut auth_data = self.generate_auth_data(env, &rp_id_hash, flags)?;
|
||||||
@@ -1361,7 +1383,6 @@ mod test {
|
|||||||
use super::command::{
|
use super::command::{
|
||||||
AuthenticatorClientPinParameters, AuthenticatorCredentialManagementParameters,
|
AuthenticatorClientPinParameters, AuthenticatorCredentialManagementParameters,
|
||||||
};
|
};
|
||||||
use super::credential_id::CBOR_CREDENTIAL_ID_SIZE;
|
|
||||||
use super::data_formats::{
|
use super::data_formats::{
|
||||||
ClientPinSubCommand, CoseKey, CredentialManagementSubCommand, GetAssertionHmacSecretInput,
|
ClientPinSubCommand, CoseKey, CredentialManagementSubCommand, GetAssertionHmacSecretInput,
|
||||||
GetAssertionOptions, MakeCredentialExtensions, MakeCredentialOptions, PinUvAuthProtocol,
|
GetAssertionOptions, MakeCredentialExtensions, MakeCredentialOptions, PinUvAuthProtocol,
|
||||||
@@ -1371,6 +1392,7 @@ mod test {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::api::crypto::ecdh::SecretKey as _;
|
use crate::api::crypto::ecdh::SecretKey as _;
|
||||||
use crate::api::customization;
|
use crate::api::customization;
|
||||||
|
use crate::api::key_store::CBOR_CREDENTIAL_ID_SIZE;
|
||||||
use crate::api::user_presence::UserPresenceResult;
|
use crate::api::user_presence::UserPresenceResult;
|
||||||
use crate::env::test::TestEnv;
|
use crate::env::test::TestEnv;
|
||||||
use crate::env::EcdhSk;
|
use crate::env::EcdhSk;
|
||||||
|
|||||||
@@ -598,8 +598,8 @@ fn serialize_min_pin_length_rp_ids(rp_ids: Vec<String>) -> Result<Vec<u8>, Ctap2
|
|||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::api::attestation_store::{self, Attestation, AttestationStore};
|
use crate::api::attestation_store::{self, Attestation, AttestationStore};
|
||||||
|
use crate::api::private_key::PrivateKey;
|
||||||
use crate::api::rng::Rng;
|
use crate::api::rng::Rng;
|
||||||
use crate::ctap::crypto_wrapper::PrivateKey;
|
|
||||||
use crate::ctap::data_formats::{
|
use crate::ctap::data_formats::{
|
||||||
CredentialProtectionPolicy, PublicKeyCredentialSource, PublicKeyCredentialType,
|
CredentialProtectionPolicy, PublicKeyCredentialSource, PublicKeyCredentialType,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user