Merge pull request #112 from ia0/aaguid

Access the persistent keys through the store
This commit is contained in:
Julien Cretin
2020-06-22 10:55:14 +02:00
committed by GitHub
7 changed files with 205 additions and 46 deletions

View File

@@ -1,9 +1,9 @@
0b54df6d548849e24d67b9b022ca09cb33c51f078ce85d0c9c4635ffc69902e1 third_party/tock/target/thumbv7em-none-eabi/release/nrf52840dk.bin
9ff63684ca08375e643f14f33dc6dc8131681bb562fb0df18f9c7f637e90cc73 target/nrf52840dk_merged.hex
f49e2205136159671f8291b284fc02300cf659f088a2ca301d74111e0e96849a target/nrf52840dk_merged.hex
052eec0ae526038352b9f7573468d0cf7fb5ec331d4dc1a2df75fdbd514ea5ca third_party/tock/target/thumbv7em-none-eabi/release/nrf52840_dongle.bin
d2373ac9df2ba8feff88f19e67ec87a58e635b94f0a0f759b6fcf4c750b256c9 target/nrf52840_dongle_merged.hex
b35ac62a490c62d4b23dddf1d8e6946badb32b5b35b40bbd75587815530094c9 target/nrf52840_dongle_merged.hex
908d7f4f40936d968b91ab6e19b2406612fe8c2c273d9c0b71ef1f55116780e0 third_party/tock/target/thumbv7em-none-eabi/release/nrf52840_dongle_dfu.bin
3c6f18ad1e1ceedeb622f39cd00ae3328ea5ad1557a9042c1b4bf831d5e1fb0d target/nrf52840_dongle_dfu_merged.hex
1adb9f71697947109020b25ad2b3fb3b03e6a07945dee14351ad67341241205e target/nrf52840_dongle_dfu_merged.hex
34ecbecaebf1188277f2310fe769c8c60310d8576493242712854deb4ba1036e third_party/tock/target/thumbv7em-none-eabi/release/nrf52840_mdk_dfu.bin
d1320adfcec35099ade04988111a947c05d14c43851fc5800d17d7a83bdba033 target/nrf52840_mdk_dfu_merged.hex
c2cbcc28b835934be4c3d3e3c5bdaba642a5811d760c1d2cb73d26b6474e4219 target/tab/ctap2.tab
1661fb4da7cbaf01529e593600f47c4613446a37f400cb0b238249d100a3d9f1 target/nrf52840_mdk_dfu_merged.hex
529ac9aef3941b45e7e480810ae4e821da433985b149028aa6a33f33e0dc1685 target/tab/ctap2.tab

View File

@@ -1,9 +1,9 @@
29382e72d0f3c6a72ce9517211952ff29ea270193d7f0ddc48ca69009ee29925 third_party/tock/target/thumbv7em-none-eabi/release/nrf52840dk.bin
bb2fbf0d9dab2b489a49d1dc3db8923086ab109d14f1f1aa8296f086a03b75dd target/nrf52840dk_merged.hex
e446a94d67f77d5346be6e476641f4ff50561f5a77bfa8bc49262f89e7399893 target/nrf52840dk_merged.hex
30f239390ae9bef0825731e4c82d40470fc5e9bded2bf0d942e92dbb5d4faba1 third_party/tock/target/thumbv7em-none-eabi/release/nrf52840_dongle.bin
c9349bd480b30e28214bb8d58d10938889050b92d34fbeb70e3110919b3a2601 target/nrf52840_dongle_merged.hex
1bf5219f7b096b4ade330e9b02544b09d10972ddf253c7fdfbd6241b03e98f31 target/nrf52840_dongle_merged.hex
e3acf15d5ae3a22aecff6cc58db5fc311f538f47328d348b7ad7db7f9ab5e72c third_party/tock/target/thumbv7em-none-eabi/release/nrf52840_dongle_dfu.bin
08f3ca1bb79e13e83149324244929b68f8d7583630d9a62a8ffdedb710c95d8b target/nrf52840_dongle_dfu_merged.hex
b83edda1b2588e3eff019fc8b2e16097e159f8a43fa5fc62a6e23497882c8dca target/nrf52840_dongle_dfu_merged.hex
cae312a26a513ada6c198fdc59b2bba3860c51726b817a9fd17a4331ee12c882 third_party/tock/target/thumbv7em-none-eabi/release/nrf52840_mdk_dfu.bin
849c67c811da8d359d4e55d81d2587b3efa2f6065d72e4db09c3e571af8fef94 target/nrf52840_mdk_dfu_merged.hex
40b413a8b645b4b47fae62a4311acb12cb0c57faff2757e45c18d9e5d441e52d target/tab/ctap2.tab
d376cb19e672ab80b9dd25e9df40af7ac833d03ede32f4a2ae21fdfd4e31d365 target/nrf52840_mdk_dfu_merged.hex
ba0e11a0036f167a56864de43db3602a8a855b38be8a53afc3a97fcaa40f2201 target/tab/ctap2.tab

