Merge pull request #222 from jmichelp/transparency
Ensure CTAP1 uses the storage to access attestation material
This commit is contained in:
@@ -13,7 +13,6 @@
|
||||
// limitations under the License.
|
||||
|
||||
use super::hid::ChannelID;
|
||||
use super::key_material::{ATTESTATION_CERTIFICATE, ATTESTATION_PRIVATE_KEY};
|
||||
use super::status_code::Ctap2StatusCode;
|
||||
use super::CtapState;
|
||||
use alloc::vec::Vec;
|
||||
@@ -36,6 +35,8 @@ pub enum Ctap1StatusCode {
|
||||
SW_WRONG_LENGTH = 0x6700,
|
||||
SW_CLA_NOT_SUPPORTED = 0x6E00,
|
||||
SW_INS_NOT_SUPPORTED = 0x6D00,
|
||||
SW_MEMERR = 0x6501,
|
||||
SW_COMMAND_ABORTED = 0x6F00,
|
||||
SW_VENDOR_KEY_HANDLE_TOO_LONG = 0xF000,
|
||||
}
|
||||
|
||||
@@ -50,6 +51,8 @@ impl TryFrom<u16> for Ctap1StatusCode {
|
||||
0x6700 => Ok(Ctap1StatusCode::SW_WRONG_LENGTH),
|
||||
0x6E00 => Ok(Ctap1StatusCode::SW_CLA_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),
|
||||
_ => Err(()),
|
||||
}
|
||||
@@ -289,20 +292,30 @@ impl Ctap1Command {
|
||||
let pk = sk.genpk();
|
||||
let key_handle = ctap_state
|
||||
.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 {
|
||||
// This is just being defensive with unreachable code.
|
||||
return Err(Ctap1StatusCode::SW_VENDOR_KEY_HANDLE_TOO_LONG);
|
||||
}
|
||||
|
||||
let mut response =
|
||||
Vec::with_capacity(105 + key_handle.len() + ATTESTATION_CERTIFICATE.len());
|
||||
let certificate = ctap_state
|
||||
.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);
|
||||
let user_pk = pk.to_uncompressed();
|
||||
response.extend_from_slice(&user_pk);
|
||||
response.push(key_handle.len() as u8);
|
||||
response.extend(key_handle.clone());
|
||||
response.extend_from_slice(&ATTESTATION_CERTIFICATE);
|
||||
response.extend_from_slice(&certificate);
|
||||
|
||||
// The first byte is reserved.
|
||||
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_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);
|
||||
|
||||
response.extend(signature.to_asn1_der());
|
||||
@@ -373,7 +386,7 @@ impl Ctap1Command {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::{CREDENTIAL_ID_BASE_SIZE, USE_SIGNATURE_COUNTER};
|
||||
use super::super::{key_material, CREDENTIAL_ID_BASE_SIZE, USE_SIGNATURE_COUNTER};
|
||||
use super::*;
|
||||
use crypto::rng256::ThreadRng256;
|
||||
use crypto::Hash256;
|
||||
@@ -433,9 +446,30 @@ mod test {
|
||||
let message = create_register_message(&application);
|
||||
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 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 =
|
||||
Ctap1Command::process_command(&message, &mut ctap_state, START_CLOCK_VALUE).unwrap();
|
||||
|
||||
assert_eq!(response[0], Ctap1Command::LEGACY_BYTE);
|
||||
assert_eq!(response[66], CREDENTIAL_ID_BASE_SIZE as u8);
|
||||
assert!(ctap_state
|
||||
@@ -447,8 +481,8 @@ mod test {
|
||||
.is_some());
|
||||
const CERT_START: usize = 67 + CREDENTIAL_ID_BASE_SIZE;
|
||||
assert_eq!(
|
||||
&response[CERT_START..CERT_START + ATTESTATION_CERTIFICATE.len()],
|
||||
&ATTESTATION_CERTIFICATE[..]
|
||||
&response[CERT_START..CERT_START + fake_cert.len()],
|
||||
&fake_cert[..]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,10 +12,14 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// 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] =
|
||||
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"));
|
||||
|
||||
@@ -522,25 +522,27 @@ where
|
||||
|
||||
let mut signature_data = auth_data.clone();
|
||||
signature_data.extend(client_data_hash);
|
||||
// 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();
|
||||
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]),
|
||||
)
|
||||
}
|
||||
None => (
|
||||
|
||||
let (signature, x5c) = if USE_BATCH_ATTESTATION {
|
||||
let attestation_private_key = self
|
||||
.persistent_store
|
||||
.attestation_private_key()?
|
||||
.ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?;
|
||||
let attestation_key =
|
||||
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]),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
sk.sign_rfc6979::<crypto::sha256::Sha256>(&signature_data),
|
||||
None,
|
||||
),
|
||||
)
|
||||
};
|
||||
let attestation_statement = PackedAttestationStatement {
|
||||
alg: SignatureAlgorithm::ES256 as i64,
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
#[cfg(feature = "with_ctap2_1")]
|
||||
use crate::ctap::data_formats::{extract_array, extract_text_string};
|
||||
use crate::ctap::data_formats::{CredentialProtectionPolicy, PublicKeyCredentialSource};
|
||||
use crate::ctap::key_material;
|
||||
use crate::ctap::pin_protocol_v1::PIN_AUTH_LENGTH;
|
||||
use crate::ctap::status_code::Ctap2StatusCode;
|
||||
use crate::ctap::{key_material, USE_BATCH_ATTESTATION};
|
||||
use crate::embedded_flash::{self, StoreConfig, StoreEntry, StoreError};
|
||||
use alloc::string::String;
|
||||
#[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 MAX_PIN_RETRIES: u8 = 8;
|
||||
const ATTESTATION_PRIVATE_KEY_LENGTH: usize = 32;
|
||||
const AAGUID_LENGTH: usize = 16;
|
||||
#[cfg(feature = "with_ctap2_1")]
|
||||
const DEFAULT_MIN_PIN_LENGTH: u8 = 4;
|
||||
// TODO(kaczmarczyck) use this for the minPinLength extension
|
||||
@@ -231,22 +229,29 @@ 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();
|
||||
}
|
||||
}
|
||||
// TODO(jmichel): remove this when vendor command is in place
|
||||
#[cfg(not(test))]
|
||||
self.load_attestation_data_from_firmware();
|
||||
|
||||
if self.store.find_one(&Key::Aaguid).is_none() {
|
||||
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(
|
||||
&self,
|
||||
rp_id: &str,
|
||||
@@ -525,29 +530,33 @@ impl PersistentStore {
|
||||
|
||||
pub fn attestation_private_key(
|
||||
&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) {
|
||||
None => return Ok(None),
|
||||
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);
|
||||
}
|
||||
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(
|
||||
&mut self,
|
||||
attestation_private_key: &[u8; ATTESTATION_PRIVATE_KEY_LENGTH],
|
||||
attestation_private_key: &[u8; key_material::ATTESTATION_PRIVATE_KEY_LENGTH],
|
||||
) -> Result<(), Ctap2StatusCode> {
|
||||
let entry = StoreEntry {
|
||||
tag: ATTESTATION_PRIVATE_KEY,
|
||||
data: attestation_private_key,
|
||||
sensitive: false,
|
||||
sensitive: true,
|
||||
};
|
||||
match self.store.find_one(&Key::AttestationPrivateKey) {
|
||||
None => self.store.insert(entry)?,
|
||||
Some((index, _)) => self.store.replace(index, entry)?,
|
||||
_ => return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -571,24 +580,27 @@ impl PersistentStore {
|
||||
};
|
||||
match self.store.find_one(&Key::AttestationCertificate) {
|
||||
None => self.store.insert(entry)?,
|
||||
Some((index, _)) => self.store.replace(index, entry)?,
|
||||
_ => return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn aaguid(&self) -> Result<[u8; AAGUID_LENGTH], Ctap2StatusCode> {
|
||||
pub fn aaguid(&self) -> Result<[u8; key_material::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 {
|
||||
if data.len() != key_material::AAGUID_LENGTH {
|
||||
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 {
|
||||
tag: AAGUID,
|
||||
data: aaguid,
|
||||
|
||||
Reference in New Issue
Block a user