Merge branch 'master' into get-next-assertion
This commit is contained in:
@@ -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(()),
|
||||||
}
|
}
|
||||||
@@ -289,20 +292,30 @@ impl Ctap1Command {
|
|||||||
let pk = sk.genpk();
|
let pk = sk.genpk();
|
||||||
let key_handle = ctap_state
|
let key_handle = ctap_state
|
||||||
.encrypt_key_handle(sk, &application, None)
|
.encrypt_key_handle(sk, &application, 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::{CREDENTIAL_ID_BASE_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;
|
||||||
@@ -433,9 +446,30 @@ mod test {
|
|||||||
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], CREDENTIAL_ID_BASE_SIZE as u8);
|
assert_eq!(response[66], CREDENTIAL_ID_BASE_SIZE as u8);
|
||||||
assert!(ctap_state
|
assert!(ctap_state
|
||||||
@@ -447,8 +481,8 @@ mod test {
|
|||||||
.is_some());
|
.is_some());
|
||||||
const CERT_START: usize = 67 + CREDENTIAL_ID_BASE_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[..]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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"));
|
||||||
|
|||||||
@@ -573,10 +573,12 @@ where
|
|||||||
|
|
||||||
let mut signature_data = auth_data.clone();
|
let mut signature_data = auth_data.clone();
|
||||||
signature_data.extend(client_data_hash);
|
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) = if USE_BATCH_ATTESTATION {
|
||||||
let (signature, x5c) = match self.persistent_store.attestation_private_key()? {
|
let attestation_private_key = self
|
||||||
Some(attestation_private_key) => {
|
.persistent_store
|
||||||
|
.attestation_private_key()?
|
||||||
|
.ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?;
|
||||||
let attestation_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
|
let attestation_certificate = self
|
||||||
@@ -587,11 +589,11 @@ where
|
|||||||
attestation_key.sign_rfc6979::<crypto::sha256::Sha256>(&signature_data),
|
attestation_key.sign_rfc6979::<crypto::sha256::Sha256>(&signature_data),
|
||||||
Some(vec![attestation_certificate]),
|
Some(vec![attestation_certificate]),
|
||||||
)
|
)
|
||||||
}
|
} else {
|
||||||
None => (
|
(
|
||||||
sk.sign_rfc6979::<crypto::sha256::Sha256>(&signature_data),
|
sk.sign_rfc6979::<crypto::sha256::Sha256>(&signature_data),
|
||||||
None,
|
None,
|
||||||
),
|
)
|
||||||
};
|
};
|
||||||
let attestation_statement = PackedAttestationStatement {
|
let attestation_statement = PackedAttestationStatement {
|
||||||
alg: SignatureAlgorithm::ES256 as i64,
|
alg: SignatureAlgorithm::ES256 as i64,
|
||||||
|
|||||||
@@ -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,8 +229,19 @@ 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))]
|
||||||
|
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() {
|
if self.store.find_one(&Key::AttestationPrivateKey).is_none() {
|
||||||
self.set_attestation_private_key(key_material::ATTESTATION_PRIVATE_KEY)
|
self.set_attestation_private_key(key_material::ATTESTATION_PRIVATE_KEY)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -242,10 +251,6 @@ impl PersistentStore {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if self.store.find_one(&Key::Aaguid).is_none() {
|
|
||||||
self.set_aaguid(key_material::AAGUID).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn find_credential(
|
pub fn find_credential(
|
||||||
&self,
|
&self,
|
||||||
@@ -545,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(())
|
||||||
}
|
}
|
||||||
@@ -591,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,
|
||||||
|
|||||||
Reference in New Issue
Block a user