View File

@@ -5,8 +5,8 @@ Min RAM size from sections in ELF: 16 bytes
Number of writeable flash regions: 0
Adding .crt0_header section. Offset: 64 (0x40). Length: 64 (0x40) bytes.
Entry point is in .text section
Adding .text section. Offset: 128 (0x80). Length: 175636 (0x2ae14) bytes.
Adding .stack section. Offset: 175764 (0x2ae94). Length: 16384 (0x4000) bytes.
Adding .text section. Offset: 128 (0x80). Length: 171876 (0x29f64) bytes.
Adding .stack section. Offset: 172004 (0x29fe4). Length: 16384 (0x4000) bytes.
Searching for .rel.X sections to add.
TBF Header:
version: 2 0x2
@@ -24,8 +24,8 @@ Min RAM size from sections in ELF: 16 bytes
Number of writeable flash regions: 0
Adding .crt0_header section. Offset: 64 (0x40). Length: 64 (0x40) bytes.
Entry point is in .text section
Adding .text section. Offset: 128 (0x80). Length: 175636 (0x2ae14) bytes.
Adding .stack section. Offset: 175764 (0x2ae94). Length: 16384 (0x4000) bytes.
Adding .text section. Offset: 128 (0x80). Length: 171876 (0x29f64) bytes.
Adding .stack section. Offset: 172004 (0x29fe4). Length: 16384 (0x4000) bytes.
Searching for .rel.X sections to add.
TBF Header:
version: 2 0x2
@@ -43,8 +43,8 @@ Min RAM size from sections in ELF: 16 bytes
Number of writeable flash regions: 0
Adding .crt0_header section. Offset: 64 (0x40). Length: 64 (0x40) bytes.
Entry point is in .text section
Adding .text section. Offset: 128 (0x80). Length: 175636 (0x2ae14) bytes.
Adding .stack section. Offset: 175764 (0x2ae94). Length: 16384 (0x4000) bytes.
Adding .text section. Offset: 128 (0x80). Length: 171876 (0x29f64) bytes.
Adding .stack section. Offset: 172004 (0x29fe4). Length: 16384 (0x4000) bytes.
Searching for .rel.X sections to add.
TBF Header:
version: 2 0x2
@@ -62,8 +62,8 @@ Min RAM size from sections in ELF: 16 bytes
Number of writeable flash regions: 0
Adding .crt0_header section. Offset: 64 (0x40). Length: 64 (0x40) bytes.
Entry point is in .text section
Adding .text section. Offset: 128 (0x80). Length: 175636 (0x2ae14) bytes.
Adding .stack section. Offset: 175764 (0x2ae94). Length: 16384 (0x4000) bytes.
Adding .text section. Offset: 128 (0x80). Length: 171876 (0x29f64) bytes.
Adding .stack section. Offset: 172004 (0x29fe4). Length: 16384 (0x4000) bytes.
Searching for .rel.X sections to add.
TBF Header:
version: 2 0x2

