implicitly generate HMAC-secret
This commit is contained in:
@@ -291,7 +291,7 @@ impl Ctap1Command {
|
|||||||
let sk = crypto::ecdsa::SecKey::gensk(ctap_state.rng);
|
let sk = crypto::ecdsa::SecKey::gensk(ctap_state.rng);
|
||||||
let pk = sk.genpk();
|
let pk = sk.genpk();
|
||||||
let key_handle = ctap_state
|
let key_handle = ctap_state
|
||||||
.encrypt_key_handle(sk, &application, None)
|
.encrypt_key_handle(sk, &application)
|
||||||
.map_err(|_| Ctap1StatusCode::SW_COMMAND_ABORTED)?;
|
.map_err(|_| Ctap1StatusCode::SW_COMMAND_ABORTED)?;
|
||||||
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.
|
||||||
@@ -386,7 +386,7 @@ impl Ctap1Command {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::super::{key_material, CREDENTIAL_ID_BASE_SIZE, USE_SIGNATURE_COUNTER};
|
use super::super::{key_material, CREDENTIAL_ID_SIZE, USE_SIGNATURE_COUNTER};
|
||||||
use super::*;
|
use super::*;
|
||||||
use crypto::rng256::ThreadRng256;
|
use crypto::rng256::ThreadRng256;
|
||||||
use crypto::Hash256;
|
use crypto::Hash256;
|
||||||
@@ -426,12 +426,12 @@ mod test {
|
|||||||
0x00,
|
0x00,
|
||||||
0x00,
|
0x00,
|
||||||
0x00,
|
0x00,
|
||||||
65 + CREDENTIAL_ID_BASE_SIZE as u8,
|
65 + CREDENTIAL_ID_SIZE as u8,
|
||||||
];
|
];
|
||||||
let challenge = [0x0C; 32];
|
let challenge = [0x0C; 32];
|
||||||
message.extend(&challenge);
|
message.extend(&challenge);
|
||||||
message.extend(application);
|
message.extend(application);
|
||||||
message.push(CREDENTIAL_ID_BASE_SIZE as u8);
|
message.push(CREDENTIAL_ID_SIZE as u8);
|
||||||
message.extend(key_handle);
|
message.extend(key_handle);
|
||||||
message
|
message
|
||||||
}
|
}
|
||||||
@@ -471,15 +471,12 @@ mod test {
|
|||||||
let response =
|
let response =
|
||||||
Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE).unwrap();
|
Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE).unwrap();
|
||||||
assert_eq!(response[0], Ctap1Command::LEGACY_BYTE);
|
assert_eq!(response[0], Ctap1Command::LEGACY_BYTE);
|
||||||
assert_eq!(response[66], CREDENTIAL_ID_BASE_SIZE as u8);
|
assert_eq!(response[66], CREDENTIAL_ID_SIZE as u8);
|
||||||
assert!(ctap_state
|
assert!(ctap_state
|
||||||
.decrypt_credential_source(
|
.decrypt_credential_source(response[67..67 + CREDENTIAL_ID_SIZE].to_vec(), &application)
|
||||||
response[67..67 + CREDENTIAL_ID_BASE_SIZE].to_vec(),
|
|
||||||
&application
|
|
||||||
)
|
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.is_some());
|
.is_some());
|
||||||
const CERT_START: usize = 67 + CREDENTIAL_ID_BASE_SIZE;
|
const CERT_START: usize = 67 + CREDENTIAL_ID_SIZE;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&response[CERT_START..CERT_START + fake_cert.len()],
|
&response[CERT_START..CERT_START + fake_cert.len()],
|
||||||
&fake_cert[..]
|
&fake_cert[..]
|
||||||
@@ -528,9 +525,7 @@ mod test {
|
|||||||
|
|
||||||
let rp_id = "example.com";
|
let rp_id = "example.com";
|
||||||
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
||||||
let key_handle = ctap_state
|
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
|
||||||
.encrypt_key_handle(sk, &application, 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(&message, &mut ctap_state, START_CLOCK_VALUE);
|
let response = Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE);
|
||||||
@@ -546,9 +541,7 @@ mod test {
|
|||||||
|
|
||||||
let rp_id = "example.com";
|
let rp_id = "example.com";
|
||||||
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
||||||
let key_handle = ctap_state
|
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
|
||||||
.encrypt_key_handle(sk, &application, None)
|
|
||||||
.unwrap();
|
|
||||||
let application = [0x55; 32];
|
let application = [0x55; 32];
|
||||||
let message = create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
let message = create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
||||||
|
|
||||||
@@ -565,9 +558,7 @@ mod test {
|
|||||||
|
|
||||||
let rp_id = "example.com";
|
let rp_id = "example.com";
|
||||||
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
||||||
let key_handle = ctap_state
|
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
|
||||||
.encrypt_key_handle(sk, &application, None)
|
|
||||||
.unwrap();
|
|
||||||
let mut message =
|
let mut message =
|
||||||
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
||||||
|
|
||||||
@@ -591,9 +582,7 @@ mod test {
|
|||||||
|
|
||||||
let rp_id = "example.com";
|
let rp_id = "example.com";
|
||||||
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
||||||
let key_handle = ctap_state
|
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
|
||||||
.encrypt_key_handle(sk, &application, 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;
|
||||||
@@ -611,9 +600,7 @@ mod test {
|
|||||||
|
|
||||||
let rp_id = "example.com";
|
let rp_id = "example.com";
|
||||||
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
||||||
let key_handle = ctap_state
|
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
|
||||||
.encrypt_key_handle(sk, &application, 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;
|
||||||
@@ -631,9 +618,7 @@ mod test {
|
|||||||
|
|
||||||
let rp_id = "example.com";
|
let rp_id = "example.com";
|
||||||
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
||||||
let key_handle = ctap_state
|
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
|
||||||
.encrypt_key_handle(sk, &application, 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;
|
||||||
@@ -659,9 +644,7 @@ mod test {
|
|||||||
|
|
||||||
let rp_id = "example.com";
|
let rp_id = "example.com";
|
||||||
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
||||||
let key_handle = ctap_state
|
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
|
||||||
.encrypt_key_handle(sk, &application, None)
|
|
||||||
.unwrap();
|
|
||||||
let message =
|
let message =
|
||||||
create_authenticate_message(&application, Ctap1Flags::EnforceUpAndSign, &key_handle);
|
create_authenticate_message(&application, Ctap1Flags::EnforceUpAndSign, &key_handle);
|
||||||
|
|
||||||
@@ -688,9 +671,7 @@ mod test {
|
|||||||
|
|
||||||
let rp_id = "example.com";
|
let rp_id = "example.com";
|
||||||
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
||||||
let key_handle = ctap_state
|
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
|
||||||
.encrypt_key_handle(sk, &application, None)
|
|
||||||
.unwrap();
|
|
||||||
let message = create_authenticate_message(
|
let message = create_authenticate_message(
|
||||||
&application,
|
&application,
|
||||||
Ctap1Flags::DontEnforceUpAndSign,
|
Ctap1Flags::DontEnforceUpAndSign,
|
||||||
@@ -712,7 +693,7 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_process_authenticate_bad_key_handle() {
|
fn test_process_authenticate_bad_key_handle() {
|
||||||
let application = [0x0A; 32];
|
let application = [0x0A; 32];
|
||||||
let key_handle = vec![0x00; CREDENTIAL_ID_BASE_SIZE];
|
let key_handle = vec![0x00; CREDENTIAL_ID_SIZE];
|
||||||
let message =
|
let message =
|
||||||
create_authenticate_message(&application, Ctap1Flags::EnforceUpAndSign, &key_handle);
|
create_authenticate_message(&application, Ctap1Flags::EnforceUpAndSign, &key_handle);
|
||||||
|
|
||||||
@@ -729,7 +710,7 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_process_authenticate_without_up() {
|
fn test_process_authenticate_without_up() {
|
||||||
let application = [0x0A; 32];
|
let application = [0x0A; 32];
|
||||||
let key_handle = vec![0x00; CREDENTIAL_ID_BASE_SIZE];
|
let key_handle = vec![0x00; CREDENTIAL_ID_SIZE];
|
||||||
let message =
|
let message =
|
||||||
create_authenticate_message(&application, Ctap1Flags::EnforceUpAndSign, &key_handle);
|
create_authenticate_message(&application, Ctap1Flags::EnforceUpAndSign, &key_handle);
|
||||||
|
|
||||||
|
|||||||
@@ -498,7 +498,6 @@ pub struct PublicKeyCredentialSource {
|
|||||||
pub rp_id: String,
|
pub rp_id: String,
|
||||||
pub user_handle: Vec<u8>, // not optional, but nullable
|
pub user_handle: Vec<u8>, // not optional, but nullable
|
||||||
pub user_display_name: Option<String>,
|
pub user_display_name: Option<String>,
|
||||||
pub cred_random: Option<Vec<u8>>,
|
|
||||||
pub cred_protect_policy: Option<CredentialProtectionPolicy>,
|
pub cred_protect_policy: Option<CredentialProtectionPolicy>,
|
||||||
pub creation_order: u64,
|
pub creation_order: u64,
|
||||||
pub user_name: Option<String>,
|
pub user_name: Option<String>,
|
||||||
@@ -513,14 +512,14 @@ enum PublicKeyCredentialSourceField {
|
|||||||
RpId = 2,
|
RpId = 2,
|
||||||
UserHandle = 3,
|
UserHandle = 3,
|
||||||
UserDisplayName = 4,
|
UserDisplayName = 4,
|
||||||
CredRandom = 5,
|
|
||||||
CredProtectPolicy = 6,
|
CredProtectPolicy = 6,
|
||||||
CreationOrder = 7,
|
CreationOrder = 7,
|
||||||
UserName = 8,
|
UserName = 8,
|
||||||
UserIcon = 9,
|
UserIcon = 9,
|
||||||
// When a field is removed, its tag should be reserved and not used for new fields. We document
|
// When a field is removed, its tag should be reserved and not used for new fields. We document
|
||||||
// those reserved tags below.
|
// those reserved tags below.
|
||||||
// Reserved tags: none.
|
// Reserved tags:
|
||||||
|
// - CredRandom = 5,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<PublicKeyCredentialSourceField> for cbor::KeyType {
|
impl From<PublicKeyCredentialSourceField> for cbor::KeyType {
|
||||||
@@ -539,7 +538,6 @@ impl From<PublicKeyCredentialSource> for cbor::Value {
|
|||||||
PublicKeyCredentialSourceField::RpId => Some(credential.rp_id),
|
PublicKeyCredentialSourceField::RpId => Some(credential.rp_id),
|
||||||
PublicKeyCredentialSourceField::UserHandle => Some(credential.user_handle),
|
PublicKeyCredentialSourceField::UserHandle => Some(credential.user_handle),
|
||||||
PublicKeyCredentialSourceField::UserDisplayName => credential.user_display_name,
|
PublicKeyCredentialSourceField::UserDisplayName => credential.user_display_name,
|
||||||
PublicKeyCredentialSourceField::CredRandom => credential.cred_random,
|
|
||||||
PublicKeyCredentialSourceField::CredProtectPolicy => credential.cred_protect_policy,
|
PublicKeyCredentialSourceField::CredProtectPolicy => credential.cred_protect_policy,
|
||||||
PublicKeyCredentialSourceField::CreationOrder => credential.creation_order,
|
PublicKeyCredentialSourceField::CreationOrder => credential.creation_order,
|
||||||
PublicKeyCredentialSourceField::UserName => credential.user_name,
|
PublicKeyCredentialSourceField::UserName => credential.user_name,
|
||||||
@@ -559,7 +557,6 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialSource {
|
|||||||
PublicKeyCredentialSourceField::RpId => rp_id,
|
PublicKeyCredentialSourceField::RpId => rp_id,
|
||||||
PublicKeyCredentialSourceField::UserHandle => user_handle,
|
PublicKeyCredentialSourceField::UserHandle => user_handle,
|
||||||
PublicKeyCredentialSourceField::UserDisplayName => user_display_name,
|
PublicKeyCredentialSourceField::UserDisplayName => user_display_name,
|
||||||
PublicKeyCredentialSourceField::CredRandom => cred_random,
|
|
||||||
PublicKeyCredentialSourceField::CredProtectPolicy => cred_protect_policy,
|
PublicKeyCredentialSourceField::CredProtectPolicy => cred_protect_policy,
|
||||||
PublicKeyCredentialSourceField::CreationOrder => creation_order,
|
PublicKeyCredentialSourceField::CreationOrder => creation_order,
|
||||||
PublicKeyCredentialSourceField::UserName => user_name,
|
PublicKeyCredentialSourceField::UserName => user_name,
|
||||||
@@ -577,7 +574,6 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialSource {
|
|||||||
let rp_id = extract_text_string(ok_or_missing(rp_id)?)?;
|
let rp_id = extract_text_string(ok_or_missing(rp_id)?)?;
|
||||||
let user_handle = extract_byte_string(ok_or_missing(user_handle)?)?;
|
let user_handle = extract_byte_string(ok_or_missing(user_handle)?)?;
|
||||||
let user_display_name = user_display_name.map(extract_text_string).transpose()?;
|
let user_display_name = user_display_name.map(extract_text_string).transpose()?;
|
||||||
let cred_random = cred_random.map(extract_byte_string).transpose()?;
|
|
||||||
let cred_protect_policy = cred_protect_policy
|
let cred_protect_policy = cred_protect_policy
|
||||||
.map(CredentialProtectionPolicy::try_from)
|
.map(CredentialProtectionPolicy::try_from)
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
@@ -601,7 +597,6 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialSource {
|
|||||||
rp_id,
|
rp_id,
|
||||||
user_handle,
|
user_handle,
|
||||||
user_display_name,
|
user_display_name,
|
||||||
cred_random,
|
|
||||||
cred_protect_policy,
|
cred_protect_policy,
|
||||||
creation_order,
|
creation_order,
|
||||||
user_name,
|
user_name,
|
||||||
@@ -1373,7 +1368,6 @@ mod test {
|
|||||||
rp_id: "example.com".to_string(),
|
rp_id: "example.com".to_string(),
|
||||||
user_handle: b"foo".to_vec(),
|
user_handle: b"foo".to_vec(),
|
||||||
user_display_name: None,
|
user_display_name: None,
|
||||||
cred_random: None,
|
|
||||||
cred_protect_policy: None,
|
cred_protect_policy: None,
|
||||||
creation_order: 0,
|
creation_order: 0,
|
||||||
user_name: None,
|
user_name: None,
|
||||||
@@ -1395,16 +1389,6 @@ mod test {
|
|||||||
Ok(credential.clone())
|
Ok(credential.clone())
|
||||||
);
|
);
|
||||||
|
|
||||||
let credential = PublicKeyCredentialSource {
|
|
||||||
cred_random: Some(vec![0x00; 32]),
|
|
||||||
..credential
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
PublicKeyCredentialSource::try_from(cbor::Value::from(credential.clone())),
|
|
||||||
Ok(credential.clone())
|
|
||||||
);
|
|
||||||
|
|
||||||
let credential = PublicKeyCredentialSource {
|
let credential = PublicKeyCredentialSource {
|
||||||
cred_protect_policy: Some(CredentialProtectionPolicy::UserVerificationOptional),
|
cred_protect_policy: Some(CredentialProtectionPolicy::UserVerificationOptional),
|
||||||
..credential
|
..credential
|
||||||
|
|||||||
128
src/ctap/mod.rs
128
src/ctap/mod.rs
@@ -86,10 +86,8 @@ pub const INITIAL_SIGNATURE_COUNTER: u32 = 1;
|
|||||||
// - 16 byte initialization vector for AES-256,
|
// - 16 byte initialization vector for AES-256,
|
||||||
// - 32 byte ECDSA private key for the credential,
|
// - 32 byte ECDSA private key for the credential,
|
||||||
// - 32 byte relying party ID hashed with SHA256,
|
// - 32 byte relying party ID hashed with SHA256,
|
||||||
// - (optional) 32 byte for HMAC-secret,
|
|
||||||
// - 32 byte HMAC-SHA256 over everything else.
|
// - 32 byte HMAC-SHA256 over everything else.
|
||||||
pub const CREDENTIAL_ID_BASE_SIZE: usize = 112;
|
pub const CREDENTIAL_ID_SIZE: usize = 112;
|
||||||
pub const CREDENTIAL_ID_MAX_SIZE: usize = CREDENTIAL_ID_BASE_SIZE + 32;
|
|
||||||
// Set this bit when checking user presence.
|
// Set this bit when checking user presence.
|
||||||
const UP_FLAG: u8 = 0x01;
|
const UP_FLAG: u8 = 0x01;
|
||||||
// Set this bit when checking user verification.
|
// Set this bit when checking user verification.
|
||||||
@@ -142,6 +140,7 @@ struct AssertionInput {
|
|||||||
client_data_hash: Vec<u8>,
|
client_data_hash: Vec<u8>,
|
||||||
auth_data: Vec<u8>,
|
auth_data: Vec<u8>,
|
||||||
hmac_secret_input: Option<GetAssertionHmacSecretInput>,
|
hmac_secret_input: Option<GetAssertionHmacSecretInput>,
|
||||||
|
has_uv: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AssertionState {
|
struct AssertionState {
|
||||||
@@ -231,7 +230,6 @@ where
|
|||||||
&mut self,
|
&mut self,
|
||||||
private_key: crypto::ecdsa::SecKey,
|
private_key: crypto::ecdsa::SecKey,
|
||||||
application: &[u8; 32],
|
application: &[u8; 32],
|
||||||
cred_random: Option<&[u8; 32]>,
|
|
||||||
) -> Result<Vec<u8>, Ctap2StatusCode> {
|
) -> Result<Vec<u8>, Ctap2StatusCode> {
|
||||||
let master_keys = self.persistent_store.master_keys()?;
|
let master_keys = self.persistent_store.master_keys()?;
|
||||||
let aes_enc_key = crypto::aes256::EncryptionKey::new(&master_keys.encryption);
|
let aes_enc_key = crypto::aes256::EncryptionKey::new(&master_keys.encryption);
|
||||||
@@ -240,16 +238,12 @@ where
|
|||||||
let mut iv = [0; 16];
|
let mut iv = [0; 16];
|
||||||
iv.copy_from_slice(&self.rng.gen_uniform_u8x32()[..16]);
|
iv.copy_from_slice(&self.rng.gen_uniform_u8x32()[..16]);
|
||||||
|
|
||||||
let block_len = if cred_random.is_some() { 6 } else { 4 };
|
let block_len = 4;
|
||||||
let mut blocks = vec![[0u8; 16]; block_len];
|
let mut blocks = vec![[0u8; 16]; block_len];
|
||||||
blocks[0].copy_from_slice(&sk_bytes[..16]);
|
blocks[0].copy_from_slice(&sk_bytes[..16]);
|
||||||
blocks[1].copy_from_slice(&sk_bytes[16..]);
|
blocks[1].copy_from_slice(&sk_bytes[16..]);
|
||||||
blocks[2].copy_from_slice(&application[..16]);
|
blocks[2].copy_from_slice(&application[..16]);
|
||||||
blocks[3].copy_from_slice(&application[16..]);
|
blocks[3].copy_from_slice(&application[16..]);
|
||||||
if let Some(cred_random) = cred_random {
|
|
||||||
blocks[4].copy_from_slice(&cred_random[..16]);
|
|
||||||
blocks[5].copy_from_slice(&cred_random[16..]);
|
|
||||||
}
|
|
||||||
cbc_encrypt(&aes_enc_key, iv, &mut blocks);
|
cbc_encrypt(&aes_enc_key, iv, &mut blocks);
|
||||||
|
|
||||||
let mut encrypted_id = Vec::with_capacity(16 * (block_len + 3));
|
let mut encrypted_id = Vec::with_capacity(16 * (block_len + 3));
|
||||||
@@ -270,11 +264,9 @@ where
|
|||||||
credential_id: Vec<u8>,
|
credential_id: Vec<u8>,
|
||||||
rp_id_hash: &[u8],
|
rp_id_hash: &[u8],
|
||||||
) -> Result<Option<PublicKeyCredentialSource>, Ctap2StatusCode> {
|
) -> Result<Option<PublicKeyCredentialSource>, Ctap2StatusCode> {
|
||||||
let has_cred_random = match credential_id.len() {
|
if credential_id.len() != CREDENTIAL_ID_SIZE {
|
||||||
CREDENTIAL_ID_BASE_SIZE => false,
|
return Ok(None);
|
||||||
CREDENTIAL_ID_MAX_SIZE => true,
|
}
|
||||||
_ => return Ok(None),
|
|
||||||
};
|
|
||||||
let master_keys = self.persistent_store.master_keys()?;
|
let master_keys = self.persistent_store.master_keys()?;
|
||||||
let payload_size = credential_id.len() - 32;
|
let payload_size = credential_id.len() - 32;
|
||||||
if !verify_hmac_256::<Sha256>(
|
if !verify_hmac_256::<Sha256>(
|
||||||
@@ -288,7 +280,7 @@ where
|
|||||||
let aes_dec_key = crypto::aes256::DecryptionKey::new(&aes_enc_key);
|
let aes_dec_key = crypto::aes256::DecryptionKey::new(&aes_enc_key);
|
||||||
let mut iv = [0; 16];
|
let mut iv = [0; 16];
|
||||||
iv.copy_from_slice(&credential_id[..16]);
|
iv.copy_from_slice(&credential_id[..16]);
|
||||||
let block_len = if has_cred_random { 6 } else { 4 };
|
let block_len = 4;
|
||||||
let mut blocks = vec![[0u8; 16]; block_len];
|
let mut blocks = vec![[0u8; 16]; block_len];
|
||||||
for i in 0..block_len {
|
for i in 0..block_len {
|
||||||
blocks[i].copy_from_slice(&credential_id[16 * (i + 1)..16 * (i + 2)]);
|
blocks[i].copy_from_slice(&credential_id[16 * (i + 1)..16 * (i + 2)]);
|
||||||
@@ -301,15 +293,6 @@ where
|
|||||||
decrypted_sk[16..].clone_from_slice(&blocks[1]);
|
decrypted_sk[16..].clone_from_slice(&blocks[1]);
|
||||||
decrypted_rp_id_hash[..16].clone_from_slice(&blocks[2]);
|
decrypted_rp_id_hash[..16].clone_from_slice(&blocks[2]);
|
||||||
decrypted_rp_id_hash[16..].clone_from_slice(&blocks[3]);
|
decrypted_rp_id_hash[16..].clone_from_slice(&blocks[3]);
|
||||||
let cred_random = if has_cred_random {
|
|
||||||
let mut decrypted_cred_random = [0; 32];
|
|
||||||
decrypted_cred_random[..16].clone_from_slice(&blocks[4]);
|
|
||||||
decrypted_cred_random[16..].clone_from_slice(&blocks[5]);
|
|
||||||
Some(decrypted_cred_random.to_vec())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
if rp_id_hash != decrypted_rp_id_hash {
|
if rp_id_hash != decrypted_rp_id_hash {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
@@ -322,7 +305,6 @@ where
|
|||||||
rp_id: String::from(""),
|
rp_id: String::from(""),
|
||||||
user_handle: vec![],
|
user_handle: vec![],
|
||||||
user_display_name: None,
|
user_display_name: None,
|
||||||
cred_random,
|
|
||||||
cred_protect_policy: None,
|
cred_protect_policy: None,
|
||||||
creation_order: 0,
|
creation_order: 0,
|
||||||
user_name: None,
|
user_name: None,
|
||||||
@@ -464,11 +446,6 @@ where
|
|||||||
(false, DEFAULT_CRED_PROTECT)
|
(false, DEFAULT_CRED_PROTECT)
|
||||||
};
|
};
|
||||||
|
|
||||||
let cred_random = if use_hmac_extension {
|
|
||||||
Some(self.rng.gen_uniform_u8x32())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
let has_extension_output = use_hmac_extension || cred_protect_policy.is_some();
|
let has_extension_output = use_hmac_extension || cred_protect_policy.is_some();
|
||||||
|
|
||||||
let rp_id = rp.rp_id;
|
let rp_id = rp.rp_id;
|
||||||
@@ -543,7 +520,6 @@ where
|
|||||||
user_display_name: user
|
user_display_name: user
|
||||||
.user_display_name
|
.user_display_name
|
||||||
.map(|s| truncate_to_char_boundary(&s, 64).to_string()),
|
.map(|s| truncate_to_char_boundary(&s, 64).to_string()),
|
||||||
cred_random: cred_random.map(|c| c.to_vec()),
|
|
||||||
cred_protect_policy,
|
cred_protect_policy,
|
||||||
creation_order: self.persistent_store.new_creation_order()?,
|
creation_order: self.persistent_store.new_creation_order()?,
|
||||||
user_name: user
|
user_name: user
|
||||||
@@ -556,7 +532,7 @@ where
|
|||||||
self.persistent_store.store_credential(credential_source)?;
|
self.persistent_store.store_credential(credential_source)?;
|
||||||
random_id
|
random_id
|
||||||
} else {
|
} else {
|
||||||
self.encrypt_key_handle(sk.clone(), &rp_id_hash, cred_random.as_ref())?
|
self.encrypt_key_handle(sk.clone(), &rp_id_hash)?
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut auth_data = self.generate_auth_data(&rp_id_hash, flags)?;
|
let mut auth_data = self.generate_auth_data(&rp_id_hash, flags)?;
|
||||||
@@ -622,10 +598,25 @@ where
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generates a different per-credential secret for each UV mode.
|
||||||
|
// The computation is deterministic, and private_key expected to be unique.
|
||||||
|
fn generate_cred_random(
|
||||||
|
&mut self,
|
||||||
|
private_key: &crypto::ecdsa::SecKey,
|
||||||
|
has_uv: bool,
|
||||||
|
) -> Result<[u8; 32], Ctap2StatusCode> {
|
||||||
|
let mut private_key_bytes = [0u8; 32];
|
||||||
|
private_key.to_bytes(&mut private_key_bytes);
|
||||||
|
let salt = crypto::sha256::Sha256::hash(&private_key_bytes);
|
||||||
|
// TODO(kaczmarczyck) KDF? hash salt together with rp_id_hash?
|
||||||
|
let key = self.persistent_store.cred_random_secret(has_uv)?;
|
||||||
|
Ok(hmac_256::<Sha256>(&key, &salt[..]))
|
||||||
|
}
|
||||||
|
|
||||||
// Processes the input of a get_assertion operation for a given credential
|
// Processes the input of a get_assertion operation for a given credential
|
||||||
// and returns the correct Get(Next)Assertion response.
|
// and returns the correct Get(Next)Assertion response.
|
||||||
fn assertion_response(
|
fn assertion_response(
|
||||||
&self,
|
&mut self,
|
||||||
credential: PublicKeyCredentialSource,
|
credential: PublicKeyCredentialSource,
|
||||||
assertion_input: AssertionInput,
|
assertion_input: AssertionInput,
|
||||||
number_of_credentials: Option<usize>,
|
number_of_credentials: Option<usize>,
|
||||||
@@ -634,13 +625,15 @@ where
|
|||||||
client_data_hash,
|
client_data_hash,
|
||||||
mut auth_data,
|
mut auth_data,
|
||||||
hmac_secret_input,
|
hmac_secret_input,
|
||||||
|
has_uv,
|
||||||
} = assertion_input;
|
} = assertion_input;
|
||||||
|
|
||||||
// Process extensions.
|
// Process extensions.
|
||||||
if let Some(hmac_secret_input) = hmac_secret_input {
|
if let Some(hmac_secret_input) = hmac_secret_input {
|
||||||
|
let cred_random = self.generate_cred_random(&credential.private_key, has_uv)?;
|
||||||
let encrypted_output = self
|
let encrypted_output = self
|
||||||
.pin_protocol_v1
|
.pin_protocol_v1
|
||||||
.process_hmac_secret(hmac_secret_input, &credential.cred_random)?;
|
.process_hmac_secret(hmac_secret_input, &cred_random)?;
|
||||||
let extensions_output = cbor_map! {
|
let extensions_output = cbor_map! {
|
||||||
"hmac-secret" => encrypted_output,
|
"hmac-secret" => encrypted_output,
|
||||||
};
|
};
|
||||||
@@ -807,6 +800,7 @@ where
|
|||||||
client_data_hash,
|
client_data_hash,
|
||||||
auth_data: self.generate_auth_data(&rp_id_hash, flags)?,
|
auth_data: self.generate_auth_data(&rp_id_hash, flags)?,
|
||||||
hmac_secret_input,
|
hmac_secret_input,
|
||||||
|
has_uv,
|
||||||
};
|
};
|
||||||
let number_of_credentials = if applicable_credentials.is_empty() {
|
let number_of_credentials = if applicable_credentials.is_empty() {
|
||||||
None
|
None
|
||||||
@@ -872,7 +866,7 @@ where
|
|||||||
max_credential_count_in_list: MAX_CREDENTIAL_COUNT_IN_LIST.map(|c| c as u64),
|
max_credential_count_in_list: MAX_CREDENTIAL_COUNT_IN_LIST.map(|c| c as u64),
|
||||||
// #TODO(106) update with version 2.1 of HMAC-secret
|
// #TODO(106) update with version 2.1 of HMAC-secret
|
||||||
#[cfg(feature = "with_ctap2_1")]
|
#[cfg(feature = "with_ctap2_1")]
|
||||||
max_credential_id_length: Some(CREDENTIAL_ID_BASE_SIZE as u64 + 32),
|
max_credential_id_length: Some(CREDENTIAL_ID_SIZE as u64),
|
||||||
#[cfg(feature = "with_ctap2_1")]
|
#[cfg(feature = "with_ctap2_1")]
|
||||||
transports: Some(vec![AuthenticatorTransport::Usb]),
|
transports: Some(vec![AuthenticatorTransport::Usb]),
|
||||||
#[cfg(feature = "with_ctap2_1")]
|
#[cfg(feature = "with_ctap2_1")]
|
||||||
@@ -1010,7 +1004,7 @@ mod test {
|
|||||||
#[cfg(feature = "with_ctap2_1")]
|
#[cfg(feature = "with_ctap2_1")]
|
||||||
expected_response.extend(
|
expected_response.extend(
|
||||||
[
|
[
|
||||||
0x08, 0x18, 0x90, 0x09, 0x81, 0x63, 0x75, 0x73, 0x62, 0x0A, 0x81, 0xA2, 0x63, 0x61,
|
0x08, 0x18, 0x70, 0x09, 0x81, 0x63, 0x75, 0x73, 0x62, 0x0A, 0x81, 0xA2, 0x63, 0x61,
|
||||||
0x6C, 0x67, 0x26, 0x64, 0x74, 0x79, 0x70, 0x65, 0x6A, 0x70, 0x75, 0x62, 0x6C, 0x69,
|
0x6C, 0x67, 0x26, 0x64, 0x74, 0x79, 0x70, 0x65, 0x6A, 0x70, 0x75, 0x62, 0x6C, 0x69,
|
||||||
0x63, 0x2D, 0x6B, 0x65, 0x79, 0x0D, 0x04,
|
0x63, 0x2D, 0x6B, 0x65, 0x79, 0x0D, 0x04,
|
||||||
]
|
]
|
||||||
@@ -1141,7 +1135,7 @@ mod test {
|
|||||||
];
|
];
|
||||||
expected_auth_data.push(INITIAL_SIGNATURE_COUNTER as u8);
|
expected_auth_data.push(INITIAL_SIGNATURE_COUNTER as u8);
|
||||||
expected_auth_data.extend(&ctap_state.persistent_store.aaguid().unwrap());
|
expected_auth_data.extend(&ctap_state.persistent_store.aaguid().unwrap());
|
||||||
expected_auth_data.extend(&[0x00, CREDENTIAL_ID_BASE_SIZE as u8]);
|
expected_auth_data.extend(&[0x00, CREDENTIAL_ID_SIZE as u8]);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
auth_data[0..expected_auth_data.len()],
|
auth_data[0..expected_auth_data.len()],
|
||||||
expected_auth_data[..]
|
expected_auth_data[..]
|
||||||
@@ -1186,7 +1180,6 @@ mod test {
|
|||||||
rp_id: String::from("example.com"),
|
rp_id: String::from("example.com"),
|
||||||
user_handle: vec![],
|
user_handle: vec![],
|
||||||
user_display_name: None,
|
user_display_name: None,
|
||||||
cred_random: None,
|
|
||||||
cred_protect_policy: None,
|
cred_protect_policy: None,
|
||||||
creation_order: 0,
|
creation_order: 0,
|
||||||
user_name: None,
|
user_name: None,
|
||||||
@@ -1291,7 +1284,7 @@ mod test {
|
|||||||
];
|
];
|
||||||
expected_auth_data.push(INITIAL_SIGNATURE_COUNTER as u8);
|
expected_auth_data.push(INITIAL_SIGNATURE_COUNTER as u8);
|
||||||
expected_auth_data.extend(&ctap_state.persistent_store.aaguid().unwrap());
|
expected_auth_data.extend(&ctap_state.persistent_store.aaguid().unwrap());
|
||||||
expected_auth_data.extend(&[0x00, CREDENTIAL_ID_MAX_SIZE as u8]);
|
expected_auth_data.extend(&[0x00, CREDENTIAL_ID_SIZE as u8]);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
auth_data[0..expected_auth_data.len()],
|
auth_data[0..expected_auth_data.len()],
|
||||||
expected_auth_data[..]
|
expected_auth_data[..]
|
||||||
@@ -1488,8 +1481,8 @@ mod test {
|
|||||||
let auth_data = make_credential_response.auth_data;
|
let auth_data = make_credential_response.auth_data;
|
||||||
let offset = 37 + ctap_state.persistent_store.aaguid().unwrap().len();
|
let offset = 37 + ctap_state.persistent_store.aaguid().unwrap().len();
|
||||||
assert_eq!(auth_data[offset], 0x00);
|
assert_eq!(auth_data[offset], 0x00);
|
||||||
assert_eq!(auth_data[offset + 1] as usize, CREDENTIAL_ID_MAX_SIZE);
|
assert_eq!(auth_data[offset + 1] as usize, CREDENTIAL_ID_SIZE);
|
||||||
auth_data[offset + 2..offset + 2 + CREDENTIAL_ID_MAX_SIZE].to_vec()
|
auth_data[offset + 2..offset + 2 + CREDENTIAL_ID_SIZE].to_vec()
|
||||||
}
|
}
|
||||||
_ => panic!("Invalid response type"),
|
_ => panic!("Invalid response type"),
|
||||||
};
|
};
|
||||||
@@ -1604,7 +1597,6 @@ mod test {
|
|||||||
rp_id: String::from("example.com"),
|
rp_id: String::from("example.com"),
|
||||||
user_handle: vec![0x1D],
|
user_handle: vec![0x1D],
|
||||||
user_display_name: None,
|
user_display_name: None,
|
||||||
cred_random: None,
|
|
||||||
cred_protect_policy: Some(
|
cred_protect_policy: Some(
|
||||||
CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIdList,
|
CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIdList,
|
||||||
),
|
),
|
||||||
@@ -1669,7 +1661,6 @@ mod test {
|
|||||||
rp_id: String::from("example.com"),
|
rp_id: String::from("example.com"),
|
||||||
user_handle: vec![0x1D],
|
user_handle: vec![0x1D],
|
||||||
user_display_name: None,
|
user_display_name: None,
|
||||||
cred_random: None,
|
|
||||||
cred_protect_policy: Some(CredentialProtectionPolicy::UserVerificationRequired),
|
cred_protect_policy: Some(CredentialProtectionPolicy::UserVerificationRequired),
|
||||||
creation_order: 0,
|
creation_order: 0,
|
||||||
user_name: None,
|
user_name: None,
|
||||||
@@ -1942,7 +1933,6 @@ mod test {
|
|||||||
rp_id: String::from("example.com"),
|
rp_id: String::from("example.com"),
|
||||||
user_handle: vec![],
|
user_handle: vec![],
|
||||||
user_display_name: None,
|
user_display_name: None,
|
||||||
cred_random: None,
|
|
||||||
cred_protect_policy: None,
|
cred_protect_policy: None,
|
||||||
creation_order: 0,
|
creation_order: 0,
|
||||||
user_name: None,
|
user_name: None,
|
||||||
@@ -2013,7 +2003,7 @@ mod test {
|
|||||||
// We are not testing the correctness of our SHA256 here, only if it is checked.
|
// We are not testing the correctness of our SHA256 here, only if it is checked.
|
||||||
let rp_id_hash = [0x55; 32];
|
let rp_id_hash = [0x55; 32];
|
||||||
let encrypted_id = ctap_state
|
let encrypted_id = ctap_state
|
||||||
.encrypt_key_handle(private_key.clone(), &rp_id_hash, None)
|
.encrypt_key_handle(private_key.clone(), &rp_id_hash)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let decrypted_source = ctap_state
|
let decrypted_source = ctap_state
|
||||||
.decrypt_credential_source(encrypted_id, &rp_id_hash)
|
.decrypt_credential_source(encrypted_id, &rp_id_hash)
|
||||||
@@ -2023,29 +2013,6 @@ mod test {
|
|||||||
assert_eq!(private_key, decrypted_source.private_key);
|
assert_eq!(private_key, decrypted_source.private_key);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_encrypt_decrypt_credential_with_cred_random() {
|
|
||||||
let mut rng = ThreadRng256 {};
|
|
||||||
let user_immediately_present = |_| Ok(());
|
|
||||||
let private_key = crypto::ecdsa::SecKey::gensk(&mut rng);
|
|
||||||
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
|
|
||||||
|
|
||||||
// Usually, the relying party ID or its hash is provided by the client.
|
|
||||||
// We are not testing the correctness of our SHA256 here, only if it is checked.
|
|
||||||
let rp_id_hash = [0x55; 32];
|
|
||||||
let cred_random = [0xC9; 32];
|
|
||||||
let encrypted_id = ctap_state
|
|
||||||
.encrypt_key_handle(private_key.clone(), &rp_id_hash, Some(&cred_random))
|
|
||||||
.unwrap();
|
|
||||||
let decrypted_source = ctap_state
|
|
||||||
.decrypt_credential_source(encrypted_id, &rp_id_hash)
|
|
||||||
.unwrap()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(private_key, decrypted_source.private_key);
|
|
||||||
assert_eq!(Some(cred_random.to_vec()), decrypted_source.cred_random);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_encrypt_decrypt_bad_hmac() {
|
fn test_encrypt_decrypt_bad_hmac() {
|
||||||
let mut rng = ThreadRng256 {};
|
let mut rng = ThreadRng256 {};
|
||||||
@@ -2056,30 +2023,7 @@ mod test {
|
|||||||
// Same as above.
|
// Same as above.
|
||||||
let rp_id_hash = [0x55; 32];
|
let rp_id_hash = [0x55; 32];
|
||||||
let encrypted_id = ctap_state
|
let encrypted_id = ctap_state
|
||||||
.encrypt_key_handle(private_key, &rp_id_hash, None)
|
.encrypt_key_handle(private_key, &rp_id_hash)
|
||||||
.unwrap();
|
|
||||||
for i in 0..encrypted_id.len() {
|
|
||||||
let mut modified_id = encrypted_id.clone();
|
|
||||||
modified_id[i] ^= 0x01;
|
|
||||||
assert!(ctap_state
|
|
||||||
.decrypt_credential_source(modified_id, &rp_id_hash)
|
|
||||||
.unwrap()
|
|
||||||
.is_none());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_encrypt_decrypt_bad_hmac_with_cred_random() {
|
|
||||||
let mut rng = ThreadRng256 {};
|
|
||||||
let user_immediately_present = |_| Ok(());
|
|
||||||
let private_key = crypto::ecdsa::SecKey::gensk(&mut rng);
|
|
||||||
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
|
|
||||||
|
|
||||||
// Same as above.
|
|
||||||
let rp_id_hash = [0x55; 32];
|
|
||||||
let cred_random = [0xC9; 32];
|
|
||||||
let encrypted_id = ctap_state
|
|
||||||
.encrypt_key_handle(private_key, &rp_id_hash, Some(&cred_random))
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
for i in 0..encrypted_id.len() {
|
for i in 0..encrypted_id.len() {
|
||||||
let mut modified_id = encrypted_id.clone();
|
let mut modified_id = encrypted_id.clone();
|
||||||
|
|||||||
@@ -56,23 +56,16 @@ fn verify_pin_auth(hmac_key: &[u8], hmac_contents: &[u8], pin_auth: &[u8]) -> bo
|
|||||||
fn encrypt_hmac_secret_output(
|
fn encrypt_hmac_secret_output(
|
||||||
shared_secret: &[u8; 32],
|
shared_secret: &[u8; 32],
|
||||||
salt_enc: &[u8],
|
salt_enc: &[u8],
|
||||||
cred_random: &[u8],
|
cred_random: &[u8; 32],
|
||||||
) -> Result<Vec<u8>, Ctap2StatusCode> {
|
) -> Result<Vec<u8>, Ctap2StatusCode> {
|
||||||
if salt_enc.len() != 32 && salt_enc.len() != 64 {
|
if salt_enc.len() != 32 && salt_enc.len() != 64 {
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION);
|
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION);
|
||||||
}
|
}
|
||||||
if cred_random.len() != 32 {
|
|
||||||
// We are strict here. We need at least 32 byte, but expect exactly 32.
|
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION);
|
|
||||||
}
|
|
||||||
let aes_enc_key = crypto::aes256::EncryptionKey::new(shared_secret);
|
let aes_enc_key = crypto::aes256::EncryptionKey::new(shared_secret);
|
||||||
let aes_dec_key = crypto::aes256::DecryptionKey::new(&aes_enc_key);
|
let aes_dec_key = crypto::aes256::DecryptionKey::new(&aes_enc_key);
|
||||||
// The specification specifically asks for a zero IV.
|
// The specification specifically asks for a zero IV.
|
||||||
let iv = [0u8; 16];
|
let iv = [0u8; 16];
|
||||||
|
|
||||||
let mut cred_random_secret = [0u8; 32];
|
|
||||||
cred_random_secret.copy_from_slice(cred_random);
|
|
||||||
|
|
||||||
// With the if clause restriction above, block_len can only be 2 or 4.
|
// With the if clause restriction above, block_len can only be 2 or 4.
|
||||||
let block_len = salt_enc.len() / 16;
|
let block_len = salt_enc.len() / 16;
|
||||||
let mut blocks = vec![[0u8; 16]; block_len];
|
let mut blocks = vec![[0u8; 16]; block_len];
|
||||||
@@ -84,7 +77,7 @@ fn encrypt_hmac_secret_output(
|
|||||||
let mut decrypted_salt1 = [0u8; 32];
|
let mut decrypted_salt1 = [0u8; 32];
|
||||||
decrypted_salt1[..16].copy_from_slice(&blocks[0]);
|
decrypted_salt1[..16].copy_from_slice(&blocks[0]);
|
||||||
decrypted_salt1[16..].copy_from_slice(&blocks[1]);
|
decrypted_salt1[16..].copy_from_slice(&blocks[1]);
|
||||||
let output1 = hmac_256::<Sha256>(&cred_random_secret, &decrypted_salt1[..]);
|
let output1 = hmac_256::<Sha256>(&cred_random[..], &decrypted_salt1[..]);
|
||||||
for i in 0..2 {
|
for i in 0..2 {
|
||||||
blocks[i].copy_from_slice(&output1[16 * i..16 * (i + 1)]);
|
blocks[i].copy_from_slice(&output1[16 * i..16 * (i + 1)]);
|
||||||
}
|
}
|
||||||
@@ -93,7 +86,7 @@ fn encrypt_hmac_secret_output(
|
|||||||
let mut decrypted_salt2 = [0u8; 32];
|
let mut decrypted_salt2 = [0u8; 32];
|
||||||
decrypted_salt2[..16].copy_from_slice(&blocks[2]);
|
decrypted_salt2[..16].copy_from_slice(&blocks[2]);
|
||||||
decrypted_salt2[16..].copy_from_slice(&blocks[3]);
|
decrypted_salt2[16..].copy_from_slice(&blocks[3]);
|
||||||
let output2 = hmac_256::<Sha256>(&cred_random_secret, &decrypted_salt2[..]);
|
let output2 = hmac_256::<Sha256>(&cred_random[..], &decrypted_salt2[..]);
|
||||||
for i in 0..2 {
|
for i in 0..2 {
|
||||||
blocks[i + 2].copy_from_slice(&output2[16 * i..16 * (i + 1)]);
|
blocks[i + 2].copy_from_slice(&output2[16 * i..16 * (i + 1)]);
|
||||||
}
|
}
|
||||||
@@ -588,7 +581,7 @@ impl PinProtocolV1 {
|
|||||||
pub fn process_hmac_secret(
|
pub fn process_hmac_secret(
|
||||||
&self,
|
&self,
|
||||||
hmac_secret_input: GetAssertionHmacSecretInput,
|
hmac_secret_input: GetAssertionHmacSecretInput,
|
||||||
cred_random: &Option<Vec<u8>>,
|
cred_random: &[u8; 32],
|
||||||
) -> Result<Vec<u8>, Ctap2StatusCode> {
|
) -> Result<Vec<u8>, Ctap2StatusCode> {
|
||||||
let GetAssertionHmacSecretInput {
|
let GetAssertionHmacSecretInput {
|
||||||
key_agreement,
|
key_agreement,
|
||||||
@@ -602,12 +595,7 @@ impl PinProtocolV1 {
|
|||||||
// Hard to tell what the correct error code here is.
|
// Hard to tell what the correct error code here is.
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION);
|
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION);
|
||||||
}
|
}
|
||||||
|
encrypt_hmac_secret_output(&shared_secret, &salt_enc[..], cred_random)
|
||||||
match cred_random {
|
|
||||||
Some(cr) => encrypt_hmac_secret_output(&shared_secret, &salt_enc[..], cr),
|
|
||||||
// This is the case if the credential was not created with HMAC-secret.
|
|
||||||
None => Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "with_ctap2_1")]
|
#[cfg(feature = "with_ctap2_1")]
|
||||||
@@ -1195,14 +1183,6 @@ mod test {
|
|||||||
let output = encrypt_hmac_secret_output(&shared_secret, &salt_enc, &cred_random);
|
let output = encrypt_hmac_secret_output(&shared_secret, &salt_enc, &cred_random);
|
||||||
assert_eq!(output.unwrap().len(), 64);
|
assert_eq!(output.unwrap().len(), 64);
|
||||||
|
|
||||||
let salt_enc = [0x5E; 32];
|
|
||||||
let cred_random = [0xC9; 33];
|
|
||||||
let output = encrypt_hmac_secret_output(&shared_secret, &salt_enc, &cred_random);
|
|
||||||
assert_eq!(
|
|
||||||
output,
|
|
||||||
Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION)
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut salt_enc = [0x00; 32];
|
let mut salt_enc = [0x00; 32];
|
||||||
let cred_random = [0xC9; 32];
|
let cred_random = [0xC9; 32];
|
||||||
|
|
||||||
|
|||||||
@@ -72,9 +72,10 @@ const AAGUID: usize = 7;
|
|||||||
const MIN_PIN_LENGTH: usize = 8;
|
const MIN_PIN_LENGTH: usize = 8;
|
||||||
#[cfg(feature = "with_ctap2_1")]
|
#[cfg(feature = "with_ctap2_1")]
|
||||||
const MIN_PIN_LENGTH_RP_IDS: usize = 9;
|
const MIN_PIN_LENGTH_RP_IDS: usize = 9;
|
||||||
|
const CRED_RANDOM_SECRET: usize = 10;
|
||||||
// Different NUM_TAGS depending on the CTAP version make the storage incompatible,
|
// Different NUM_TAGS depending on the CTAP version make the storage incompatible,
|
||||||
// so we use the maximum.
|
// so we use the maximum.
|
||||||
const NUM_TAGS: usize = 10;
|
const NUM_TAGS: usize = 11;
|
||||||
|
|
||||||
const MAX_PIN_RETRIES: u8 = 8;
|
const MAX_PIN_RETRIES: u8 = 8;
|
||||||
#[cfg(feature = "with_ctap2_1")]
|
#[cfg(feature = "with_ctap2_1")]
|
||||||
@@ -108,6 +109,7 @@ enum Key {
|
|||||||
MinPinLength,
|
MinPinLength,
|
||||||
#[cfg(feature = "with_ctap2_1")]
|
#[cfg(feature = "with_ctap2_1")]
|
||||||
MinPinLengthRpIds,
|
MinPinLengthRpIds,
|
||||||
|
CredRandomSecret,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct MasterKeys {
|
pub struct MasterKeys {
|
||||||
@@ -166,6 +168,7 @@ impl StoreConfig for Config {
|
|||||||
MIN_PIN_LENGTH => add(Key::MinPinLength),
|
MIN_PIN_LENGTH => add(Key::MinPinLength),
|
||||||
#[cfg(feature = "with_ctap2_1")]
|
#[cfg(feature = "with_ctap2_1")]
|
||||||
MIN_PIN_LENGTH_RP_IDS => add(Key::MinPinLengthRpIds),
|
MIN_PIN_LENGTH_RP_IDS => add(Key::MinPinLengthRpIds),
|
||||||
|
CRED_RANDOM_SECRET => add(Key::CredRandomSecret),
|
||||||
_ => debug_assert!(false),
|
_ => debug_assert!(false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -230,6 +233,22 @@ impl PersistentStore {
|
|||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.store.find_one(&Key::CredRandomSecret).is_none() {
|
||||||
|
let cred_random_with_uv = rng.gen_uniform_u8x32();
|
||||||
|
let cred_random_without_uv = rng.gen_uniform_u8x32();
|
||||||
|
let mut cred_random = Vec::with_capacity(64);
|
||||||
|
cred_random.extend_from_slice(&cred_random_without_uv);
|
||||||
|
cred_random.extend_from_slice(&cred_random_with_uv);
|
||||||
|
self.store
|
||||||
|
.insert(StoreEntry {
|
||||||
|
tag: CRED_RANDOM_SECRET,
|
||||||
|
data: &cred_random,
|
||||||
|
sensitive: true,
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
// TODO(jmichel): remove this when vendor command is in place
|
// TODO(jmichel): remove this when vendor command is in place
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
self.load_attestation_data_from_firmware();
|
self.load_attestation_data_from_firmware();
|
||||||
@@ -411,6 +430,15 @@ impl PersistentStore {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn cred_random_secret(&self, has_uv: bool) -> Result<[u8; 32], Ctap2StatusCode> {
|
||||||
|
let (_, entry) = self.store.find_one(&Key::CredRandomSecret).unwrap();
|
||||||
|
if entry.data.len() != 64 {
|
||||||
|
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
|
||||||
|
}
|
||||||
|
let offset = if has_uv { 32 } else { 0 };
|
||||||
|
Ok(*array_ref![entry.data, offset, 32])
|
||||||
|
}
|
||||||
|
|
||||||
pub fn pin_hash(&self) -> Result<Option<[u8; PIN_AUTH_LENGTH]>, Ctap2StatusCode> {
|
pub fn pin_hash(&self) -> Result<Option<[u8; PIN_AUTH_LENGTH]>, Ctap2StatusCode> {
|
||||||
let data = match self.store.find_one(&Key::PinHash) {
|
let data = match self.store.find_one(&Key::PinHash) {
|
||||||
None => return Ok(None),
|
None => return Ok(None),
|
||||||
@@ -721,7 +749,6 @@ mod test {
|
|||||||
rp_id: String::from(rp_id),
|
rp_id: String::from(rp_id),
|
||||||
user_handle,
|
user_handle,
|
||||||
user_display_name: None,
|
user_display_name: None,
|
||||||
cred_random: None,
|
|
||||||
cred_protect_policy: None,
|
cred_protect_policy: None,
|
||||||
creation_order: 0,
|
creation_order: 0,
|
||||||
user_name: None,
|
user_name: None,
|
||||||
@@ -900,7 +927,6 @@ mod test {
|
|||||||
rp_id: String::from("example.com"),
|
rp_id: String::from("example.com"),
|
||||||
user_handle: vec![0x00],
|
user_handle: vec![0x00],
|
||||||
user_display_name: None,
|
user_display_name: None,
|
||||||
cred_random: None,
|
|
||||||
cred_protect_policy: Some(
|
cred_protect_policy: Some(
|
||||||
CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIdList,
|
CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIdList,
|
||||||
),
|
),
|
||||||
@@ -946,7 +972,6 @@ mod test {
|
|||||||
rp_id: String::from("example.com"),
|
rp_id: String::from("example.com"),
|
||||||
user_handle: vec![0x00],
|
user_handle: vec![0x00],
|
||||||
user_display_name: None,
|
user_display_name: None,
|
||||||
cred_random: None,
|
|
||||||
cred_protect_policy: None,
|
cred_protect_policy: None,
|
||||||
creation_order: 0,
|
creation_order: 0,
|
||||||
user_name: None,
|
user_name: None,
|
||||||
@@ -968,7 +993,6 @@ mod test {
|
|||||||
rp_id: String::from("example.com"),
|
rp_id: String::from("example.com"),
|
||||||
user_handle: vec![0x00],
|
user_handle: vec![0x00],
|
||||||
user_display_name: None,
|
user_display_name: None,
|
||||||
cred_random: None,
|
|
||||||
cred_protect_policy: Some(CredentialProtectionPolicy::UserVerificationRequired),
|
cred_protect_policy: Some(CredentialProtectionPolicy::UserVerificationRequired),
|
||||||
creation_order: 0,
|
creation_order: 0,
|
||||||
user_name: None,
|
user_name: None,
|
||||||
@@ -987,7 +1011,7 @@ mod test {
|
|||||||
let mut rng = ThreadRng256 {};
|
let mut rng = ThreadRng256 {};
|
||||||
let mut persistent_store = PersistentStore::new(&mut rng);
|
let mut persistent_store = PersistentStore::new(&mut rng);
|
||||||
|
|
||||||
// Master keys stay the same between resets.
|
// Master keys stay the same between calls.
|
||||||
let master_keys_1 = persistent_store.master_keys().unwrap();
|
let master_keys_1 = persistent_store.master_keys().unwrap();
|
||||||
let master_keys_2 = persistent_store.master_keys().unwrap();
|
let master_keys_2 = persistent_store.master_keys().unwrap();
|
||||||
assert_eq!(master_keys_2.encryption, master_keys_1.encryption);
|
assert_eq!(master_keys_2.encryption, master_keys_1.encryption);
|
||||||
@@ -1003,6 +1027,28 @@ mod test {
|
|||||||
assert!(master_keys_3.hmac != master_hmac_key.as_slice());
|
assert!(master_keys_3.hmac != master_hmac_key.as_slice());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cred_random_secret() {
|
||||||
|
let mut rng = ThreadRng256 {};
|
||||||
|
let mut persistent_store = PersistentStore::new(&mut rng);
|
||||||
|
|
||||||
|
// Master keys stay the same between calls.
|
||||||
|
let cred_random_with_uv_1 = persistent_store.cred_random_secret(true).unwrap();
|
||||||
|
let cred_random_without_uv_1 = persistent_store.cred_random_secret(false).unwrap();
|
||||||
|
let cred_random_with_uv_2 = persistent_store.cred_random_secret(true).unwrap();
|
||||||
|
let cred_random_without_uv_2 = persistent_store.cred_random_secret(false).unwrap();
|
||||||
|
assert_eq!(cred_random_with_uv_1, cred_random_with_uv_2);
|
||||||
|
assert_eq!(cred_random_without_uv_1, cred_random_without_uv_2);
|
||||||
|
|
||||||
|
// Master keys change after reset. This test may fail if the random generator produces the
|
||||||
|
// same keys.
|
||||||
|
persistent_store.reset(&mut rng).unwrap();
|
||||||
|
let cred_random_with_uv_3 = persistent_store.cred_random_secret(true).unwrap();
|
||||||
|
let cred_random_without_uv_3 = persistent_store.cred_random_secret(false).unwrap();
|
||||||
|
assert!(cred_random_with_uv_1 != cred_random_with_uv_3);
|
||||||
|
assert!(cred_random_without_uv_1 != cred_random_without_uv_3);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_pin_hash() {
|
fn test_pin_hash() {
|
||||||
let mut rng = ThreadRng256 {};
|
let mut rng = ThreadRng256 {};
|
||||||
@@ -1170,7 +1216,6 @@ mod test {
|
|||||||
rp_id: String::from("example.com"),
|
rp_id: String::from("example.com"),
|
||||||
user_handle: vec![0x00],
|
user_handle: vec![0x00],
|
||||||
user_display_name: None,
|
user_display_name: None,
|
||||||
cred_random: None,
|
|
||||||
cred_protect_policy: None,
|
cred_protect_policy: None,
|
||||||
creation_order: 0,
|
creation_order: 0,
|
||||||
user_name: None,
|
user_name: None,
|
||||||
|
|||||||
Reference in New Issue
Block a user