Merge branch 'master' into v2_fuzz

This commit is contained in:
Julien Cretin
2020-11-28 18:45:41 +01:00
14 changed files with 1227 additions and 389 deletions

View File

@@ -27,7 +27,7 @@ jobs:
- name: Set up OpenSK - name: Set up OpenSK
run: ./setup.sh run: ./setup.sh
- run: echo "::set-env name=RUSTFLAGS::-C target-feature=+aes" - run: echo "RUSTFLAGS=-C target-feature=+aes" >> $GITHUB_ENV
- name: Unit testing of crypto library (release mode) - name: Unit testing of crypto library (release mode)
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1

View File

@@ -147,7 +147,7 @@ fn process_message<CheckUserPresence>(
pub fn process_ctap_any_type(data: &[u8]) { pub fn process_ctap_any_type(data: &[u8]) {
// Initialize ctap state and hid and get the allocated cid. // Initialize ctap state and hid and get the allocated cid.
let mut rng = ThreadRng256 {}; let mut rng = ThreadRng256 {};
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present); let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
let mut ctap_hid = CtapHid::new(); let mut ctap_hid = CtapHid::new();
let cid = initialize(&mut ctap_state, &mut ctap_hid); let cid = initialize(&mut ctap_state, &mut ctap_hid);
// Wrap input as message with the allocated cid. // Wrap input as message with the allocated cid.
@@ -165,7 +165,7 @@ pub fn process_ctap_specific_type(data: &[u8], input_type: InputType) {
} }
// Initialize ctap state and hid and get the allocated cid. // Initialize ctap state and hid and get the allocated cid.
let mut rng = ThreadRng256 {}; let mut rng = ThreadRng256 {};
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present); let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
let mut ctap_hid = CtapHid::new(); let mut ctap_hid = CtapHid::new();
let cid = initialize(&mut ctap_state, &mut ctap_hid); let cid = initialize(&mut ctap_state, &mut ctap_hid);
// Wrap input as message with allocated cid and command type. // Wrap input as message with allocated cid and command type.

View File