View File

@@ -5,8 +5,8 @@ Min RAM size from sections in ELF: 16 bytes
Number of writeable flash regions: 0
Adding .crt0_header section. Offset: 64 (0x40). Length: 64 (0x40) bytes.
Entry point is in .text section
Adding .text section. Offset: 128 (0x80). Length: 175252 (0x2ac94) bytes.
Adding .stack section. Offset: 175380 (0x2ad14). Length: 16384 (0x4000) bytes.
Adding .text section. Offset: 128 (0x80). Length: 171316 (0x29d34) bytes.
Adding .stack section. Offset: 171444 (0x29db4). Length: 16384 (0x4000) bytes.
Searching for .rel.X sections to add.
TBF Header:
version: 2 0x2
@@ -24,8 +24,8 @@ Min RAM size from sections in ELF: 16 bytes
Number of writeable flash regions: 0
Adding .crt0_header section. Offset: 64 (0x40). Length: 64 (0x40) bytes.
Entry point is in .text section
Adding .text section. Offset: 128 (0x80). Length: 175252 (0x2ac94) bytes.
Adding .stack section. Offset: 175380 (0x2ad14). Length: 16384 (0x4000) bytes.
Adding .text section. Offset: 128 (0x80). Length: 171316 (0x29d34) bytes.
Adding .stack section. Offset: 171444 (0x29db4). Length: 16384 (0x4000) bytes.
Searching for .rel.X sections to add.
TBF Header:
version: 2 0x2
@@ -43,8 +43,8 @@ Min RAM size from sections in ELF: 16 bytes
Number of writeable flash regions: 0
Adding .crt0_header section. Offset: 64 (0x40). Length: 64 (0x40) bytes.
Entry point is in .text section
Adding .text section. Offset: 128 (0x80). Length: 175252 (0x2ac94) bytes.
Adding .stack section. Offset: 175380 (0x2ad14). Length: 16384 (0x4000) bytes.
Adding .text section. Offset: 128 (0x80). Length: 171316 (0x29d34) bytes.
Adding .stack section. Offset: 171444 (0x29db4). Length: 16384 (0x4000) bytes.
Searching for .rel.X sections to add.
TBF Header:
version: 2 0x2
@@ -62,8 +62,8 @@ Min RAM size from sections in ELF: 16 bytes
Number of writeable flash regions: 0
Adding .crt0_header section. Offset: 64 (0x40). Length: 64 (0x40) bytes.
Entry point is in .text section
Adding .text section. Offset: 128 (0x80). Length: 175252 (0x2ac94) bytes.
Adding .stack section. Offset: 175380 (0x2ad14). Length: 16384 (0x4000) bytes.
Adding .text section. Offset: 128 (0x80). Length: 171316 (0x29d34) bytes.
Adding .stack section. Offset: 171444 (0x29db4). Length: 16384 (0x4000) bytes.
Searching for .rel.X sections to add.
TBF Header:
version: 2 0x2

View File

