Merge branch 'master' into empty-auth-up
This commit is contained in:
@@ -288,7 +288,7 @@ impl Ctap1Command {
|
||||
let sk = crypto::ecdsa::SecKey::gensk(ctap_state.rng);
|
||||
let pk = sk.genpk();
|
||||
let key_handle = ctap_state
|
||||
.encrypt_key_handle(sk, &application)
|
||||
.encrypt_key_handle(sk, &application, None)
|
||||
.map_err(|_| Ctap1StatusCode::SW_VENDOR_KEY_HANDLE_TOO_LONG)?;
|
||||
if key_handle.len() > 0xFF {
|
||||
// This is just being defensive with unreachable code.
|
||||
@@ -373,7 +373,7 @@ impl Ctap1Command {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::{ENCRYPTED_CREDENTIAL_ID_SIZE, USE_SIGNATURE_COUNTER};
|
||||
use super::super::{CREDENTIAL_ID_BASE_SIZE, USE_SIGNATURE_COUNTER};
|
||||
use super::*;
|
||||
use crypto::rng256::ThreadRng256;
|
||||
use crypto::Hash256;
|
||||
@@ -413,12 +413,12 @@ mod test {
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
65 + ENCRYPTED_CREDENTIAL_ID_SIZE as u8,
|
||||
65 + CREDENTIAL_ID_BASE_SIZE as u8,
|
||||
];
|
||||
let challenge = [0x0C; 32];
|
||||
message.extend(&challenge);
|
||||
message.extend(application);
|
||||
message.push(ENCRYPTED_CREDENTIAL_ID_SIZE as u8);
|
||||
message.push(CREDENTIAL_ID_BASE_SIZE as u8);
|
||||
message.extend(key_handle);
|
||||
message
|
||||
}
|
||||
@@ -437,15 +437,15 @@ mod test {
|
||||
Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE).unwrap();
|
||||
|
||||
assert_eq!(response[0], Ctap1Command::LEGACY_BYTE);
|
||||
assert_eq!(response[66], ENCRYPTED_CREDENTIAL_ID_SIZE as u8);
|
||||
assert_eq!(response[66], CREDENTIAL_ID_BASE_SIZE as u8);
|
||||
assert!(ctap_state
|
||||
.decrypt_credential_source(
|
||||
response[67..67 + ENCRYPTED_CREDENTIAL_ID_SIZE].to_vec(),
|
||||
response[67..67 + CREDENTIAL_ID_BASE_SIZE].to_vec(),
|
||||
&application
|
||||
)
|
||||
.unwrap()
|
||||
.is_some());
|
||||
const CERT_START: usize = 67 + ENCRYPTED_CREDENTIAL_ID_SIZE;
|
||||
const CERT_START: usize = 67 + CREDENTIAL_ID_BASE_SIZE;
|
||||
assert_eq!(
|
||||
&response[CERT_START..CERT_START + ATTESTATION_CERTIFICATE.len()],
|
||||
&ATTESTATION_CERTIFICATE[..]
|
||||
@@ -494,7 +494,9 @@ mod test {
|
||||
|
||||
let rp_id = "example.com";
|
||||
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
||||
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
|
||||
let key_handle = ctap_state
|
||||
.encrypt_key_handle(sk, &application, None)
|
||||
.unwrap();
|
||||
let message = create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
||||
|
||||
let response = Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE);
|
||||
@@ -510,7 +512,9 @@ mod test {
|
||||
|
||||
let rp_id = "example.com";
|
||||
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
||||
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
|
||||
let key_handle = ctap_state
|
||||
.encrypt_key_handle(sk, &application, None)
|
||||
.unwrap();
|
||||
let application = [0x55; 32];
|
||||
let message = create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
||||
|
||||
@@ -527,7 +531,9 @@ mod test {
|
||||
|
||||
let rp_id = "example.com";
|
||||
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
||||
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
|
||||
let key_handle = ctap_state
|
||||
.encrypt_key_handle(sk, &application, None)
|
||||
.unwrap();
|
||||
let mut message =
|
||||
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
||||
|
||||
@@ -551,7 +557,9 @@ mod test {
|
||||
|
||||
let rp_id = "example.com";
|
||||
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
||||
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
|
||||
let key_handle = ctap_state
|
||||
.encrypt_key_handle(sk, &application, None)
|
||||
.unwrap();
|
||||
let mut message =
|
||||
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
||||
message[0] = 0xEE;
|
||||
@@ -569,7 +577,9 @@ mod test {
|
||||
|
||||
let rp_id = "example.com";
|
||||
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
||||
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
|
||||
let key_handle = ctap_state
|
||||
.encrypt_key_handle(sk, &application, None)
|
||||
.unwrap();
|
||||
let mut message =
|
||||
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
||||
message[1] = 0xEE;
|
||||
@@ -587,7 +597,9 @@ mod test {
|
||||
|
||||
let rp_id = "example.com";
|
||||
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
||||
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
|
||||
let key_handle = ctap_state
|
||||
.encrypt_key_handle(sk, &application, None)
|
||||
.unwrap();
|
||||
let mut message =
|
||||
create_authenticate_message(&application, Ctap1Flags::CheckOnly, &key_handle);
|
||||
message[2] = 0xEE;
|
||||
@@ -605,7 +617,9 @@ mod test {
|
||||
|
||||
let rp_id = "example.com";
|
||||
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
||||
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
|
||||
let key_handle = ctap_state
|
||||
.encrypt_key_handle(sk, &application, None)
|
||||
.unwrap();
|
||||
let message =
|
||||
create_authenticate_message(&application, Ctap1Flags::EnforceUpAndSign, &key_handle);
|
||||
|
||||
@@ -630,7 +644,9 @@ mod test {
|
||||
|
||||
let rp_id = "example.com";
|
||||
let application = crypto::sha256::Sha256::hash(rp_id.as_bytes());
|
||||
let key_handle = ctap_state.encrypt_key_handle(sk, &application).unwrap();
|
||||
let key_handle = ctap_state
|
||||
.encrypt_key_handle(sk, &application, None)
|
||||
.unwrap();
|
||||
let message = create_authenticate_message(
|
||||
&application,
|
||||
Ctap1Flags::DontEnforceUpAndSign,
|
||||
@@ -650,7 +666,7 @@ mod test {
|
||||
#[test]
|
||||
fn test_process_authenticate_bad_key_handle() {
|
||||
let application = [0x0A; 32];
|
||||
let key_handle = vec![0x00; ENCRYPTED_CREDENTIAL_ID_SIZE];
|
||||
let key_handle = vec![0x00; CREDENTIAL_ID_BASE_SIZE];
|
||||
let message =
|
||||
create_authenticate_message(&application, Ctap1Flags::EnforceUpAndSign, &key_handle);
|
||||
|
||||
@@ -667,7 +683,7 @@ mod test {
|
||||
#[test]
|
||||
fn test_process_authenticate_without_up() {
|
||||
let application = [0x0A; 32];
|
||||
let key_handle = vec![0x00; ENCRYPTED_CREDENTIAL_ID_SIZE];
|
||||
let key_handle = vec![0x00; CREDENTIAL_ID_BASE_SIZE];
|
||||
let message =
|
||||
create_authenticate_message(&application, Ctap1Flags::EnforceUpAndSign, &key_handle);
|
||||
|
||||
|
||||
@@ -361,10 +361,8 @@ impl TryFrom<cbor::Value> for MakeCredentialOptions {
|
||||
Some(options_entry) => extract_bool(options_entry)?,
|
||||
None => false,
|
||||
};
|
||||
if let Some(options_entry) = up {
|
||||
if !extract_bool(options_entry)? {
|
||||
return Err(Ctap2StatusCode::CTAP2_ERR_INVALID_OPTION);
|
||||
}
|
||||
if up.is_some() {
|
||||
return Err(Ctap2StatusCode::CTAP2_ERR_INVALID_OPTION);
|
||||
}
|
||||
let uv = match uv {
|
||||
Some(options_entry) => extract_bool(options_entry)?,
|
||||
|
||||
@@ -227,44 +227,35 @@ impl CtapHid {
|
||||
}
|
||||
// CTAP specification (version 20190130) section 8.1.9.1.3
|
||||
CtapHid::COMMAND_INIT => {
|
||||
if cid == CtapHid::CHANNEL_BROADCAST {
|
||||
if message.payload.len() != 8 {
|
||||
return CtapHid::error_message(cid, CtapHid::ERR_INVALID_LEN);
|
||||
}
|
||||
if message.payload.len() != 8 {
|
||||
return CtapHid::error_message(cid, CtapHid::ERR_INVALID_LEN);
|
||||
}
|
||||
|
||||
let new_cid = if cid == CtapHid::CHANNEL_BROADCAST {
|
||||
// TODO: Prevent allocating 2^32 channels.
|
||||
self.allocated_cids += 1;
|
||||
let allocated_cid = (self.allocated_cids as u32).to_ne_bytes();
|
||||
|
||||
let mut payload = vec![0; 17];
|
||||
payload[..8].copy_from_slice(&message.payload);
|
||||
payload[8..12].copy_from_slice(&allocated_cid);
|
||||
payload[12] = CtapHid::PROTOCOL_VERSION;
|
||||
payload[13] = CtapHid::DEVICE_VERSION_MAJOR;
|
||||
payload[14] = CtapHid::DEVICE_VERSION_MINOR;
|
||||
payload[15] = CtapHid::DEVICE_VERSION_BUILD;
|
||||
payload[16] = CtapHid::CAPABILITIES;
|
||||
|
||||
// This unwrap is safe because the payload length is 17 <= 7609 bytes.
|
||||
CtapHid::split_message(Message {
|
||||
cid,
|
||||
cmd: CtapHid::COMMAND_INIT,
|
||||
payload,
|
||||
})
|
||||
.unwrap()
|
||||
(self.allocated_cids as u32).to_ne_bytes()
|
||||
} else {
|
||||
// Sync the channel and discard the current transaction.
|
||||
// TODO: The specification (version 20190130) wording isn't clear about
|
||||
// the payload format in this case.
|
||||
//
|
||||
// This unwrap is safe because the payload length is 0 <= 7609 bytes.
|
||||
CtapHid::split_message(Message {
|
||||
cid,
|
||||
cmd: CtapHid::COMMAND_INIT,
|
||||
payload: vec![],
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
cid
|
||||
};
|
||||
|
||||
let mut payload = vec![0; 17];
|
||||
payload[..8].copy_from_slice(&message.payload);
|
||||
payload[8..12].copy_from_slice(&new_cid);
|
||||
payload[12] = CtapHid::PROTOCOL_VERSION;
|
||||
payload[13] = CtapHid::DEVICE_VERSION_MAJOR;
|
||||
payload[14] = CtapHid::DEVICE_VERSION_MINOR;
|
||||
payload[15] = CtapHid::DEVICE_VERSION_BUILD;
|
||||
payload[16] = CtapHid::CAPABILITIES;
|
||||
|
||||
// This unwrap is safe because the payload length is 17 <= 7609 bytes.
|
||||
CtapHid::split_message(Message {
|
||||
cid,
|
||||
cmd: CtapHid::COMMAND_INIT,
|
||||
payload,
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
// CTAP specification (version 20190130) section 8.1.9.1.4
|
||||
CtapHid::COMMAND_PING => {
|
||||
@@ -307,7 +298,9 @@ impl CtapHid {
|
||||
HidPacketIterator::none()
|
||||
}
|
||||
Err((cid, error)) => {
|
||||
if !self.is_allocated_channel(cid) {
|
||||
if !self.is_allocated_channel(cid)
|
||||
&& error != receive::Error::UnexpectedContinuation
|
||||
{
|
||||
CtapHid::error_message(cid, CtapHid::ERR_INVALID_CHANNEL)
|
||||
} else {
|
||||
match error {
|
||||
@@ -523,6 +516,27 @@ mod test {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_spurious_continuation_packet() {
|
||||
let mut rng = ThreadRng256 {};
|
||||
let user_immediately_present = |_| Ok(());
|
||||
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
|
||||
let mut ctap_hid = CtapHid::new();
|
||||
|
||||
let mut packet = [0x00; 64];
|
||||
packet[0..7].copy_from_slice(&[0xC1, 0xC1, 0xC1, 0xC1, 0x00, 0x51, 0x51]);
|
||||
let mut assembler_reply = MessageAssembler::new();
|
||||
for pkt_reply in ctap_hid.process_hid_packet(&packet, DUMMY_CLOCK_VALUE, &mut ctap_state) {
|
||||
// Continuation packets are silently ignored.
|
||||
assert_eq!(
|
||||
assembler_reply
|
||||
.parse_packet(&pkt_reply, DUMMY_TIMESTAMP)
|
||||
.unwrap(),
|
||||
None
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_command_init() {
|
||||
let mut rng = ThreadRng256 {};
|
||||
@@ -568,6 +582,66 @@ mod test {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_command_init_for_sync() {
|
||||
let mut rng = ThreadRng256 {};
|
||||
let user_immediately_present = |_| Ok(());
|
||||
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
|
||||
let mut ctap_hid = CtapHid::new();
|
||||
let cid = cid_from_init(&mut ctap_hid, &mut ctap_state);
|
||||
|
||||
// Ping packet with a length longer than one packet.
|
||||
let mut packet1 = [0x51; 64];
|
||||
packet1[..4].copy_from_slice(&cid);
|
||||
packet1[4..7].copy_from_slice(&[0x81, 0x02, 0x00]);
|
||||
// Init packet on the same channel.
|
||||
let mut packet2 = [0x00; 64];
|
||||
packet2[..4].copy_from_slice(&cid);
|
||||
packet2[4..15].copy_from_slice(&[
|
||||
0x86, 0x00, 0x08, 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0,
|
||||
]);
|
||||
let mut result = Vec::new();
|
||||
let mut assembler_reply = MessageAssembler::new();
|
||||
for pkt_request in &[packet1, packet2] {
|
||||
for pkt_reply in
|
||||
ctap_hid.process_hid_packet(&pkt_request, DUMMY_CLOCK_VALUE, &mut ctap_state)
|
||||
{
|
||||
if let Some(message) = assembler_reply
|
||||
.parse_packet(&pkt_reply, DUMMY_TIMESTAMP)
|
||||
.unwrap()
|
||||
{
|
||||
result.push(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
assert_eq!(
|
||||
result,
|
||||
vec![Message {
|
||||
cid,
|
||||
cmd: CtapHid::COMMAND_INIT,
|
||||
payload: vec![
|
||||
0x12, // Nonce
|
||||
0x34,
|
||||
0x56,
|
||||
0x78,
|
||||
0x9A,
|
||||
0xBC,
|
||||
0xDE,
|
||||
0xF0,
|
||||
cid[0], // Allocated CID
|
||||
cid[1],
|
||||
cid[2],
|
||||
cid[3],
|
||||
0x02, // Protocol version
|
||||
0x00, // Device version
|
||||
0x00,
|
||||
0x00,
|
||||
CtapHid::CAPABILITIES
|
||||
]
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_command_ping() {
|
||||
let mut rng = ThreadRng256 {};
|
||||
|
||||
@@ -586,5 +586,33 @@ mod test {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_init_sync() {
|
||||
let mut assembler = MessageAssembler::new();
|
||||
// Ping packet with a length longer than one packet.
|
||||
assert_eq!(
|
||||
assembler.parse_packet(
|
||||
&byte_extend(&[0x12, 0x34, 0x56, 0x78, 0x81, 0x02, 0x00], 0x51),
|
||||
DUMMY_TIMESTAMP
|
||||
),
|
||||
Ok(None)
|
||||
);
|
||||
// Init packet on the same channel.
|
||||
assert_eq!(
|
||||
assembler.parse_packet(
|
||||
&zero_extend(&[
|
||||
0x12, 0x34, 0x56, 0x78, 0x86, 0x00, 0x08, 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC,
|
||||
0xDE, 0xF0
|
||||
]),
|
||||
DUMMY_TIMESTAMP
|
||||
),
|
||||
Ok(Some(Message {
|
||||
cid: [0x12, 0x34, 0x56, 0x78],
|
||||
cmd: 0x06,
|
||||
payload: vec![0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0]
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: more tests
|
||||
}
|
||||
|
||||
233
src/ctap/mod.rs
233
src/ctap/mod.rs
@@ -83,8 +83,10 @@ const USE_SIGNATURE_COUNTER: bool = true;
|
||||
// - 16 byte initialization vector for AES-256,
|
||||
// - 32 byte ECDSA private key for the credential,
|
||||
// - 32 byte relying party ID hashed with SHA256,
|
||||
// - (optional) 32 byte for HMAC-secret,
|
||||
// - 32 byte HMAC-SHA256 over everything else.
|
||||
pub const ENCRYPTED_CREDENTIAL_ID_SIZE: usize = 112;
|
||||
pub const CREDENTIAL_ID_BASE_SIZE: usize = 112;
|
||||
pub const CREDENTIAL_ID_MAX_SIZE: usize = CREDENTIAL_ID_BASE_SIZE + 32;
|
||||
// Set this bit when checking user presence.
|
||||
const UP_FLAG: u8 = 0x01;
|
||||
// Set this bit when checking user verification.
|
||||
@@ -195,6 +197,7 @@ where
|
||||
&mut self,
|
||||
private_key: crypto::ecdsa::SecKey,
|
||||
application: &[u8; 32],
|
||||
cred_random: Option<&[u8; 32]>,
|
||||
) -> Result<Vec<u8>, Ctap2StatusCode> {
|
||||
let master_keys = self.persistent_store.master_keys()?;
|
||||
let aes_enc_key = crypto::aes256::EncryptionKey::new(&master_keys.encryption);
|
||||
@@ -203,14 +206,19 @@ where
|
||||
let mut iv = [0; 16];
|
||||
iv.copy_from_slice(&self.rng.gen_uniform_u8x32()[..16]);
|
||||
|
||||
let mut blocks = [[0u8; 16]; 4];
|
||||
let block_len = if cred_random.is_some() { 6 } else { 4 };
|
||||
let mut blocks = vec![[0u8; 16]; block_len];
|
||||
blocks[0].copy_from_slice(&sk_bytes[..16]);
|
||||
blocks[1].copy_from_slice(&sk_bytes[16..]);
|
||||
blocks[2].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);
|
||||
|
||||
let mut encrypted_id = Vec::with_capacity(ENCRYPTED_CREDENTIAL_ID_SIZE);
|
||||
let mut encrypted_id = Vec::with_capacity(16 * (block_len + 3));
|
||||
encrypted_id.extend(&iv);
|
||||
for b in &blocks {
|
||||
encrypted_id.extend(b);
|
||||
@@ -228,11 +236,13 @@ where
|
||||
credential_id: Vec<u8>,
|
||||
rp_id_hash: &[u8],
|
||||
) -> Result<Option<PublicKeyCredentialSource>, Ctap2StatusCode> {
|
||||
if credential_id.len() != ENCRYPTED_CREDENTIAL_ID_SIZE {
|
||||
return Ok(None);
|
||||
}
|
||||
let has_cred_random = match credential_id.len() {
|
||||
CREDENTIAL_ID_BASE_SIZE => false,
|
||||
CREDENTIAL_ID_MAX_SIZE => true,
|
||||
_ => return Ok(None),
|
||||
};
|
||||
let master_keys = self.persistent_store.master_keys()?;
|
||||
let payload_size = ENCRYPTED_CREDENTIAL_ID_SIZE - 32;
|
||||
let payload_size = credential_id.len() - 32;
|
||||
if !verify_hmac_256::<Sha256>(
|
||||
&master_keys.hmac,
|
||||
&credential_id[..payload_size],
|
||||
@@ -244,8 +254,9 @@ where
|
||||
let aes_dec_key = crypto::aes256::DecryptionKey::new(&aes_enc_key);
|
||||
let mut iv = [0; 16];
|
||||
iv.copy_from_slice(&credential_id[..16]);
|
||||
let mut blocks = [[0u8; 16]; 4];
|
||||
for i in 0..4 {
|
||||
let block_len = if has_cred_random { 6 } else { 4 };
|
||||
let mut blocks = vec![[0u8; 16]; block_len];
|
||||
for i in 0..block_len {
|
||||
blocks[i].copy_from_slice(&credential_id[16 * (i + 1)..16 * (i + 2)]);
|
||||
}
|
||||
|
||||
@@ -256,6 +267,14 @@ where
|
||||
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[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 {
|
||||
return Ok(None);
|
||||
@@ -269,7 +288,7 @@ where
|
||||
rp_id: String::from(""),
|
||||
user_handle: vec![],
|
||||
other_ui: None,
|
||||
cred_random: None,
|
||||
cred_random,
|
||||
cred_protect_policy: None,
|
||||
}))
|
||||
}
|
||||
@@ -381,11 +400,7 @@ where
|
||||
};
|
||||
|
||||
let cred_random = if use_hmac_extension {
|
||||
if !options.rk {
|
||||
// The extension is actually supported, but we need resident keys.
|
||||
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION);
|
||||
}
|
||||
Some(self.rng.gen_uniform_u8x32().to_vec())
|
||||
Some(self.rng.gen_uniform_u8x32())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -393,12 +408,16 @@ where
|
||||
let has_extension_output = use_hmac_extension || cred_protect_policy.is_some();
|
||||
|
||||
let rp_id = rp.rp_id;
|
||||
let rp_id_hash = Sha256::hash(rp_id.as_bytes());
|
||||
if let Some(exclude_list) = exclude_list {
|
||||
for cred_desc in exclude_list {
|
||||
if self
|
||||
.persistent_store
|
||||
.find_credential(&rp_id, &cred_desc.key_id, pin_uv_auth_param.is_none())?
|
||||
.is_some()
|
||||
|| self
|
||||
.decrypt_credential_source(cred_desc.key_id, &rp_id_hash)?
|
||||
.is_some()
|
||||
{
|
||||
// Perform this check, so bad actors can't brute force exclude_list
|
||||
// without user interaction.
|
||||
@@ -447,7 +466,6 @@ where
|
||||
let sk = crypto::ecdsa::SecKey::gensk(self.rng);
|
||||
let pk = sk.genpk();
|
||||
|
||||
let rp_id_hash = Sha256::hash(rp_id.as_bytes());
|
||||
let credential_id = if options.rk {
|
||||
let random_id = self.rng.gen_uniform_u8x32().to_vec();
|
||||
let credential_source = PublicKeyCredentialSource {
|
||||
@@ -461,13 +479,13 @@ where
|
||||
other_ui: user
|
||||
.user_display_name
|
||||
.map(|s| truncate_to_char_boundary(&s, 64).to_string()),
|
||||
cred_random,
|
||||
cred_random: cred_random.map(|c| c.to_vec()),
|
||||
cred_protect_policy,
|
||||
};
|
||||
self.persistent_store.store_credential(credential_source)?;
|
||||
random_id
|
||||
} else {
|
||||
self.encrypt_key_handle(sk.clone(), &rp_id_hash)?
|
||||
self.encrypt_key_handle(sk.clone(), &rp_id_hash, cred_random.as_ref())?
|
||||
};
|
||||
|
||||
let mut auth_data = self.generate_auth_data(&rp_id_hash, flags)?;
|
||||
@@ -727,10 +745,9 @@ where
|
||||
]),
|
||||
#[cfg(feature = "with_ctap2_1")]
|
||||
max_credential_count_in_list: MAX_CREDENTIAL_COUNT_IN_LIST.map(|c| c as u64),
|
||||
// You can use ENCRYPTED_CREDENTIAL_ID_SIZE here, but if your
|
||||
// browser passes that value, it might be used to fingerprint.
|
||||
// #TODO(106) update with version 2.1 of HMAC-secret
|
||||
#[cfg(feature = "with_ctap2_1")]
|
||||
max_credential_id_length: None,
|
||||
max_credential_id_length: Some(CREDENTIAL_ID_BASE_SIZE as u64 + 32),
|
||||
#[cfg(feature = "with_ctap2_1")]
|
||||
transports: Some(vec![AuthenticatorTransport::Usb]),
|
||||
#[cfg(feature = "with_ctap2_1")]
|
||||
@@ -828,7 +845,7 @@ mod test {
|
||||
let info_reponse = ctap_state.process_command(&[0x04], DUMMY_CHANNEL_ID);
|
||||
|
||||
#[cfg(feature = "with_ctap2_1")]
|
||||
let mut expected_response = vec![0x00, 0xA9, 0x01];
|
||||
let mut expected_response = vec![0x00, 0xAA, 0x01];
|
||||
#[cfg(not(feature = "with_ctap2_1"))]
|
||||
let mut expected_response = vec![0x00, 0xA6, 0x01];
|
||||
// The difference here is a longer array of supported versions.
|
||||
@@ -863,9 +880,9 @@ mod test {
|
||||
#[cfg(feature = "with_ctap2_1")]
|
||||
expected_response.extend(
|
||||
[
|
||||
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, 0x63, 0x2D, 0x6B,
|
||||
0x65, 0x79, 0x0D, 0x04,
|
||||
0x08, 0x18, 0x90, 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,
|
||||
0x63, 0x2D, 0x6B, 0x65, 0x79, 0x0D, 0x04,
|
||||
]
|
||||
.iter(),
|
||||
);
|
||||
@@ -992,7 +1009,7 @@ mod test {
|
||||
0x12, 0x55, 0x86, 0xCE, 0x19, 0x47, 0x41, 0x00, 0x00, 0x00, 0x00,
|
||||
];
|
||||
expected_auth_data.extend(&ctap_state.persistent_store.aaguid().unwrap());
|
||||
expected_auth_data.extend(&[0x00, ENCRYPTED_CREDENTIAL_ID_SIZE as u8]);
|
||||
expected_auth_data.extend(&[0x00, CREDENTIAL_ID_BASE_SIZE as u8]);
|
||||
assert_eq!(
|
||||
auth_data[0..expected_auth_data.len()],
|
||||
expected_auth_data[..]
|
||||
@@ -1113,6 +1130,56 @@ mod test {
|
||||
let user_immediately_present = |_| Ok(());
|
||||
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
|
||||
|
||||
let extensions = Some(MakeCredentialExtensions {
|
||||
hmac_secret: true,
|
||||
cred_protect: None,
|
||||
});
|
||||
let mut make_credential_params = create_minimal_make_credential_parameters();
|
||||
make_credential_params.options.rk = false;
|
||||
make_credential_params.extensions = extensions;
|
||||
let make_credential_response =
|
||||
ctap_state.process_make_credential(make_credential_params, DUMMY_CHANNEL_ID);
|
||||
|
||||
match make_credential_response.unwrap() {
|
||||
ResponseData::AuthenticatorMakeCredential(make_credential_response) => {
|
||||
let AuthenticatorMakeCredentialResponse {
|
||||
fmt,
|
||||
auth_data,
|
||||
att_stmt,
|
||||
} = make_credential_response;
|
||||
// The expected response is split to only assert the non-random parts.
|
||||
assert_eq!(fmt, "packed");
|
||||
let mut expected_auth_data = vec![
|
||||
0xA3, 0x79, 0xA6, 0xF6, 0xEE, 0xAF, 0xB9, 0xA5, 0x5E, 0x37, 0x8C, 0x11, 0x80,
|
||||
0x34, 0xE2, 0x75, 0x1E, 0x68, 0x2F, 0xAB, 0x9F, 0x2D, 0x30, 0xAB, 0x13, 0xD2,
|
||||
0x12, 0x55, 0x86, 0xCE, 0x19, 0x47, 0xC1, 0x00, 0x00, 0x00, 0x00,
|
||||
];
|
||||
expected_auth_data.extend(&ctap_state.persistent_store.aaguid().unwrap());
|
||||
expected_auth_data.extend(&[0x00, CREDENTIAL_ID_MAX_SIZE as u8]);
|
||||
assert_eq!(
|
||||
auth_data[0..expected_auth_data.len()],
|
||||
expected_auth_data[..]
|
||||
);
|
||||
let expected_extension_cbor = vec![
|
||||
0xA1, 0x6B, 0x68, 0x6D, 0x61, 0x63, 0x2D, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74,
|
||||
0xF5,
|
||||
];
|
||||
assert_eq!(
|
||||
auth_data[auth_data.len() - expected_extension_cbor.len()..auth_data.len()],
|
||||
expected_extension_cbor[..]
|
||||
);
|
||||
assert_eq!(att_stmt.alg, SignatureAlgorithm::ES256 as i64);
|
||||
}
|
||||
_ => panic!("Invalid response type"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_process_make_credential_hmac_secret_resident_key() {
|
||||
let mut rng = ThreadRng256 {};
|
||||
let user_immediately_present = |_| Ok(());
|
||||
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
|
||||
|
||||
let extensions = Some(MakeCredentialExtensions {
|
||||
hmac_secret: true,
|
||||
cred_protect: None,
|
||||
@@ -1219,6 +1286,70 @@ mod test {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_process_get_assertion_hmac_secret() {
|
||||
let mut rng = ThreadRng256 {};
|
||||
let sk = crypto::ecdh::SecKey::gensk(&mut rng);
|
||||
let user_immediately_present = |_| Ok(());
|
||||
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
|
||||
|
||||
let make_extensions = Some(MakeCredentialExtensions {
|
||||
hmac_secret: true,
|
||||
cred_protect: None,
|
||||
});
|
||||
let mut make_credential_params = create_minimal_make_credential_parameters();
|
||||
make_credential_params.options.rk = false;
|
||||
make_credential_params.extensions = make_extensions;
|
||||
let make_credential_response =
|
||||
ctap_state.process_make_credential(make_credential_params, DUMMY_CHANNEL_ID);
|
||||
assert!(make_credential_response.is_ok());
|
||||
let credential_id = match make_credential_response.unwrap() {
|
||||
ResponseData::AuthenticatorMakeCredential(make_credential_response) => {
|
||||
let auth_data = make_credential_response.auth_data;
|
||||
let offset = 37 + ctap_state.persistent_store.aaguid().unwrap().len();
|
||||
assert_eq!(auth_data[offset], 0x00);
|
||||
assert_eq!(auth_data[offset + 1] as usize, CREDENTIAL_ID_MAX_SIZE);
|
||||
auth_data[offset + 2..offset + 2 + CREDENTIAL_ID_MAX_SIZE].to_vec()
|
||||
}
|
||||
_ => panic!("Invalid response type"),
|
||||
};
|
||||
|
||||
let pk = sk.genpk();
|
||||
let hmac_secret_input = GetAssertionHmacSecretInput {
|
||||
key_agreement: CoseKey::from(pk),
|
||||
salt_enc: vec![0x02; 32],
|
||||
salt_auth: vec![0x03; 16],
|
||||
};
|
||||
let get_extensions = Some(GetAssertionExtensions {
|
||||
hmac_secret: Some(hmac_secret_input),
|
||||
});
|
||||
|
||||
let cred_desc = PublicKeyCredentialDescriptor {
|
||||
key_type: PublicKeyCredentialType::PublicKey,
|
||||
key_id: credential_id,
|
||||
transports: None,
|
||||
};
|
||||
let get_assertion_params = AuthenticatorGetAssertionParameters {
|
||||
rp_id: String::from("example.com"),
|
||||
client_data_hash: vec![0xCD],
|
||||
allow_list: Some(vec![cred_desc]),
|
||||
extensions: get_extensions,
|
||||
options: GetAssertionOptions {
|
||||
up: false,
|
||||
uv: false,
|
||||
},
|
||||
pin_uv_auth_param: None,
|
||||
pin_uv_auth_protocol: None,
|
||||
};
|
||||
let get_assertion_response =
|
||||
ctap_state.process_get_assertion(get_assertion_params, DUMMY_CHANNEL_ID);
|
||||
|
||||
assert_eq!(
|
||||
get_assertion_response,
|
||||
Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_residential_process_get_assertion_hmac_secret() {
|
||||
let mut rng = ThreadRng256 {};
|
||||
@@ -1434,7 +1565,7 @@ mod test {
|
||||
// We are not testing the correctness of our SHA256 here, only if it is checked.
|
||||
let rp_id_hash = [0x55; 32];
|
||||
let encrypted_id = ctap_state
|
||||
.encrypt_key_handle(private_key.clone(), &rp_id_hash)
|
||||
.encrypt_key_handle(private_key.clone(), &rp_id_hash, None)
|
||||
.unwrap();
|
||||
let decrypted_source = ctap_state
|
||||
.decrypt_credential_source(encrypted_id, &rp_id_hash)
|
||||
@@ -1444,6 +1575,29 @@ mod test {
|
||||
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);
|
||||
|
||||
// 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]
|
||||
fn test_encrypt_decrypt_bad_hmac() {
|
||||
let mut rng = ThreadRng256 {};
|
||||
@@ -1454,7 +1608,30 @@ mod test {
|
||||
// Same as above.
|
||||
let rp_id_hash = [0x55; 32];
|
||||
let encrypted_id = ctap_state
|
||||
.encrypt_key_handle(private_key, &rp_id_hash)
|
||||
.encrypt_key_handle(private_key, &rp_id_hash, None)
|
||||
.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);
|
||||
|
||||
// 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();
|
||||
for i in 0..encrypted_id.len() {
|
||||
let mut modified_id = encrypted_id.clone();
|
||||
|
||||
@@ -19,7 +19,6 @@ use super::status_code::Ctap2StatusCode;
|
||||
use super::storage::PersistentStore;
|
||||
#[cfg(feature = "with_ctap2_1")]
|
||||
use alloc::string::String;
|
||||
#[cfg(feature = "with_ctap2_1")]
|
||||
use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
use arrayref::array_ref;
|
||||
@@ -74,10 +73,9 @@ fn encrypt_hmac_secret_output(
|
||||
let mut cred_random_secret = [0u8; 32];
|
||||
cred_random_secret.copy_from_slice(cred_random);
|
||||
|
||||
// Initialization of 4 blocks in any case makes this function more readable.
|
||||
let mut blocks = [[0u8; 16]; 4];
|
||||
// With the if clause restriction above, block_len can only be 2 or 4.
|
||||
let block_len = salt_enc.len() / 16;
|
||||
let mut blocks = vec![[0u8; 16]; block_len];
|
||||
for i in 0..block_len {
|
||||
blocks[i].copy_from_slice(&salt_enc[16 * i..16 * (i + 1)]);
|
||||
}
|
||||
@@ -85,8 +83,8 @@ fn encrypt_hmac_secret_output(
|
||||
|
||||
let mut decrypted_salt1 = [0u8; 32];
|
||||
decrypted_salt1[..16].copy_from_slice(&blocks[0]);
|
||||
let output1 = hmac_256::<Sha256>(&cred_random_secret, &decrypted_salt1[..]);
|
||||
decrypted_salt1[16..].copy_from_slice(&blocks[1]);
|
||||
let output1 = hmac_256::<Sha256>(&cred_random_secret, &decrypted_salt1[..]);
|
||||
for i in 0..2 {
|
||||
blocks[i].copy_from_slice(&output1[16 * i..16 * (i + 1)]);
|
||||
}
|
||||
@@ -638,36 +636,52 @@ impl PinProtocolV1 {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use arrayref::array_refs;
|
||||
use crypto::rng256::ThreadRng256;
|
||||
|
||||
// Stores a PIN hash corresponding to the dummy PIN "1234".
|
||||
fn set_standard_pin(persistent_store: &mut PersistentStore) {
|
||||
let mut pin = [0u8; 64];
|
||||
pin[0] = 0x31;
|
||||
pin[1] = 0x32;
|
||||
pin[2] = 0x33;
|
||||
pin[3] = 0x34;
|
||||
pin[..4].copy_from_slice(b"1234");
|
||||
let mut pin_hash = [0u8; 16];
|
||||
pin_hash.copy_from_slice(&Sha256::hash(&pin[..])[..16]);
|
||||
persistent_store.set_pin_hash(&pin_hash).unwrap();
|
||||
}
|
||||
|
||||
// Encrypts the message with a zero IV and key derived from shared_secret.
|
||||
fn encrypt_message(shared_secret: &[u8; 32], message: &[u8]) -> Vec<u8> {
|
||||
assert!(message.len() % 16 == 0);
|
||||
let block_len = message.len() / 16;
|
||||
let mut blocks = vec![[0u8; 16]; block_len];
|
||||
for i in 0..block_len {
|
||||
blocks[i][..].copy_from_slice(&message[i * 16..(i + 1) * 16]);
|
||||
}
|
||||
let aes_enc_key = crypto::aes256::EncryptionKey::new(shared_secret);
|
||||
let iv = [0u8; 16];
|
||||
cbc_encrypt(&aes_enc_key, iv, &mut blocks);
|
||||
blocks.iter().flatten().cloned().collect::<Vec<u8>>()
|
||||
}
|
||||
|
||||
// Decrypts the message with a zero IV and key derived from shared_secret.
|
||||
fn decrypt_message(shared_secret: &[u8; 32], message: &[u8]) -> Vec<u8> {
|
||||
assert!(message.len() % 16 == 0);
|
||||
let block_len = message.len() / 16;
|
||||
let mut blocks = vec![[0u8; 16]; block_len];
|
||||
for i in 0..block_len {
|
||||
blocks[i][..].copy_from_slice(&message[i * 16..(i + 1) * 16]);
|
||||
}
|
||||
let aes_enc_key = crypto::aes256::EncryptionKey::new(shared_secret);
|
||||
let aes_dec_key = crypto::aes256::DecryptionKey::new(&aes_enc_key);
|
||||
let iv = [0u8; 16];
|
||||
cbc_decrypt(&aes_dec_key, iv, &mut blocks);
|
||||
blocks.iter().flatten().cloned().collect::<Vec<u8>>()
|
||||
}
|
||||
|
||||
// Fails on PINs bigger than 64 bytes.
|
||||
fn encrypt_pin(shared_secret: &[u8; 32], pin: Vec<u8>) -> Vec<u8> {
|
||||
assert!(pin.len() <= 64);
|
||||
let mut padded_pin = [0u8; 64];
|
||||
padded_pin[..pin.len()].copy_from_slice(&pin[..]);
|
||||
let aes_enc_key = crypto::aes256::EncryptionKey::new(shared_secret);
|
||||
let mut blocks = [[0u8; 16]; 4];
|
||||
let (b0, b1, b2, b3) = array_refs!(&padded_pin, 16, 16, 16, 16);
|
||||
blocks[0][..].copy_from_slice(b0);
|
||||
blocks[1][..].copy_from_slice(b1);
|
||||
blocks[2][..].copy_from_slice(b2);
|
||||
blocks[3][..].copy_from_slice(b3);
|
||||
let iv = [0u8; 16];
|
||||
cbc_encrypt(&aes_enc_key, iv, &mut blocks);
|
||||
blocks.iter().flatten().cloned().collect::<Vec<u8>>()
|
||||
encrypt_message(shared_secret, &padded_pin)
|
||||
}
|
||||
|
||||
// Encrypts the dummy PIN "1234".
|
||||
@@ -677,22 +691,10 @@ mod test {
|
||||
|
||||
// Encrypts the PIN hash corresponding to the dummy PIN "1234".
|
||||
fn encrypt_standard_pin_hash(shared_secret: &[u8; 32]) -> Vec<u8> {
|
||||
let aes_enc_key = crypto::aes256::EncryptionKey::new(shared_secret);
|
||||
let mut pin = [0u8; 64];
|
||||
pin[0] = 0x31;
|
||||
pin[1] = 0x32;
|
||||
pin[2] = 0x33;
|
||||
pin[3] = 0x34;
|
||||
pin[..4].copy_from_slice(b"1234");
|
||||
let pin_hash = Sha256::hash(&pin);
|
||||
|
||||
let mut blocks = [[0u8; 16]; 1];
|
||||
blocks[0].copy_from_slice(&pin_hash[..16]);
|
||||
let iv = [0u8; 16];
|
||||
cbc_encrypt(&aes_enc_key, iv, &mut blocks);
|
||||
|
||||
let mut encrypted_pin_hash = Vec::with_capacity(16);
|
||||
encrypted_pin_hash.extend(&blocks[0]);
|
||||
encrypted_pin_hash
|
||||
encrypt_message(shared_secret, &pin_hash[..16])
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1184,6 +1186,56 @@ mod test {
|
||||
output,
|
||||
Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION)
|
||||
);
|
||||
|
||||
let mut salt_enc = [0x00; 32];
|
||||
let cred_random = [0xC9; 32];
|
||||
|
||||
// Test values to check for reproducibility.
|
||||
let salt1 = [0x01; 32];
|
||||
let salt2 = [0x02; 32];
|
||||
let expected_output1 = hmac_256::<Sha256>(&cred_random, &salt1);
|
||||
let expected_output2 = hmac_256::<Sha256>(&cred_random, &salt2);
|
||||
|
||||
let salt_enc1 = encrypt_message(&shared_secret, &salt1);
|
||||
salt_enc.copy_from_slice(salt_enc1.as_slice());
|
||||
let output = encrypt_hmac_secret_output(&shared_secret, &salt_enc, &cred_random).unwrap();
|
||||
let output_dec = decrypt_message(&shared_secret, &output);
|
||||
assert_eq!(&output_dec, &expected_output1);
|
||||
|
||||
let salt_enc2 = &encrypt_message(&shared_secret, &salt2);
|
||||
salt_enc.copy_from_slice(salt_enc2.as_slice());
|
||||
let output = encrypt_hmac_secret_output(&shared_secret, &salt_enc, &cred_random).unwrap();
|
||||
let output_dec = decrypt_message(&shared_secret, &output);
|
||||
assert_eq!(&output_dec, &expected_output2);
|
||||
|
||||
let mut salt_enc = [0x00; 64];
|
||||
let mut salt12 = [0x00; 64];
|
||||
salt12[..32].copy_from_slice(&salt1);
|
||||
salt12[32..].copy_from_slice(&salt2);
|
||||
let salt_enc12 = encrypt_message(&shared_secret, &salt12);
|
||||
salt_enc.copy_from_slice(salt_enc12.as_slice());
|
||||
let output = encrypt_hmac_secret_output(&shared_secret, &salt_enc, &cred_random).unwrap();
|
||||
let output_dec = decrypt_message(&shared_secret, &output);
|
||||
assert_eq!(&output_dec[..32], &expected_output1);
|
||||
assert_eq!(&output_dec[32..], &expected_output2);
|
||||
|
||||
let mut salt_enc = [0x00; 64];
|
||||
let mut salt02 = [0x00; 64];
|
||||
salt02[32..].copy_from_slice(&salt2);
|
||||
let salt_enc02 = encrypt_message(&shared_secret, &salt02);
|
||||
salt_enc.copy_from_slice(salt_enc02.as_slice());
|
||||
let output = encrypt_hmac_secret_output(&shared_secret, &salt_enc, &cred_random).unwrap();
|
||||
let output_dec = decrypt_message(&shared_secret, &output);
|
||||
assert_eq!(&output_dec[32..], &expected_output2);
|
||||
|
||||
let mut salt_enc = [0x00; 64];
|
||||
let mut salt10 = [0x00; 64];
|
||||
salt10[..32].copy_from_slice(&salt1);
|
||||
let salt_enc10 = encrypt_message(&shared_secret, &salt10);
|
||||
salt_enc.copy_from_slice(salt_enc10.as_slice());
|
||||
let output = encrypt_hmac_secret_output(&shared_secret, &salt_enc, &cred_random).unwrap();
|
||||
let output_dec = decrypt_message(&shared_secret, &output);
|
||||
assert_eq!(&output_dec[..32], &expected_output1);
|
||||
}
|
||||
|
||||
#[cfg(feature = "with_ctap2_1")]
|
||||
|
||||
Reference in New Issue
Block a user