@@ -181,6 +181,12 @@ pub enum StoreInvariant {
}, },
} }
impl From<StoreError> for StoreInvariant {
fn from(error: StoreError) -> StoreInvariant {
StoreInvariant::StoreError(error)
}
}
impl StoreDriver { impl StoreDriver {
/// Provides read-only access to the storage. /// Provides read-only access to the storage.
pub fn storage(&self) -> &BufferStorage { pub fn storage(&self) -> &BufferStorage {
@@ -249,6 +255,10 @@ impl StoreDriverOff {
} }
/// Powers on the store without interruption. /// Powers on the store without interruption.
///
/// # Panics
///
/// Panics if the store cannot be powered on.
pub fn power_on(self) -> Result<StoreDriverOn, StoreInvariant> { pub fn power_on(self) -> Result<StoreDriverOn, StoreInvariant> {
Ok(self Ok(self
.partial_power_on(StoreInterruption::none()) .partial_power_on(StoreInterruption::none())
@@ -506,8 +516,8 @@ impl StoreDriverOn {
/// Checks that the store and model are in sync. /// Checks that the store and model are in sync.
fn check_model(&self) -> Result<(), StoreInvariant> { fn check_model(&self) -> Result<(), StoreInvariant> {
let mut model_content = self.model.content().clone(); let mut model_content = self.model.content().clone();
for handle in self.store.iter().unwrap() { for handle in self.store.iter()? {
let handle = handle.unwrap(); let handle = handle?;
let model_value = match model_content.remove(&handle.get_key()) { let model_value = match model_content.remove(&handle.get_key()) {
None => { None => {
return Err(StoreInvariant::OnlyInStore { return Err(StoreInvariant::OnlyInStore {
@@ -516,7 +526,7 @@ impl StoreDriverOn {
} }
Some(x) => x, Some(x) => x,
}; };
let store_value = handle.get_value(&self.store).unwrap().into_boxed_slice(); let store_value = handle.get_value(&self.store)?.into_boxed_slice();
if store_value != model_value { if store_value != model_value {
return Err(StoreInvariant::DifferentValue { return Err(StoreInvariant::DifferentValue {
key: handle.get_key(), key: handle.get_key(),
@@ -528,7 +538,7 @@ impl StoreDriverOn {
if let Some(&key) = model_content.keys().next() { if let Some(&key) = model_content.keys().next() {
return Err(StoreInvariant::OnlyInModel { key }); return Err(StoreInvariant::OnlyInModel { key });
} }
let store_capacity = self.store.capacity().unwrap().remaining(); let store_capacity = self.store.capacity()?.remaining();
let model_capacity = self.model.capacity().remaining(); let model_capacity = self.model.capacity().remaining();
if store_capacity != model_capacity { if store_capacity != model_capacity {
return Err(StoreInvariant::DifferentCapacity { return Err(StoreInvariant::DifferentCapacity {
@@ -544,8 +554,8 @@ impl StoreDriverOn {
let format = self.model.format(); let format = self.model.format();
let storage = self.store.storage(); let storage = self.store.storage();
let num_words = format.page_size() / format.word_size(); let num_words = format.page_size() / format.word_size();
let head = self.store.head().unwrap(); let head = self.store.head()?;
let tail = self.store.tail().unwrap(); let tail = self.store.tail()?;
for page in 0..format.num_pages() { for page in 0..format.num_pages() {
// Check the erase cycle of the page. // Check the erase cycle of the page.
let store_erase = head.cycle(format) + (page < head.page(format)) as Nat; let store_erase = head.cycle(format) + (page < head.page(format)) as Nat;

46
metadata/metadata.json Normal file
View File

@@ -0,0 +1,46 @@
{
"assertionScheme": "FIDOV2",
"keyProtection": 1,
"attestationRootCertificates": [],
"aaguid": "664d9f67-84a2-412a-9ff7-b4f7d8ee6d05",
"publicKeyAlgAndEncoding": 260,
"protocolFamily": "fido2",
"upv": [
{
"major": 1,
"minor": 0
}
],
"icon": "data:image/png;base64,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",
"matcherProtection": 1,
"supportedExtensions": [
{
"id": "hmac-secret",
"fail_if_unknown": false
},
{
"id": "credProtect",
"fail_if_unknown": false
}
],
"cryptoStrength": 128,
"description": "OpenSK authenticator",
"authenticatorVersion": 1,
"isSecondFactorOnly": false,
"userVerificationDetails": [
[
{
"userVerification": 1
},
{
"userVerification": 4
}
]
],
"attachmentHint": 6,
"attestationTypes": [
15880
],
"authenticationAlgorithm": 1,
"tcDisplay": 0
}

View File

@@ -57,8 +57,8 @@ impl Command {
const AUTHENTICATOR_GET_INFO: u8 = 0x04; const AUTHENTICATOR_GET_INFO: u8 = 0x04;
const AUTHENTICATOR_CLIENT_PIN: u8 = 0x06; const AUTHENTICATOR_CLIENT_PIN: u8 = 0x06;
const AUTHENTICATOR_RESET: u8 = 0x07; const AUTHENTICATOR_RESET: u8 = 0x07;
// TODO(kaczmarczyck) use or remove those constants
const AUTHENTICATOR_GET_NEXT_ASSERTION: u8 = 0x08; const AUTHENTICATOR_GET_NEXT_ASSERTION: u8 = 0x08;
// TODO(kaczmarczyck) use or remove those constants
const AUTHENTICATOR_BIO_ENROLLMENT: u8 = 0x09; const AUTHENTICATOR_BIO_ENROLLMENT: u8 = 0x09;
const AUTHENTICATOR_CREDENTIAL_MANAGEMENT: u8 = 0xA0; const AUTHENTICATOR_CREDENTIAL_MANAGEMENT: u8 = 0xA0;
const AUTHENTICATOR_SELECTION: u8 = 0xB0; const AUTHENTICATOR_SELECTION: u8 = 0xB0;

View File

@@ -13,7 +13,6 @@
// limitations under the License. // limitations under the License.
use super::hid::ChannelID; use super::hid::ChannelID;
use super::key_material::{ATTESTATION_CERTIFICATE, ATTESTATION_PRIVATE_KEY};
use super::status_code::Ctap2StatusCode; use super::status_code::Ctap2StatusCode;
use super::CtapState; use super::CtapState;
use alloc::vec::Vec; use alloc::vec::Vec;
@@ -36,6 +35,8 @@ pub enum Ctap1StatusCode {
SW_WRONG_LENGTH = 0x6700, SW_WRONG_LENGTH = 0x6700,
SW_CLA_NOT_SUPPORTED = 0x6E00, SW_CLA_NOT_SUPPORTED = 0x6E00,
SW_INS_NOT_SUPPORTED = 0x6D00, SW_INS_NOT_SUPPORTED = 0x6D00,
SW_MEMERR = 0x6501,
SW_COMMAND_ABORTED = 0x6F00,
SW_VENDOR_KEY_HANDLE_TOO_LONG = 0xF000, SW_VENDOR_KEY_HANDLE_TOO_LONG = 0xF000,
} }
@@ -50,6 +51,8 @@ impl TryFrom<u16> for Ctap1StatusCode {
0x6700 => Ok(Ctap1StatusCode::SW_WRONG_LENGTH), 0x6700 => Ok(Ctap1StatusCode::SW_WRONG_LENGTH),
0x6E00 => Ok(Ctap1StatusCode::SW_CLA_NOT_SUPPORTED), 0x6E00 => Ok(Ctap1StatusCode::SW_CLA_NOT_SUPPORTED),
0x6D00 => Ok(Ctap1StatusCode::SW_INS_NOT_SUPPORTED), 0x6D00 => Ok(Ctap1StatusCode::SW_INS_NOT_SUPPORTED),
0x6501 => Ok(Ctap1StatusCode::SW_MEMERR),
0x6F00 => Ok(Ctap1StatusCode::SW_COMMAND_ABORTED),
0xF000 => Ok(Ctap1StatusCode::SW_VENDOR_KEY_HANDLE_TOO_LONG), 0xF000 => Ok(Ctap1StatusCode::SW_VENDOR_KEY_HANDLE_TOO_LONG),
_ => Err(()), _ => Err(()),
} }
@@ -288,21 +291,31 @@ 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) .encrypt_key_handle(sk, &application, None)
.map_err(|_| Ctap1StatusCode::SW_VENDOR_KEY_HANDLE_TOO_LONG)?; .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.
return Err(Ctap1StatusCode::SW_VENDOR_KEY_HANDLE_TOO_LONG); return Err(Ctap1StatusCode::SW_VENDOR_KEY_HANDLE_TOO_LONG);
} }
let mut response = let certificate = ctap_state
Vec::with_capacity(105 + key_handle.len() + ATTESTATION_CERTIFICATE.len()); .persistent_store
.attestation_certificate()
.map_err(|_| Ctap1StatusCode::SW_MEMERR)?
.ok_or(Ctap1StatusCode::SW_COMMAND_ABORTED)?;
let private_key = ctap_state
.persistent_store
.attestation_private_key()
.map_err(|_| Ctap1StatusCode::SW_MEMERR)?
.ok_or(Ctap1StatusCode::SW_COMMAND_ABORTED)?;
let mut response = Vec::with_capacity(105 + key_handle.len() + certificate.len());
response.push(Ctap1Command::LEGACY_BYTE); response.push(Ctap1Command::LEGACY_BYTE);
let user_pk = pk.to_uncompressed(); let user_pk = pk.to_uncompressed();
response.extend_from_slice(&user_pk); response.extend_from_slice(&user_pk);
response.push(key_handle.len() as u8); response.push(key_handle.len() as u8);
response.extend(key_handle.clone()); response.extend(key_handle.clone());
response.extend_from_slice(&ATTESTATION_CERTIFICATE); response.extend_from_slice(&certificate);
// The first byte is reserved. // The first byte is reserved.
let mut signature_data = Vec::with_capacity(66 + key_handle.len()); let mut signature_data = Vec::with_capacity(66 + key_handle.len());
@@ -312,7 +325,7 @@ impl Ctap1Command {
signature_data.extend(key_handle); signature_data.extend(key_handle);
signature_data.extend_from_slice(&user_pk); signature_data.extend_from_slice(&user_pk);
let attestation_key = crypto::ecdsa::SecKey::from_bytes(ATTESTATION_PRIVATE_KEY).unwrap(); let attestation_key = crypto::ecdsa::SecKey::from_bytes(private_key).unwrap();
let signature = attestation_key.sign_rfc6979::<crypto::sha256::Sha256>(&signature_data); let signature = attestation_key.sign_rfc6979::<crypto::sha256::Sha256>(&signature_data);
response.extend(signature.to_asn1_der()); response.extend(signature.to_asn1_der());
@@ -373,7 +386,7 @@ impl Ctap1Command {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::super::{ENCRYPTED_CREDENTIAL_ID_SIZE, USE_SIGNATURE_COUNTER}; use super::super::{key_material, CREDENTIAL_ID_BASE_SIZE, USE_SIGNATURE_COUNTER};
use super::*; use super::*;
use crypto::rng256::ThreadRng256; use crypto::rng256::ThreadRng256;
use crypto::Hash256; use crypto::Hash256;
@@ -413,12 +426,12 @@ mod test {
0x00, 0x00,
0x00, 0x00,
0x00, 0x00,
65 + ENCRYPTED_CREDENTIAL_ID_SIZE as u8, 65 + CREDENTIAL_ID_BASE_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(ENCRYPTED_CREDENTIAL_ID_SIZE as u8); message.push(CREDENTIAL_ID_BASE_SIZE as u8);
message.extend(key_handle); message.extend(key_handle);
message message
} }
@@ -427,28 +440,49 @@ mod test {
fn test_process_register() { fn test_process_register() {
let mut rng = ThreadRng256 {}; let mut rng = ThreadRng256 {};
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1"); let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence); let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
let application = [0x0A; 32]; let application = [0x0A; 32];
let message = create_register_message(&application); let message = create_register_message(&application);
ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE); ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE);
ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE); ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE);
let response = Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE);
// Certificate and private key are missing
assert_eq!(response, Err(Ctap1StatusCode::SW_COMMAND_ABORTED));
let fake_key = [0x41u8; key_material::ATTESTATION_PRIVATE_KEY_LENGTH];
assert!(ctap_state
.persistent_store
.set_attestation_private_key(&fake_key)
.is_ok());
ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE);
ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE);
let response = Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE);
// Certificate is still missing
assert_eq!(response, Err(Ctap1StatusCode::SW_COMMAND_ABORTED));
let fake_cert = [0x99u8; 100]; // Arbitrary length
assert!(ctap_state
.persistent_store
.set_attestation_certificate(&fake_cert[..])
.is_ok());
ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE);
ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE);
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], ENCRYPTED_CREDENTIAL_ID_SIZE as u8); assert_eq!(response[66], CREDENTIAL_ID_BASE_SIZE as u8);
assert!(ctap_state assert!(ctap_state
.decrypt_credential_source( .decrypt_credential_source(
response[67..67 + ENCRYPTED_CREDENTIAL_ID_SIZE].to_vec(), response[67..67 + CREDENTIAL_ID_BASE_SIZE].to_vec(),
&application &application
) )
.unwrap() .unwrap()
.is_some()); .is_some());
const CERT_START: usize = 67 + ENCRYPTED_CREDENTIAL_ID_SIZE; const CERT_START: usize = 67 + CREDENTIAL_ID_BASE_SIZE;
assert_eq!( assert_eq!(
&response[CERT_START..CERT_START + ATTESTATION_CERTIFICATE.len()], &response[CERT_START..CERT_START + fake_cert.len()],
&ATTESTATION_CERTIFICATE[..] &fake_cert[..]
); );
} }
@@ -456,7 +490,7 @@ mod test {
fn test_process_register_bad_message() { fn test_process_register_bad_message() {
let mut rng = ThreadRng256 {}; let mut rng = ThreadRng256 {};
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1"); let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence); let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
let application = [0x0A; 32]; let application = [0x0A; 32];
let message = create_register_message(&application); let message = create_register_message(&application);
@@ -476,7 +510,7 @@ mod test {
let mut rng = ThreadRng256 {}; let mut rng = ThreadRng256 {};
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1"); let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence); let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE); ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE);
ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE); ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE);
@@ -490,11 +524,13 @@ mod test {
let mut rng = ThreadRng256 {}; let mut rng = ThreadRng256 {};
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1"); let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
let sk = crypto::ecdsa::SecKey::gensk(&mut rng); let sk = crypto::ecdsa::SecKey::gensk(&mut rng);
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence); let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
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.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 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);
@@ -506,11 +542,13 @@ mod test {
let mut rng = ThreadRng256 {}; let mut rng = ThreadRng256 {};
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1"); let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
let sk = crypto::ecdsa::SecKey::gensk(&mut rng); let sk = crypto::ecdsa::SecKey::gensk(&mut rng);
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence); let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
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.encrypt_key_handle(sk, &application).unwrap(); let key_handle = ctap_state
.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);
@@ -523,11 +561,13 @@ mod test {
let mut rng = ThreadRng256 {}; let mut rng = ThreadRng256 {};
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1"); let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
let sk = crypto::ecdsa::SecKey::gensk(&mut rng); let sk = crypto::ecdsa::SecKey::gensk(&mut rng);
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence); let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
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.encrypt_key_handle(sk, &application).unwrap(); let key_handle = ctap_state
.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);
@@ -547,11 +587,13 @@ mod test {
let mut rng = ThreadRng256 {}; let mut rng = ThreadRng256 {};
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1"); let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
let sk = crypto::ecdsa::SecKey::gensk(&mut rng); let sk = crypto::ecdsa::SecKey::gensk(&mut rng);
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence); let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
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.encrypt_key_handle(sk, &application).unwrap(); let key_handle = ctap_state
.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;
@@ -565,11 +607,13 @@ mod test {
let mut rng = ThreadRng256 {}; let mut rng = ThreadRng256 {};
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1"); let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
let sk = crypto::ecdsa::SecKey::gensk(&mut rng); let sk = crypto::ecdsa::SecKey::gensk(&mut rng);
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence); let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
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.encrypt_key_handle(sk, &application).unwrap(); let key_handle = ctap_state
.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;
@@ -583,11 +627,13 @@ mod test {
let mut rng = ThreadRng256 {}; let mut rng = ThreadRng256 {};
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1"); let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
let sk = crypto::ecdsa::SecKey::gensk(&mut rng); let sk = crypto::ecdsa::SecKey::gensk(&mut rng);
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence); let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
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.encrypt_key_handle(sk, &application).unwrap(); let key_handle = ctap_state
.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;
@@ -601,11 +647,13 @@ mod test {
let mut rng = ThreadRng256 {}; let mut rng = ThreadRng256 {};
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1"); let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
let sk = crypto::ecdsa::SecKey::gensk(&mut rng); let sk = crypto::ecdsa::SecKey::gensk(&mut rng);
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence); let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
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.encrypt_key_handle(sk, &application).unwrap(); let key_handle = ctap_state
.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);
@@ -626,11 +674,13 @@ mod test {
let mut rng = ThreadRng256 {}; let mut rng = ThreadRng256 {};
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1"); let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
let sk = crypto::ecdsa::SecKey::gensk(&mut rng); let sk = crypto::ecdsa::SecKey::gensk(&mut rng);
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence); let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
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.encrypt_key_handle(sk, &application).unwrap(); let key_handle = ctap_state
.encrypt_key_handle(sk, &application, None)
.unwrap();
let message = create_authenticate_message( let message = create_authenticate_message(
&application, &application,
Ctap1Flags::DontEnforceUpAndSign, Ctap1Flags::DontEnforceUpAndSign,
@@ -650,13 +700,13 @@ 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; ENCRYPTED_CREDENTIAL_ID_SIZE]; let key_handle = vec![0x00; CREDENTIAL_ID_BASE_SIZE];
let message = let message =
create_authenticate_message(&application, Ctap1Flags::EnforceUpAndSign, &key_handle); create_authenticate_message(&application, Ctap1Flags::EnforceUpAndSign, &key_handle);
let mut rng = ThreadRng256 {}; let mut rng = ThreadRng256 {};
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1"); let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence); let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE); ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE);
ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE); ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE);
@@ -667,13 +717,13 @@ 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; ENCRYPTED_CREDENTIAL_ID_SIZE]; let key_handle = vec![0x00; CREDENTIAL_ID_BASE_SIZE];
let message = let message =
create_authenticate_message(&application, Ctap1Flags::EnforceUpAndSign, &key_handle); create_authenticate_message(&application, Ctap1Flags::EnforceUpAndSign, &key_handle);
let mut rng = ThreadRng256 {}; let mut rng = ThreadRng256 {};
let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1"); let dummy_user_presence = |_| panic!("Unexpected user presence check in CTAP1");
let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence); let mut ctap_state = CtapState::new(&mut rng, dummy_user_presence, START_CLOCK_VALUE);
ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE); ctap_state.u2f_up_state.consume_up(START_CLOCK_VALUE);
ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE); ctap_state.u2f_up_state.grant_up(START_CLOCK_VALUE);

View File

@@ -308,7 +308,8 @@ impl TryFrom<cbor::Value> for GetAssertionExtensions {
} }
} }
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Clone, Debug, PartialEq))] #[derive(Clone)]
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
pub struct GetAssertionHmacSecretInput { pub struct GetAssertionHmacSecretInput {
pub key_agreement: CoseKey, pub key_agreement: CoseKey,
pub salt_enc: Vec<u8>, pub salt_enc: Vec<u8>,
@@ -361,10 +362,8 @@ impl TryFrom<cbor::Value> for MakeCredentialOptions {
Some(options_entry) => extract_bool(options_entry)?, Some(options_entry) => extract_bool(options_entry)?,
None => false, None => false,
}; };
if let Some(options_entry) = up { if up.is_some() {
if !extract_bool(options_entry)? { return Err(Ctap2StatusCode::CTAP2_ERR_INVALID_OPTION);
return Err(Ctap2StatusCode::CTAP2_ERR_INVALID_OPTION);
}
} }
let uv = match uv { let uv = match uv {
Some(options_entry) => extract_bool(options_entry)?, Some(options_entry) => extract_bool(options_entry)?,
@@ -501,6 +500,7 @@ pub struct PublicKeyCredentialSource {
pub other_ui: Option<String>, pub other_ui: Option<String>,
pub cred_random: Option<Vec<u8>>, pub cred_random: Option<Vec<u8>>,
pub cred_protect_policy: Option<CredentialProtectionPolicy>, pub cred_protect_policy: Option<CredentialProtectionPolicy>,
pub creation_order: u64,
} }
// We serialize credentials for the persistent storage using CBOR maps. Each field of a credential // We serialize credentials for the persistent storage using CBOR maps. Each field of a credential
@@ -513,6 +513,7 @@ enum PublicKeyCredentialSourceField {
OtherUi = 4, OtherUi = 4,
CredRandom = 5, CredRandom = 5,
CredProtectPolicy = 6, CredProtectPolicy = 6,
CreationOrder = 7,
// 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: none.
@@ -536,6 +537,7 @@ impl From<PublicKeyCredentialSource> for cbor::Value {
PublicKeyCredentialSourceField::OtherUi => credential.other_ui, PublicKeyCredentialSourceField::OtherUi => credential.other_ui,
PublicKeyCredentialSourceField::CredRandom => credential.cred_random, PublicKeyCredentialSourceField::CredRandom => credential.cred_random,
PublicKeyCredentialSourceField::CredProtectPolicy => credential.cred_protect_policy, PublicKeyCredentialSourceField::CredProtectPolicy => credential.cred_protect_policy,
PublicKeyCredentialSourceField::CreationOrder => credential.creation_order,
} }
} }
} }
@@ -553,6 +555,7 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialSource {
PublicKeyCredentialSourceField::OtherUi => other_ui, PublicKeyCredentialSourceField::OtherUi => other_ui,
PublicKeyCredentialSourceField::CredRandom => cred_random, PublicKeyCredentialSourceField::CredRandom => cred_random,
PublicKeyCredentialSourceField::CredProtectPolicy => cred_protect_policy, PublicKeyCredentialSourceField::CredProtectPolicy => cred_protect_policy,
PublicKeyCredentialSourceField::CreationOrder => creation_order,
} = extract_map(cbor_value)?; } = extract_map(cbor_value)?;
} }
@@ -570,6 +573,7 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialSource {
let cred_protect_policy = cred_protect_policy let cred_protect_policy = cred_protect_policy
.map(CredentialProtectionPolicy::try_from) .map(CredentialProtectionPolicy::try_from)
.transpose()?; .transpose()?;
let creation_order = creation_order.map(extract_unsigned).unwrap_or(Ok(0))?;
// We don't return whether there were unknown fields in the CBOR value. This means that // We don't return whether there were unknown fields in the CBOR value. This means that
// deserialization is not injective. In particular deserialization is only an inverse of // deserialization is not injective. In particular deserialization is only an inverse of
// serialization at a given version of OpenSK. This is not a problem because: // serialization at a given version of OpenSK. This is not a problem because:
@@ -589,6 +593,7 @@ impl TryFrom<cbor::Value> for PublicKeyCredentialSource {
other_ui, other_ui,
cred_random, cred_random,
cred_protect_policy, cred_protect_policy,
creation_order,
}) })
} }
} }
@@ -605,7 +610,8 @@ impl PublicKeyCredentialSource {
// TODO(kaczmarczyck) we could decide to split this data type up // TODO(kaczmarczyck) we could decide to split this data type up
// It depends on the algorithm though, I think. // It depends on the algorithm though, I think.
// So before creating a mess, this is my workaround. // So before creating a mess, this is my workaround.
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Clone, Debug, PartialEq))] #[derive(Clone)]
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
pub struct CoseKey(pub BTreeMap<cbor::KeyType, cbor::Value>); pub struct CoseKey(pub BTreeMap<cbor::KeyType, cbor::Value>);
// This is the algorithm specifier that is supposed to be used in a COSE key // This is the algorithm specifier that is supposed to be used in a COSE key
@@ -1357,6 +1363,7 @@ mod test {
other_ui: None, other_ui: None,
cred_random: None, cred_random: None,
cred_protect_policy: None, cred_protect_policy: None,
creation_order: 0,
}; };
assert_eq!( assert_eq!(

View File

@@ -200,7 +200,8 @@ impl CtapHid {
// Each transaction is atomic, so we process the command directly here and // Each transaction is atomic, so we process the command directly here and
// don't handle any other packet in the meantime. // don't handle any other packet in the meantime.
// TODO: Send keep-alive packets in the meantime. // TODO: Send keep-alive packets in the meantime.
let response = ctap_state.process_command(&message.payload, cid); let response =
ctap_state.process_command(&message.payload, cid, clock_value);
if let Some(iterator) = CtapHid::split_message(Message { if let Some(iterator) = CtapHid::split_message(Message {
cid, cid,
cmd: CtapHid::COMMAND_CBOR, cmd: CtapHid::COMMAND_CBOR,
@@ -227,44 +228,35 @@ impl CtapHid {
} }
// CTAP specification (version 20190130) section 8.1.9.1.3 // CTAP specification (version 20190130) section 8.1.9.1.3
CtapHid::COMMAND_INIT => { CtapHid::COMMAND_INIT => {
if cid == CtapHid::CHANNEL_BROADCAST { if message.payload.len() != 8 {
if message.payload.len() != 8 { return CtapHid::error_message(cid, CtapHid::ERR_INVALID_LEN);
return CtapHid::error_message(cid, CtapHid::ERR_INVALID_LEN); }
}
let new_cid = if cid == CtapHid::CHANNEL_BROADCAST {
// TODO: Prevent allocating 2^32 channels. // TODO: Prevent allocating 2^32 channels.
self.allocated_cids += 1; self.allocated_cids += 1;
let allocated_cid = (self.allocated_cids as u32).to_ne_bytes(); (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()
} else { } else {
// Sync the channel and discard the current transaction. // Sync the channel and discard the current transaction.
// TODO: The specification (version 20190130) wording isn't clear about cid
// the payload format in this case. };
//
// This unwrap is safe because the payload length is 0 <= 7609 bytes. let mut payload = vec![0; 17];
CtapHid::split_message(Message { payload[..8].copy_from_slice(&message.payload);
cid, payload[8..12].copy_from_slice(&new_cid);
cmd: CtapHid::COMMAND_INIT, payload[12] = CtapHid::PROTOCOL_VERSION;
payload: vec![], payload[13] = CtapHid::DEVICE_VERSION_MAJOR;
}) payload[14] = CtapHid::DEVICE_VERSION_MINOR;
.unwrap() 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 // CTAP specification (version 20190130) section 8.1.9.1.4
CtapHid::COMMAND_PING => { CtapHid::COMMAND_PING => {
@@ -307,7 +299,9 @@ impl CtapHid {
HidPacketIterator::none() HidPacketIterator::none()
} }
Err((cid, error)) => { 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) CtapHid::error_message(cid, CtapHid::ERR_INVALID_CHANNEL)
} else { } else {
match error { match error {
@@ -523,11 +517,32 @@ 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, DUMMY_CLOCK_VALUE);
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] #[test]
fn test_command_init() { fn test_command_init() {
let mut rng = ThreadRng256 {}; let mut rng = ThreadRng256 {};
let user_immediately_present = |_| Ok(()); let user_immediately_present = |_| Ok(());
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present); let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
let mut ctap_hid = CtapHid::new(); let mut ctap_hid = CtapHid::new();
let reply = process_messages( let reply = process_messages(
@@ -568,11 +583,71 @@ 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, DUMMY_CLOCK_VALUE);
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] #[test]
fn test_command_ping() { fn test_command_ping() {
let mut rng = ThreadRng256 {}; let mut rng = ThreadRng256 {};
let user_immediately_present = |_| Ok(()); let user_immediately_present = |_| Ok(());
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present); let mut ctap_state = CtapState::new(&mut rng, user_immediately_present, DUMMY_CLOCK_VALUE);
let mut ctap_hid = CtapHid::new(); let mut ctap_hid = CtapHid::new();
let cid = cid_from_init(&mut ctap_hid, &mut ctap_state); let cid = cid_from_init(&mut ctap_hid, &mut ctap_state);

View File

@@ -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 // TODO: more tests
} }

View File

@@ -12,10 +12,14 @@
// 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.
pub const AAGUID: &[u8; 16] = include_bytes!(concat!(env!("OUT_DIR"), "/opensk_aaguid.bin")); pub const ATTESTATION_PRIVATE_KEY_LENGTH: usize = 32;
pub const AAGUID_LENGTH: usize = 16;
pub const AAGUID: &[u8; AAGUID_LENGTH] =
include_bytes!(concat!(env!("OUT_DIR"), "/opensk_aaguid.bin"));
pub const ATTESTATION_CERTIFICATE: &[u8] = pub const ATTESTATION_CERTIFICATE: &[u8] =
include_bytes!(concat!(env!("OUT_DIR"), "/opensk_cert.bin")); include_bytes!(concat!(env!("OUT_DIR"), "/opensk_cert.bin"));
pub const ATTESTATION_PRIVATE_KEY: &[u8; 32] = pub const ATTESTATION_PRIVATE_KEY: &[u8; ATTESTATION_PRIVATE_KEY_LENGTH] =
include_bytes!(concat!(env!("OUT_DIR"), "/opensk_pkey.bin")); include_bytes!(concat!(env!("OUT_DIR"), "/opensk_pkey.bin"));

File diff suppressed because it is too large Load Diff

View File

@@ -19,7 +19,6 @@ use super::status_code::Ctap2StatusCode;
use super::storage::PersistentStore; use super::storage::PersistentStore;
#[cfg(feature = "with_ctap2_1")] #[cfg(feature = "with_ctap2_1")]
use alloc::string::String; use alloc::string::String;
#[cfg(feature = "with_ctap2_1")]
use alloc::vec; use alloc::vec;
use alloc::vec::Vec; use alloc::vec::Vec;
use arrayref::array_ref; use arrayref::array_ref;
@@ -74,10 +73,9 @@ fn encrypt_hmac_secret_output(
let mut cred_random_secret = [0u8; 32]; let mut cred_random_secret = [0u8; 32];
cred_random_secret.copy_from_slice(cred_random); 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. // 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];
for i in 0..block_len { for i in 0..block_len {
blocks[i].copy_from_slice(&salt_enc[16 * i..16 * (i + 1)]); 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]; let mut decrypted_salt1 = [0u8; 32];
decrypted_salt1[..16].copy_from_slice(&blocks[0]); 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]); decrypted_salt1[16..].copy_from_slice(&blocks[1]);
let output1 = hmac_256::<Sha256>(&cred_random_secret, &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)]);
} }
@@ -638,36 +636,52 @@ impl PinProtocolV1 {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use arrayref::array_refs;
use crypto::rng256::ThreadRng256; use crypto::rng256::ThreadRng256;
// Stores a PIN hash corresponding to the dummy PIN "1234". // Stores a PIN hash corresponding to the dummy PIN "1234".
fn set_standard_pin(persistent_store: &mut PersistentStore) { fn set_standard_pin(persistent_store: &mut PersistentStore) {
let mut pin = [0u8; 64]; let mut pin = [0u8; 64];
pin[0] = 0x31; pin[..4].copy_from_slice(b"1234");
pin[1] = 0x32;
pin[2] = 0x33;
pin[3] = 0x34;
let mut pin_hash = [0u8; 16]; let mut pin_hash = [0u8; 16];
pin_hash.copy_from_slice(&Sha256::hash(&pin[..])[..16]); pin_hash.copy_from_slice(&Sha256::hash(&pin[..])[..16]);
persistent_store.set_pin_hash(&pin_hash).unwrap(); 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. // Fails on PINs bigger than 64 bytes.
fn encrypt_pin(shared_secret: &[u8; 32], pin: Vec<u8>) -> Vec<u8> { fn encrypt_pin(shared_secret: &[u8; 32], pin: Vec<u8>) -> Vec<u8> {
assert!(pin.len() <= 64); assert!(pin.len() <= 64);
let mut padded_pin = [0u8; 64]; let mut padded_pin = [0u8; 64];
padded_pin[..pin.len()].copy_from_slice(&pin[..]); padded_pin[..pin.len()].copy_from_slice(&pin[..]);
let aes_enc_key = crypto::aes256::EncryptionKey::new(shared_secret); encrypt_message(shared_secret, &padded_pin)
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>>()
} }
// Encrypts the dummy PIN "1234". // Encrypts the dummy PIN "1234".
@@ -677,22 +691,10 @@ mod test {
// Encrypts the PIN hash corresponding to the dummy PIN "1234". // Encrypts the PIN hash corresponding to the dummy PIN "1234".
fn encrypt_standard_pin_hash(shared_secret: &[u8; 32]) -> Vec<u8> { 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]; let mut pin = [0u8; 64];
pin[0] = 0x31; pin[..4].copy_from_slice(b"1234");
pin[1] = 0x32;
pin[2] = 0x33;
pin[3] = 0x34;
let pin_hash = Sha256::hash(&pin); let pin_hash = Sha256::hash(&pin);
encrypt_message(shared_secret, &pin_hash[..16])
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
} }
#[test] #[test]
@@ -1184,6 +1186,56 @@ mod test {
output, output,
Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_EXTENSION) 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")] #[cfg(feature = "with_ctap2_1")]

View File

@@ -15,9 +15,9 @@
#[cfg(feature = "with_ctap2_1")] #[cfg(feature = "with_ctap2_1")]
use crate::ctap::data_formats::{extract_array, extract_text_string}; use crate::ctap::data_formats::{extract_array, extract_text_string};
use crate::ctap::data_formats::{CredentialProtectionPolicy, PublicKeyCredentialSource}; use crate::ctap::data_formats::{CredentialProtectionPolicy, PublicKeyCredentialSource};
use crate::ctap::key_material;
use crate::ctap::pin_protocol_v1::PIN_AUTH_LENGTH; use crate::ctap::pin_protocol_v1::PIN_AUTH_LENGTH;
use crate::ctap::status_code::Ctap2StatusCode; use crate::ctap::status_code::Ctap2StatusCode;
use crate::ctap::{key_material, USE_BATCH_ATTESTATION};
use crate::embedded_flash::{self, StoreConfig, StoreEntry, StoreError}; use crate::embedded_flash::{self, StoreConfig, StoreEntry, StoreError};
use alloc::string::String; use alloc::string::String;
#[cfg(any(test, feature = "ram_storage", feature = "with_ctap2_1"))] #[cfg(any(test, feature = "ram_storage", feature = "with_ctap2_1"))]
@@ -76,8 +76,6 @@ const MIN_PIN_LENGTH_RP_IDS: usize = 9;
const NUM_TAGS: usize = 10; const NUM_TAGS: usize = 10;
const MAX_PIN_RETRIES: u8 = 8; const MAX_PIN_RETRIES: u8 = 8;
const ATTESTATION_PRIVATE_KEY_LENGTH: usize = 32;
const AAGUID_LENGTH: usize = 16;
#[cfg(feature = "with_ctap2_1")] #[cfg(feature = "with_ctap2_1")]
const DEFAULT_MIN_PIN_LENGTH: u8 = 4; const DEFAULT_MIN_PIN_LENGTH: u8 = 4;
// TODO(kaczmarczyck) use this for the minPinLength extension // TODO(kaczmarczyck) use this for the minPinLength extension
@@ -231,22 +229,29 @@ impl PersistentStore {
}) })
.unwrap(); .unwrap();
} }
// The following 3 entries are meant to be written by vendor-specific commands. // TODO(jmichel): remove this when vendor command is in place
if USE_BATCH_ATTESTATION { #[cfg(not(test))]
if self.store.find_one(&Key::AttestationPrivateKey).is_none() { self.load_attestation_data_from_firmware();
self.set_attestation_private_key(key_material::ATTESTATION_PRIVATE_KEY)
.unwrap();
}
if self.store.find_one(&Key::AttestationCertificate).is_none() {
self.set_attestation_certificate(key_material::ATTESTATION_CERTIFICATE)
.unwrap();
}
}
if self.store.find_one(&Key::Aaguid).is_none() { if self.store.find_one(&Key::Aaguid).is_none() {
self.set_aaguid(key_material::AAGUID).unwrap(); self.set_aaguid(key_material::AAGUID).unwrap();
} }
} }
// TODO(jmichel): remove this function when vendor command is in place.
#[cfg(not(test))]
fn load_attestation_data_from_firmware(&mut self) {
// The following 2 entries are meant to be written by vendor-specific commands.
if self.store.find_one(&Key::AttestationPrivateKey).is_none() {
self.set_attestation_private_key(key_material::ATTESTATION_PRIVATE_KEY)
.unwrap();
}
if self.store.find_one(&Key::AttestationCertificate).is_none() {
self.set_attestation_certificate(key_material::ATTESTATION_CERTIFICATE)
.unwrap();
}
}
pub fn find_credential( pub fn find_credential(
&self, &self,
rp_id: &str, rp_id: &str,
@@ -337,6 +342,26 @@ impl PersistentStore {
.count()) .count())
} }
pub fn new_creation_order(&self) -> Result<u64, Ctap2StatusCode> {
Ok(self
.store
.find_all(&Key::Credential {
rp_id: None,
credential_id: None,
user_handle: None,
})
.filter_map(|(_, entry)| {
debug_assert_eq!(entry.tag, TAG_CREDENTIAL);
let credential = deserialize_credential(entry.data);
debug_assert!(credential.is_some());
credential
})
.map(|c| c.creation_order)
.max()
.unwrap_or(0)
.wrapping_add(1))
}
pub fn global_signature_counter(&self) -> Result<u32, Ctap2StatusCode> { pub fn global_signature_counter(&self) -> Result<u32, Ctap2StatusCode> {
Ok(self Ok(self
.store .store
@@ -525,29 +550,33 @@ impl PersistentStore {
pub fn attestation_private_key( pub fn attestation_private_key(
&self, &self,
) -> Result<Option<&[u8; ATTESTATION_PRIVATE_KEY_LENGTH]>, Ctap2StatusCode> { ) -> Result<Option<&[u8; key_material::ATTESTATION_PRIVATE_KEY_LENGTH]>, Ctap2StatusCode> {
let data = match self.store.find_one(&Key::AttestationPrivateKey) { let data = match self.store.find_one(&Key::AttestationPrivateKey) {
None => return Ok(None), None => return Ok(None),
Some((_, entry)) => entry.data, Some((_, entry)) => entry.data,
}; };
if data.len() != ATTESTATION_PRIVATE_KEY_LENGTH { if data.len() != key_material::ATTESTATION_PRIVATE_KEY_LENGTH {
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR); return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
} }
Ok(Some(array_ref!(data, 0, ATTESTATION_PRIVATE_KEY_LENGTH))) Ok(Some(array_ref!(
data,
0,
key_material::ATTESTATION_PRIVATE_KEY_LENGTH
)))
} }
pub fn set_attestation_private_key( pub fn set_attestation_private_key(
&mut self, &mut self,
attestation_private_key: &[u8; ATTESTATION_PRIVATE_KEY_LENGTH], attestation_private_key: &[u8; key_material::ATTESTATION_PRIVATE_KEY_LENGTH],
) -> Result<(), Ctap2StatusCode> { ) -> Result<(), Ctap2StatusCode> {
let entry = StoreEntry { let entry = StoreEntry {
tag: ATTESTATION_PRIVATE_KEY, tag: ATTESTATION_PRIVATE_KEY,
data: attestation_private_key, data: attestation_private_key,
sensitive: false, sensitive: true,
}; };
match self.store.find_one(&Key::AttestationPrivateKey) { match self.store.find_one(&Key::AttestationPrivateKey) {
None => self.store.insert(entry)?, None => self.store.insert(entry)?,
Some((index, _)) => self.store.replace(index, entry)?, _ => return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR),
} }
Ok(()) Ok(())
} }
@@ -571,24 +600,27 @@ impl PersistentStore {
}; };
match self.store.find_one(&Key::AttestationCertificate) { match self.store.find_one(&Key::AttestationCertificate) {
None => self.store.insert(entry)?, None => self.store.insert(entry)?,
Some((index, _)) => self.store.replace(index, entry)?, _ => return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR),
} }
Ok(()) Ok(())
} }
pub fn aaguid(&self) -> Result<[u8; AAGUID_LENGTH], Ctap2StatusCode> { pub fn aaguid(&self) -> Result<[u8; key_material::AAGUID_LENGTH], Ctap2StatusCode> {
let (_, entry) = self let (_, entry) = self
.store .store
.find_one(&Key::Aaguid) .find_one(&Key::Aaguid)
.ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?; .ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?;
let data = entry.data; let data = entry.data;
if data.len() != AAGUID_LENGTH { if data.len() != key_material::AAGUID_LENGTH {
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR); return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
} }
Ok(*array_ref![data, 0, AAGUID_LENGTH]) Ok(*array_ref![data, 0, key_material::AAGUID_LENGTH])
} }
pub fn set_aaguid(&mut self, aaguid: &[u8; AAGUID_LENGTH]) -> Result<(), Ctap2StatusCode> { pub fn set_aaguid(
&mut self,
aaguid: &[u8; key_material::AAGUID_LENGTH],
) -> Result<(), Ctap2StatusCode> {
let entry = StoreEntry { let entry = StoreEntry {
tag: AAGUID, tag: AAGUID,
data: aaguid, data: aaguid,
@@ -690,6 +722,7 @@ mod test {
other_ui: None, other_ui: None,
cred_random: None, cred_random: None,
cred_protect_policy: None, cred_protect_policy: None,
creation_order: 0,
} }
} }
@@ -725,6 +758,21 @@ mod test {
assert!(persistent_store.count_credentials().unwrap() > 0); assert!(persistent_store.count_credentials().unwrap() > 0);
} }
#[test]
fn test_credential_order() {
let mut rng = ThreadRng256 {};
let mut persistent_store = PersistentStore::new(&mut rng);
let credential_source = create_credential_source(&mut rng, "example.com", vec![]);
let current_latest_creation = credential_source.creation_order;
assert!(persistent_store.store_credential(credential_source).is_ok());
let mut credential_source = create_credential_source(&mut rng, "example.com", vec![]);
credential_source.creation_order = persistent_store.new_creation_order().unwrap();
assert!(credential_source.creation_order > current_latest_creation);
let current_latest_creation = credential_source.creation_order;
assert!(persistent_store.store_credential(credential_source).is_ok());
assert!(persistent_store.new_creation_order().unwrap() > current_latest_creation);
}
#[test] #[test]
#[allow(clippy::assertions_on_constants)] #[allow(clippy::assertions_on_constants)]
fn test_fill_store() { fn test_fill_store() {
@@ -853,6 +901,7 @@ mod test {
cred_protect_policy: Some( cred_protect_policy: Some(
CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIdList, CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIdList,
), ),
creation_order: 0,
}; };
assert!(persistent_store.store_credential(credential).is_ok()); assert!(persistent_store.store_credential(credential).is_ok());
@@ -894,6 +943,7 @@ mod test {
other_ui: None, other_ui: None,
cred_random: None, cred_random: None,
cred_protect_policy: None, cred_protect_policy: None,
creation_order: 0,
}; };
assert_eq!(found_credential, Some(expected_credential)); assert_eq!(found_credential, Some(expected_credential));
} }
@@ -913,6 +963,7 @@ mod test {
other_ui: None, other_ui: None,
cred_random: None, cred_random: None,
cred_protect_policy: Some(CredentialProtectionPolicy::UserVerificationRequired), cred_protect_policy: Some(CredentialProtectionPolicy::UserVerificationRequired),
creation_order: 0,
}; };
assert!(persistent_store.store_credential(credential).is_ok()); assert!(persistent_store.store_credential(credential).is_ok());
@@ -1090,6 +1141,7 @@ mod test {
other_ui: None, other_ui: None,
cred_random: None, cred_random: None,
cred_protect_policy: None, cred_protect_policy: None,
creation_order: 0,
}; };
let serialized = serialize_credential(credential.clone()).unwrap(); let serialized = serialize_credential(credential.clone()).unwrap();
let reconstructed = deserialize_credential(&serialized).unwrap(); let reconstructed = deserialize_credential(&serialized).unwrap();