@@ -38,7 +38,6 @@ use self::data_formats::{
SignatureAlgorithm,
};
use self::hid::ChannelID;
use self::key_material::{AAGUID, ATTESTATION_CERTIFICATE, ATTESTATION_PRIVATE_KEY};
use self::response::{
AuthenticatorClientPinResponse, AuthenticatorGetAssertionResponse,
AuthenticatorGetInfoResponse, AuthenticatorMakeCredentialResponse, ResponseData,
@@ -529,7 +528,7 @@ where
};
let mut auth_data = self.generate_auth_data(&rp_id_hash, flags);
auth_data.extend(AAGUID);
auth_data.extend(self.persistent_store.aaguid()?);
// The length is fixed to 0x20 or 0x70 and fits one byte.
if credential_id.len() > 0xFF {
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_RESPONSE_TOO_LONG);
@@ -554,18 +553,25 @@ where
let mut signature_data = auth_data.clone();
signature_data.extend(client_data_hash);
let (signature, x5c) = if USE_BATCH_ATTESTATION {
// We currently use the presence of the attestation private key in the persistent storage to
// decide whether batch attestation is needed.
let (signature, x5c) = match self.persistent_store.attestation_private_key()? {
Some(attestation_private_key) => {
let attestation_key =
crypto::ecdsa::SecKey::from_bytes(ATTESTATION_PRIVATE_KEY).unwrap();
crypto::ecdsa::SecKey::from_bytes(attestation_private_key).unwrap();
let attestation_certificate = self
.persistent_store
.attestation_certificate()?
.ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?;
(
attestation_key.sign_rfc6979::<crypto::sha256::Sha256>(&signature_data),
Some(vec![ATTESTATION_CERTIFICATE.to_vec()]),
Some(vec![attestation_certificate]),
)
} else {
(
}
None => (
sk.sign_rfc6979::<crypto::sha256::Sha256>(&signature_data),
None,
)
),
};
let attestation_statement = PackedAttestationStatement {
alg: SignatureAlgorithm::ES256 as i64,
@@ -792,7 +798,7 @@ where
String::from(FIDO2_VERSION_STRING),
],
extensions: Some(vec![String::from("hmac-secret")]),
aaguid: *AAGUID,
aaguid: *self.persistent_store.aaguid()?,
options: Some(options_map),
max_msg_size: Some(1024),
pin_protocols: Some(vec![
@@ -1148,7 +1154,7 @@ mod test {
0x02, 0x81, 0x6B, 0x68, 0x6D, 0x61, 0x63, 0x2D, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74,
0x03, 0x50,
]);
expected_response.extend(AAGUID);
expected_response.extend(ctap_state.persistent_store.aaguid().unwrap());
expected_response.extend(&[
0x04, 0xA3, 0x62, 0x72, 0x6B, 0xF5, 0x62, 0x75, 0x70, 0xF5, 0x69, 0x63, 0x6C, 0x69,
0x65, 0x6E, 0x74, 0x50, 0x69, 0x6E, 0xF4, 0x05, 0x19, 0x04, 0x00, 0x06, 0x81, 0x01,
@@ -1247,7 +1253,7 @@ mod test {
0x34, 0xE2, 0x75, 0x1E, 0x68, 0x2F, 0xAB, 0x9F, 0x2D, 0x30, 0xAB, 0x13, 0xD2,
0x12, 0x55, 0x86, 0xCE, 0x19, 0x47, 0x41, 0x00, 0x00, 0x00, 0x00,
];
expected_auth_data.extend(AAGUID);
expected_auth_data.extend(ctap_state.persistent_store.aaguid().unwrap());
expected_auth_data.extend(&[0x00, 0x20]);
assert_eq!(
auth_data[0..expected_auth_data.len()],
@@ -1284,7 +1290,7 @@ mod test {
0x34, 0xE2, 0x75, 0x1E, 0x68, 0x2F, 0xAB, 0x9F, 0x2D, 0x30, 0xAB, 0x13, 0xD2,
0x12, 0x55, 0x86, 0xCE, 0x19, 0x47, 0x41, 0x00, 0x00, 0x00, 0x00,
];
expected_auth_data.extend(AAGUID);
expected_auth_data.extend(ctap_state.persistent_store.aaguid().unwrap());
expected_auth_data.extend(&[0x00, ENCRYPTED_CREDENTIAL_ID_SIZE as u8]);
assert_eq!(
auth_data[0..expected_auth_data.len()],
@@ -1427,7 +1433,7 @@ mod test {
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(AAGUID);
expected_auth_data.extend(ctap_state.persistent_store.aaguid().unwrap());
expected_auth_data.extend(&[0x00, 0x20]);
assert_eq!(
auth_data[0..expected_auth_data.len()],

View File

@@ -67,5 +67,11 @@ pub enum Ctap2StatusCode {
// CTAP2_ERR_VENDOR_FIRST = 0xF0,
CTAP2_ERR_VENDOR_RESPONSE_TOO_LONG = 0xF0,
CTAP2_ERR_VENDOR_RESPONSE_CANNOT_WRITE_CBOR = 0xF1,
/// An internal invariant is broken.
///
/// This type of error is unexpected and the current state is undefined.
CTAP2_ERR_VENDOR_INTERNAL_ERROR = 0xF2,
CTAP2_ERR_VENDOR_LAST = 0xFF,
}

View File

@@ -15,7 +15,7 @@
use crate::crypto::rng256::Rng256;
use crate::ctap::data_formats::{CredentialProtectionPolicy, PublicKeyCredentialSource};
use crate::ctap::status_code::Ctap2StatusCode;
use crate::ctap::PIN_AUTH_LENGTH;
use crate::ctap::{key_material, PIN_AUTH_LENGTH, USE_BATCH_ATTESTATION};
use alloc::string::String;
use alloc::vec::Vec;
use core::convert::TryInto;
@@ -56,9 +56,14 @@ const GLOBAL_SIGNATURE_COUNTER: usize = 1;
const MASTER_KEYS: usize = 2;
const PIN_HASH: usize = 3;
const PIN_RETRIES: usize = 4;
const NUM_TAGS: usize = 5;
const ATTESTATION_PRIVATE_KEY: usize = 5;
const ATTESTATION_CERTIFICATE: usize = 6;
const AAGUID: usize = 7;
const NUM_TAGS: usize = 8;
const MAX_PIN_RETRIES: u8 = 6;
const ATTESTATION_PRIVATE_KEY_LENGTH: usize = 32;
const AAGUID_LENGTH: usize = 16;
#[derive(PartialEq, Eq, PartialOrd, Ord)]
enum Key {
@@ -73,6 +78,9 @@ enum Key {
MasterKeys,
PinHash,
PinRetries,
AttestationPrivateKey,
AttestationCertificate,
Aaguid,
}
pub struct MasterKeys<'a> {
@@ -124,6 +132,9 @@ impl StoreConfig for Config {
MASTER_KEYS => add(Key::MasterKeys),
PIN_HASH => add(Key::PinHash),
PIN_RETRIES => add(Key::PinRetries),
ATTESTATION_PRIVATE_KEY => add(Key::AttestationPrivateKey),
ATTESTATION_CERTIFICATE => add(Key::AttestationCertificate),
AAGUID => add(Key::Aaguid),
_ => debug_assert!(false),
}
}
@@ -197,6 +208,20 @@ impl PersistentStore {
})
.unwrap();
}
// The following 3 entries are meant to be written by vendor-specific commands.
if USE_BATCH_ATTESTATION {
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();
}
}
if self.store.find_one(&Key::Aaguid).is_none() {
self.set_aaguid(key_material::AAGUID).unwrap();
}
}
pub fn find_credential(
@@ -395,10 +420,88 @@ impl PersistentStore {
.unwrap();
}
pub fn attestation_private_key(
&self,
) -> Result<Option<&[u8; ATTESTATION_PRIVATE_KEY_LENGTH]>, Ctap2StatusCode> {
let data = match self.store.find_one(&Key::AttestationPrivateKey) {
None => return Ok(None),
Some((_, entry)) => entry.data,
};
if data.len() != ATTESTATION_PRIVATE_KEY_LENGTH {
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
}
Ok(Some(array_ref!(data, 0, ATTESTATION_PRIVATE_KEY_LENGTH)))
}
pub fn set_attestation_private_key(
&mut self,
attestation_private_key: &[u8; ATTESTATION_PRIVATE_KEY_LENGTH],
) -> Result<(), Ctap2StatusCode> {
let entry = StoreEntry {
tag: ATTESTATION_PRIVATE_KEY,
data: attestation_private_key,
sensitive: false,
};
match self.store.find_one(&Key::AttestationPrivateKey) {
None => self.store.insert(entry)?,
Some((index, _)) => self.store.replace(index, entry)?,
}
Ok(())
}
pub fn attestation_certificate(&self) -> Result<Option<Vec<u8>>, Ctap2StatusCode> {
let data = match self.store.find_one(&Key::AttestationCertificate) {
None => return Ok(None),
Some((_, entry)) => entry.data,
};
Ok(Some(data.to_vec()))
}
pub fn set_attestation_certificate(
&mut self,
attestation_certificate: &[u8],
) -> Result<(), Ctap2StatusCode> {
let entry = StoreEntry {
tag: ATTESTATION_CERTIFICATE,
data: attestation_certificate,
sensitive: false,
};
match self.store.find_one(&Key::AttestationCertificate) {
None => self.store.insert(entry)?,
Some((index, _)) => self.store.replace(index, entry)?,
}
Ok(())
}
pub fn aaguid(&self) -> Result<&[u8; AAGUID_LENGTH], Ctap2StatusCode> {
let (_, entry) = self
.store
.find_one(&Key::Aaguid)
.ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?;
let data = entry.data;
if data.len() != AAGUID_LENGTH {
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
}
Ok(array_ref!(data, 0, AAGUID_LENGTH))
}
pub fn set_aaguid(&mut self, aaguid: &[u8; AAGUID_LENGTH]) -> Result<(), Ctap2StatusCode> {
let entry = StoreEntry {
tag: AAGUID,
data: aaguid,
sensitive: false,
};
match self.store.find_one(&Key::Aaguid) {
None => self.store.insert(entry)?,
Some((index, _)) => self.store.replace(index, entry)?,
}
Ok(())
}
pub fn reset(&mut self, rng: &mut impl Rng256) {
loop {
let index = {
let mut iter = self.store.iter();
let mut iter = self.store.iter().filter(|(_, entry)| should_reset(entry));
match iter.next() {
None => break,
Some((index, _)) => index,
@@ -420,6 +523,13 @@ impl From<StoreError> for Ctap2StatusCode {
}
}
fn should_reset(entry: &StoreEntry<'_>) -> bool {
match entry.tag {
ATTESTATION_PRIVATE_KEY | ATTESTATION_CERTIFICATE | AAGUID => false,
_ => true,
}
}
fn deserialize_credential(data: &[u8]) -> Option<PublicKeyCredentialSource> {
let cbor = cbor::read(data).ok()?;
cbor.try_into().ok()
@@ -745,4 +855,41 @@ mod test {
persistent_store.reset_pin_retries();
assert_eq!(persistent_store.pin_retries(), MAX_PIN_RETRIES);
}
#[test]
fn test_persistent_keys() {
let mut rng = ThreadRng256 {};
let mut persistent_store = PersistentStore::new(&mut rng);
// Make sure the attestation are absent. There is no batch attestation in tests.
assert!(persistent_store
.attestation_private_key()
.unwrap()
.is_none());
assert!(persistent_store
.attestation_certificate()
.unwrap()
.is_none());
// Make sure the persistent keys are initialized.
persistent_store
.set_attestation_private_key(key_material::ATTESTATION_PRIVATE_KEY)
.unwrap();
persistent_store
.set_attestation_certificate(key_material::ATTESTATION_CERTIFICATE)
.unwrap();
assert_eq!(persistent_store.aaguid().unwrap(), key_material::AAGUID);
// The persistent keys stay initialized and preserve their value after a reset.
persistent_store.reset(&mut rng);
assert_eq!(
persistent_store.attestation_private_key().unwrap().unwrap(),
key_material::ATTESTATION_PRIVATE_KEY
);
assert_eq!(
persistent_store.attestation_certificate().unwrap().unwrap(),
key_material::ATTESTATION_CERTIFICATE
);
assert_eq!(persistent_store.aaguid().unwrap(), key_material::AAGUID);
}
}