View File

@@ -37,9 +37,11 @@ use libtock_drivers::console::Console;
use libtock_drivers::led; use libtock_drivers::led;
use libtock_drivers::result::{FlexUnwrap, TockError}; use libtock_drivers::result::{FlexUnwrap, TockError};
use libtock_drivers::timer; use libtock_drivers::timer;
use libtock_drivers::timer::Duration;
#[cfg(feature = "debug_ctap")] #[cfg(feature = "debug_ctap")]
use libtock_drivers::timer::Timer; use libtock_drivers::timer::Timer;
use libtock_drivers::timer::{Duration, Timestamp}; #[cfg(feature = "debug_ctap")]
use libtock_drivers::timer::Timestamp;
use libtock_drivers::usb_ctap_hid; use libtock_drivers::usb_ctap_hid;
const KEEPALIVE_DELAY_MS: isize = 100; const KEEPALIVE_DELAY_MS: isize = 100;
@@ -57,12 +59,13 @@ fn main() {
panic!("Cannot setup USB driver"); panic!("Cannot setup USB driver");
} }
let boot_time = timer.get_current_clock().flex_unwrap();
let mut rng = TockRng256 {}; let mut rng = TockRng256 {};
let mut ctap_state = CtapState::new(&mut rng, check_user_presence); let mut ctap_state = CtapState::new(&mut rng, check_user_presence, boot_time);
let mut ctap_hid = CtapHid::new(); let mut ctap_hid = CtapHid::new();
let mut led_counter = 0; let mut led_counter = 0;
let mut last_led_increment = timer.get_current_clock().flex_unwrap(); let mut last_led_increment = boot_time;
// Main loop. If CTAP1 is used, we register button presses for U2F while receiving and waiting. // Main loop. If CTAP1 is used, we register button presses for U2F while receiving and waiting.
// The way TockOS and apps currently interact, callbacks need a yield syscall to execute, // The way TockOS and apps currently interact, callbacks need a yield syscall to execute,
@@ -115,7 +118,7 @@ fn main() {
// These calls are making sure that even for long inactivity, wrapping clock values // These calls are making sure that even for long inactivity, wrapping clock values
// never randomly wink or grant user presence for U2F. // never randomly wink or grant user presence for U2F.
ctap_state.check_disable_reset(Timestamp::<isize>::from_clock_value(now)); ctap_state.update_command_permission(now);
ctap_hid.wink_permission = ctap_hid.wink_permission.check_expiration(now); ctap_hid.wink_permission = ctap_hid.wink_permission.check_expiration(now);
if has_packet { if has_packet {