Abstract attestation support
This commit is contained in:
110
src/api/attestation_store.rs
Normal file
110
src/api/attestation_store.rs
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
use alloc::string::String;
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
use persistent_store::{StoreError, StoreUpdate};
|
||||||
|
|
||||||
|
use crate::env::Env;
|
||||||
|
|
||||||
|
/// Identifies an attestation.
|
||||||
|
pub enum Id {
|
||||||
|
Batch,
|
||||||
|
Enterprise { rp_id: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "std", derive(Debug, PartialEq, Eq))]
|
||||||
|
pub struct Attestation {
|
||||||
|
/// ECDSA private key (big-endian).
|
||||||
|
pub private_key: [u8; 32],
|
||||||
|
pub certificate: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stores enterprise or batch attestations.
|
||||||
|
///
|
||||||
|
/// Implementations don't need to distinguish different attestations. In particular, setting one
|
||||||
|
/// attestation may set other ones.
|
||||||
|
pub trait AttestationStore {
|
||||||
|
/// Returns an attestation given its id, if it exists.
|
||||||
|
///
|
||||||
|
/// This should always return the attestation. Checking whether it is ok to use the attestation
|
||||||
|
/// is done in the CTAP library.
|
||||||
|
fn get(&mut self, id: &Id) -> Result<Option<Attestation>, Error>;
|
||||||
|
|
||||||
|
/// Sets the attestation for a given id.
|
||||||
|
///
|
||||||
|
/// This function may not be supported.
|
||||||
|
fn set(&mut self, id: &Id, attestation: Option<&Attestation>) -> Result<(), Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attestation store errors.
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum Error {
|
||||||
|
Storage,
|
||||||
|
Internal,
|
||||||
|
NoSupport,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Keys of the environment store reserved for the attestation store.
|
||||||
|
pub const STORAGE_KEYS: &[usize] = &[1, 2];
|
||||||
|
|
||||||
|
/// Implements a default attestation store using the environment store.
|
||||||
|
///
|
||||||
|
/// The same attestation is used for batch and enterprise.
|
||||||
|
pub trait Helper: Env {}
|
||||||
|
|
||||||
|
impl<T: Helper> AttestationStore for T {
|
||||||
|
fn get(&mut self, _: &Id) -> Result<Option<Attestation>, Error> {
|
||||||
|
let private_key = self.store().find(PRIVATE_KEY_STORAGE_KEY)?;
|
||||||
|
let certificate = self.store().find(CERTIFICATE_STORAGE_KEY)?;
|
||||||
|
let (private_key, certificate) = match (private_key, certificate) {
|
||||||
|
(Some(x), Some(y)) => (x, y),
|
||||||
|
(None, None) => return Ok(None),
|
||||||
|
_ => return Err(Error::Internal),
|
||||||
|
};
|
||||||
|
if private_key.len() != 32 {
|
||||||
|
return Err(Error::Internal);
|
||||||
|
}
|
||||||
|
Ok(Some(Attestation {
|
||||||
|
private_key: *array_ref![private_key, 0, 32],
|
||||||
|
certificate,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set(&mut self, _: &Id, attestation: Option<&Attestation>) -> Result<(), Error> {
|
||||||
|
let updates = match attestation {
|
||||||
|
None => [
|
||||||
|
StoreUpdate::Remove {
|
||||||
|
key: PRIVATE_KEY_STORAGE_KEY,
|
||||||
|
},
|
||||||
|
StoreUpdate::Remove {
|
||||||
|
key: CERTIFICATE_STORAGE_KEY,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
Some(attestation) => [
|
||||||
|
StoreUpdate::Insert {
|
||||||
|
key: PRIVATE_KEY_STORAGE_KEY,
|
||||||
|
value: &attestation.private_key[..],
|
||||||
|
},
|
||||||
|
StoreUpdate::Insert {
|
||||||
|
key: CERTIFICATE_STORAGE_KEY,
|
||||||
|
value: &attestation.certificate[..],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
Ok(self.store().transaction(&updates)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const PRIVATE_KEY_STORAGE_KEY: usize = STORAGE_KEYS[0];
|
||||||
|
const CERTIFICATE_STORAGE_KEY: usize = STORAGE_KEYS[1];
|
||||||
|
|
||||||
|
impl From<StoreError> for Error {
|
||||||
|
fn from(error: StoreError) -> Self {
|
||||||
|
match error {
|
||||||
|
StoreError::InvalidArgument
|
||||||
|
| StoreError::NoCapacity
|
||||||
|
| StoreError::NoLifetime
|
||||||
|
| StoreError::InvalidStorage => Error::Internal,
|
||||||
|
StoreError::StorageError => Error::Storage,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
//! The [environment](crate::env::Env) is split into components. Each component has an API described
|
//! The [environment](crate::env::Env) is split into components. Each component has an API described
|
||||||
//! by a trait. This module gathers the API of those components.
|
//! by a trait. This module gathers the API of those components.
|
||||||
|
|
||||||
|
pub mod attestation_store;
|
||||||
pub mod connection;
|
pub mod connection;
|
||||||
pub mod customization;
|
pub mod customization;
|
||||||
pub mod firmware_protection;
|
pub mod firmware_protection;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ use super::super::clock::CtapInstant;
|
|||||||
use super::apdu::{Apdu, ApduStatusCode};
|
use super::apdu::{Apdu, ApduStatusCode};
|
||||||
use super::crypto_wrapper::{decrypt_credential_source, encrypt_key_handle, PrivateKey};
|
use super::crypto_wrapper::{decrypt_credential_source, encrypt_key_handle, PrivateKey};
|
||||||
use super::CtapState;
|
use super::CtapState;
|
||||||
use crate::ctap::storage;
|
use crate::api::attestation_store::{self, Attestation, AttestationStore};
|
||||||
use crate::env::Env;
|
use crate::env::Env;
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use arrayref::array_ref;
|
use arrayref::array_ref;
|
||||||
@@ -257,12 +257,14 @@ impl Ctap1Command {
|
|||||||
return Err(Ctap1StatusCode::SW_INTERNAL_EXCEPTION);
|
return Err(Ctap1StatusCode::SW_INTERNAL_EXCEPTION);
|
||||||
}
|
}
|
||||||
|
|
||||||
let certificate = storage::attestation_certificate(env)
|
let Attestation {
|
||||||
|
private_key,
|
||||||
|
certificate,
|
||||||
|
} = env
|
||||||
|
.attestation_store()
|
||||||
|
.get(&attestation_store::Id::Batch)
|
||||||
.map_err(|_| Ctap1StatusCode::SW_MEMERR)?
|
.map_err(|_| Ctap1StatusCode::SW_MEMERR)?
|
||||||
.ok_or(Ctap1StatusCode::SW_INTERNAL_EXCEPTION)?;
|
.ok_or(Ctap1StatusCode::SW_INTERNAL_EXCEPTION)?;
|
||||||
let private_key = storage::attestation_private_key(env)
|
|
||||||
.map_err(|_| Ctap1StatusCode::SW_INTERNAL_EXCEPTION)?
|
|
||||||
.ok_or(Ctap1StatusCode::SW_INTERNAL_EXCEPTION)?;
|
|
||||||
|
|
||||||
let mut response = Vec::with_capacity(105 + key_handle.len() + certificate.len());
|
let mut response = Vec::with_capacity(105 + key_handle.len() + certificate.len());
|
||||||
response.push(Ctap1Command::LEGACY_BYTE);
|
response.push(Ctap1Command::LEGACY_BYTE);
|
||||||
@@ -345,10 +347,10 @@ impl Ctap1Command {
|
|||||||
mod test {
|
mod test {
|
||||||
use super::super::crypto_wrapper::ECDSA_CREDENTIAL_ID_SIZE;
|
use super::super::crypto_wrapper::ECDSA_CREDENTIAL_ID_SIZE;
|
||||||
use super::super::data_formats::SignatureAlgorithm;
|
use super::super::data_formats::SignatureAlgorithm;
|
||||||
use super::super::key_material;
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::api::customization::Customization;
|
use crate::api::customization::Customization;
|
||||||
use crate::clock::TEST_CLOCK_FREQUENCY_HZ;
|
use crate::clock::TEST_CLOCK_FREQUENCY_HZ;
|
||||||
|
use crate::ctap::storage;
|
||||||
use crate::env::test::TestEnv;
|
use crate::env::test::TestEnv;
|
||||||
use crypto::Hash256;
|
use crypto::Hash256;
|
||||||
|
|
||||||
@@ -423,21 +425,13 @@ mod test {
|
|||||||
// Certificate and private key are missing
|
// Certificate and private key are missing
|
||||||
assert_eq!(response, Err(Ctap1StatusCode::SW_INTERNAL_EXCEPTION));
|
assert_eq!(response, Err(Ctap1StatusCode::SW_INTERNAL_EXCEPTION));
|
||||||
|
|
||||||
let fake_key = [0x41u8; key_material::ATTESTATION_PRIVATE_KEY_LENGTH];
|
let attestation = Attestation {
|
||||||
assert!(storage::set_attestation_private_key(&mut env, &fake_key).is_ok());
|
private_key: [0x41; 32],
|
||||||
ctap_state.u2f_up_state.consume_up(CtapInstant::new(0));
|
certificate: vec![0x99; 100],
|
||||||
ctap_state.u2f_up_state.grant_up(CtapInstant::new(0));
|
};
|
||||||
let response = Ctap1Command::process_command(
|
env.attestation_store()
|
||||||
&mut env,
|
.set(&attestation_store::Id::Batch, Some(&attestation))
|
||||||
&message,
|
.unwrap();
|
||||||
&mut ctap_state,
|
|
||||||
CtapInstant::new(0_u64),
|
|
||||||
);
|
|
||||||
// Certificate is still missing
|
|
||||||
assert_eq!(response, Err(Ctap1StatusCode::SW_INTERNAL_EXCEPTION));
|
|
||||||
|
|
||||||
let fake_cert = [0x99u8; 100]; // Arbitrary length
|
|
||||||
assert!(storage::set_attestation_certificate(&mut env, &fake_cert[..]).is_ok());
|
|
||||||
ctap_state.u2f_up_state.consume_up(CtapInstant::new(0));
|
ctap_state.u2f_up_state.consume_up(CtapInstant::new(0));
|
||||||
ctap_state.u2f_up_state.grant_up(CtapInstant::new(0));
|
ctap_state.u2f_up_state.grant_up(CtapInstant::new(0));
|
||||||
let response =
|
let response =
|
||||||
@@ -454,8 +448,8 @@ mod test {
|
|||||||
.is_some());
|
.is_some());
|
||||||
const CERT_START: usize = 67 + ECDSA_CREDENTIAL_ID_SIZE;
|
const CERT_START: usize = 67 + ECDSA_CREDENTIAL_ID_SIZE;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&response[CERT_START..CERT_START + fake_cert.len()],
|
&response[CERT_START..][..attestation.certificate.len()],
|
||||||
&fake_cert[..]
|
&attestation.certificate
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
106
src/ctap/mod.rs
106
src/ctap/mod.rs
@@ -62,6 +62,7 @@ use self::status_code::Ctap2StatusCode;
|
|||||||
use self::timed_permission::TimedPermission;
|
use self::timed_permission::TimedPermission;
|
||||||
#[cfg(feature = "with_ctap1")]
|
#[cfg(feature = "with_ctap1")]
|
||||||
use self::timed_permission::U2fUserPresenceState;
|
use self::timed_permission::U2fUserPresenceState;
|
||||||
|
use crate::api::attestation_store::{self, Attestation, AttestationStore};
|
||||||
use crate::api::connection::{HidConnection, SendOrRecvStatus};
|
use crate::api::connection::{HidConnection, SendOrRecvStatus};
|
||||||
use crate::api::customization::Customization;
|
use crate::api::customization::Customization;
|
||||||
use crate::api::firmware_protection::FirmwareProtection;
|
use crate::api::firmware_protection::FirmwareProtection;
|
||||||
@@ -860,7 +861,7 @@ impl CtapState {
|
|||||||
key_type: PublicKeyCredentialType::PublicKey,
|
key_type: PublicKeyCredentialType::PublicKey,
|
||||||
credential_id: random_id.clone(),
|
credential_id: random_id.clone(),
|
||||||
private_key: private_key.clone(),
|
private_key: private_key.clone(),
|
||||||
rp_id,
|
rp_id: rp_id.clone(),
|
||||||
user_handle: user.user_id,
|
user_handle: user.user_id,
|
||||||
// This input is user provided, so we crop it to 64 byte for storage.
|
// This input is user provided, so we crop it to 64 byte for storage.
|
||||||
// The UTF8 encoding is always preserved, so the string might end up shorter.
|
// The UTF8 encoding is always preserved, so the string might end up shorter.
|
||||||
@@ -918,21 +919,31 @@ impl CtapState {
|
|||||||
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);
|
||||||
|
|
||||||
let (signature, x5c) = if env.customization().use_batch_attestation() || ep_att {
|
let attestation_id = if env.customization().use_batch_attestation() {
|
||||||
let attestation_private_key = storage::attestation_private_key(env)?
|
Some(attestation_store::Id::Batch)
|
||||||
.ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?;
|
} else if ep_att {
|
||||||
let attestation_key =
|
Some(attestation_store::Id::Enterprise { rp_id })
|
||||||
crypto::ecdsa::SecKey::from_bytes(&attestation_private_key).unwrap();
|
|
||||||
let attestation_certificate = storage::attestation_certificate(env)?
|
|
||||||
.ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?;
|
|
||||||
(
|
|
||||||
attestation_key
|
|
||||||
.sign_rfc6979::<Sha256>(&signature_data)
|
|
||||||
.to_asn1_der(),
|
|
||||||
Some(vec![attestation_certificate]),
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
(private_key.sign_and_encode(env, &signature_data)?, None)
|
None
|
||||||
|
};
|
||||||
|
let (signature, x5c) = match attestation_id {
|
||||||
|
Some(id) => {
|
||||||
|
let Attestation {
|
||||||
|
private_key,
|
||||||
|
certificate,
|
||||||
|
} = env
|
||||||
|
.attestation_store()
|
||||||
|
.get(&id)?
|
||||||
|
.ok_or(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)?;
|
||||||
|
let attestation_key = crypto::ecdsa::SecKey::from_bytes(&private_key).unwrap();
|
||||||
|
(
|
||||||
|
attestation_key
|
||||||
|
.sign_rfc6979::<Sha256>(&signature_data)
|
||||||
|
.to_asn1_der(),
|
||||||
|
Some(vec![certificate]),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
None => (private_key.sign_and_encode(env, &signature_data)?, None),
|
||||||
};
|
};
|
||||||
let attestation_statement = PackedAttestationStatement {
|
let attestation_statement = PackedAttestationStatement {
|
||||||
alg: SignatureAlgorithm::ES256 as i64,
|
alg: SignatureAlgorithm::ES256 as i64,
|
||||||
@@ -1338,41 +1349,26 @@ impl CtapState {
|
|||||||
if params.attestation_material.is_some() || params.lockdown {
|
if params.attestation_material.is_some() || params.lockdown {
|
||||||
check_user_presence(env, channel)?;
|
check_user_presence(env, channel)?;
|
||||||
}
|
}
|
||||||
|
// This command is for U2F support and we use the batch attestation there.
|
||||||
|
let attestation_id = attestation_store::Id::Batch;
|
||||||
|
|
||||||
// Sanity checks
|
// Sanity checks
|
||||||
let current_priv_key = storage::attestation_private_key(env)?;
|
let current_attestation = env.attestation_store().get(&attestation_id)?;
|
||||||
let current_cert = storage::attestation_certificate(env)?;
|
|
||||||
|
|
||||||
let response = match params.attestation_material {
|
let response = match params.attestation_material {
|
||||||
// Only reading values.
|
|
||||||
None => AuthenticatorVendorConfigureResponse {
|
None => AuthenticatorVendorConfigureResponse {
|
||||||
cert_programmed: current_cert.is_some(),
|
cert_programmed: current_attestation.is_some(),
|
||||||
pkey_programmed: current_priv_key.is_some(),
|
pkey_programmed: current_attestation.is_some(),
|
||||||
},
|
},
|
||||||
// Device is already fully programmed. We don't leak information.
|
|
||||||
Some(_) if current_cert.is_some() && current_priv_key.is_some() => {
|
|
||||||
AuthenticatorVendorConfigureResponse {
|
|
||||||
cert_programmed: true,
|
|
||||||
pkey_programmed: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Device is partially or not programmed. We complete the process.
|
|
||||||
Some(data) => {
|
Some(data) => {
|
||||||
if let Some(current_cert) = ¤t_cert {
|
// We don't overwrite the attestation if it's already set. We don't return any error
|
||||||
if current_cert != &data.certificate {
|
// to not leak information.
|
||||||
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
|
if current_attestation.is_none() {
|
||||||
}
|
let attestation = Attestation {
|
||||||
}
|
private_key: data.private_key,
|
||||||
if let Some(current_priv_key) = ¤t_priv_key {
|
certificate: data.certificate,
|
||||||
if current_priv_key != &data.private_key {
|
};
|
||||||
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
|
env.attestation_store()
|
||||||
}
|
.set(&attestation_id, Some(&attestation))?;
|
||||||
}
|
|
||||||
if current_cert.is_none() {
|
|
||||||
storage::set_attestation_certificate(env, &data.certificate)?;
|
|
||||||
}
|
|
||||||
if current_priv_key.is_none() {
|
|
||||||
storage::set_attestation_private_key(env, &data.private_key)?;
|
|
||||||
}
|
}
|
||||||
AuthenticatorVendorConfigureResponse {
|
AuthenticatorVendorConfigureResponse {
|
||||||
cert_programmed: true,
|
cert_programmed: true,
|
||||||
@@ -3145,12 +3141,11 @@ mod test {
|
|||||||
))
|
))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
storage::attestation_certificate(&mut env).unwrap().unwrap(),
|
env.attestation_store().get(&attestation_store::Id::Batch),
|
||||||
dummy_cert
|
Ok(Some(Attestation {
|
||||||
);
|
private_key: dummy_key,
|
||||||
assert_eq!(
|
certificate: dummy_cert.to_vec(),
|
||||||
storage::attestation_private_key(&mut env).unwrap().unwrap(),
|
}))
|
||||||
dummy_key
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Try to inject other dummy values and check that initial values are retained.
|
// Try to inject other dummy values and check that initial values are retained.
|
||||||
@@ -3176,12 +3171,11 @@ mod test {
|
|||||||
))
|
))
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
storage::attestation_certificate(&mut env).unwrap().unwrap(),
|
env.attestation_store().get(&attestation_store::Id::Batch),
|
||||||
dummy_cert
|
Ok(Some(Attestation {
|
||||||
);
|
private_key: dummy_key,
|
||||||
assert_eq!(
|
certificate: dummy_cert.to_vec(),
|
||||||
storage::attestation_private_key(&mut env).unwrap().unwrap(),
|
}))
|
||||||
dummy_key
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Now try to lock the device
|
// Now try to lock the device
|
||||||
|
|||||||
@@ -12,8 +12,8 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
use crate::api::key_store;
|
|
||||||
use crate::api::user_presence::UserPresenceError;
|
use crate::api::user_presence::UserPresenceError;
|
||||||
|
use crate::api::{attestation_store, key_store};
|
||||||
|
|
||||||
// CTAP specification (version 20190130) section 6.3
|
// CTAP specification (version 20190130) section 6.3
|
||||||
// For now, only the CTAP2 codes are here, the CTAP1 are not included.
|
// For now, only the CTAP2 codes are here, the CTAP1 are not included.
|
||||||
@@ -100,3 +100,14 @@ impl From<key_store::Error> for Ctap2StatusCode {
|
|||||||
Self::CTAP2_ERR_VENDOR_INTERNAL_ERROR
|
Self::CTAP2_ERR_VENDOR_INTERNAL_ERROR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<attestation_store::Error> for Ctap2StatusCode {
|
||||||
|
fn from(error: attestation_store::Error) -> Self {
|
||||||
|
use attestation_store::Error;
|
||||||
|
match error {
|
||||||
|
Error::Storage => Self::CTAP2_ERR_VENDOR_HARDWARE_FAILURE,
|
||||||
|
Error::Internal => Self::CTAP2_ERR_VENDOR_INTERNAL_ERROR,
|
||||||
|
Error::NoSupport => Self::CTAP2_ERR_VENDOR_INTERNAL_ERROR,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -437,58 +437,6 @@ pub fn commit_large_blob_array(
|
|||||||
)?)
|
)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the attestation private key if defined.
|
|
||||||
pub fn attestation_private_key(
|
|
||||||
env: &mut impl Env,
|
|
||||||
) -> Result<Option<[u8; key_material::ATTESTATION_PRIVATE_KEY_LENGTH]>, Ctap2StatusCode> {
|
|
||||||
match env.store().find(key::ATTESTATION_PRIVATE_KEY)? {
|
|
||||||
None => Ok(None),
|
|
||||||
Some(key) if key.len() == key_material::ATTESTATION_PRIVATE_KEY_LENGTH => {
|
|
||||||
Ok(Some(*array_ref![
|
|
||||||
key,
|
|
||||||
0,
|
|
||||||
key_material::ATTESTATION_PRIVATE_KEY_LENGTH
|
|
||||||
]))
|
|
||||||
}
|
|
||||||
Some(_) => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the attestation private key.
|
|
||||||
///
|
|
||||||
/// If it is already defined, it is overwritten.
|
|
||||||
pub fn set_attestation_private_key(
|
|
||||||
env: &mut impl Env,
|
|
||||||
attestation_private_key: &[u8; key_material::ATTESTATION_PRIVATE_KEY_LENGTH],
|
|
||||||
) -> Result<(), Ctap2StatusCode> {
|
|
||||||
match env.store().find(key::ATTESTATION_PRIVATE_KEY)? {
|
|
||||||
None => Ok(env
|
|
||||||
.store()
|
|
||||||
.insert(key::ATTESTATION_PRIVATE_KEY, attestation_private_key)?),
|
|
||||||
Some(_) => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the attestation certificate if defined.
|
|
||||||
pub fn attestation_certificate(env: &mut impl Env) -> Result<Option<Vec<u8>>, Ctap2StatusCode> {
|
|
||||||
Ok(env.store().find(key::ATTESTATION_CERTIFICATE)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the attestation certificate.
|
|
||||||
///
|
|
||||||
/// If it is already defined, it is overwritten.
|
|
||||||
pub fn set_attestation_certificate(
|
|
||||||
env: &mut impl Env,
|
|
||||||
attestation_certificate: &[u8],
|
|
||||||
) -> Result<(), Ctap2StatusCode> {
|
|
||||||
match env.store().find(key::ATTESTATION_CERTIFICATE)? {
|
|
||||||
None => Ok(env
|
|
||||||
.store()
|
|
||||||
.insert(key::ATTESTATION_CERTIFICATE, attestation_certificate)?),
|
|
||||||
Some(_) => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the AAGUID.
|
/// Returns the AAGUID.
|
||||||
pub fn aaguid(env: &mut impl Env) -> Result<[u8; key_material::AAGUID_LENGTH], Ctap2StatusCode> {
|
pub fn aaguid(env: &mut impl Env) -> Result<[u8; key_material::AAGUID_LENGTH], Ctap2StatusCode> {
|
||||||
let aaguid = env
|
let aaguid = env
|
||||||
@@ -545,10 +493,9 @@ pub fn enterprise_attestation(env: &mut impl Env) -> Result<bool, Ctap2StatusCod
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Marks enterprise attestation as enabled.
|
/// Marks enterprise attestation as enabled.
|
||||||
|
///
|
||||||
|
/// Doesn't check whether an attestation is setup because it depends on the RP id.
|
||||||
pub fn enable_enterprise_attestation(env: &mut impl Env) -> Result<(), Ctap2StatusCode> {
|
pub fn enable_enterprise_attestation(env: &mut impl Env) -> Result<(), Ctap2StatusCode> {
|
||||||
if attestation_private_key(env)?.is_none() || attestation_certificate(env)?.is_none() {
|
|
||||||
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
|
|
||||||
}
|
|
||||||
if !enterprise_attestation(env)? {
|
if !enterprise_attestation(env)? {
|
||||||
env.store().insert(key::ENTERPRISE_ATTESTATION, &[])?;
|
env.store().insert(key::ENTERPRISE_ATTESTATION, &[])?;
|
||||||
}
|
}
|
||||||
@@ -696,6 +643,7 @@ fn serialize_min_pin_length_rp_ids(rp_ids: Vec<String>) -> Result<Vec<u8>, Ctap2
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::api::attestation_store::{self, Attestation, AttestationStore};
|
||||||
use crate::ctap::crypto_wrapper::PrivateKey;
|
use crate::ctap::crypto_wrapper::PrivateKey;
|
||||||
use crate::ctap::data_formats::{PublicKeyCredentialSource, PublicKeyCredentialType};
|
use crate::ctap::data_formats::{PublicKeyCredentialSource, PublicKeyCredentialType};
|
||||||
use crate::env::test::TestEnv;
|
use crate::env::test::TestEnv;
|
||||||
@@ -1033,25 +981,26 @@ mod test {
|
|||||||
init(&mut env).unwrap();
|
init(&mut env).unwrap();
|
||||||
|
|
||||||
// Make sure the attestation are absent. There is no batch attestation in tests.
|
// Make sure the attestation are absent. There is no batch attestation in tests.
|
||||||
assert!(attestation_private_key(&mut env).unwrap().is_none());
|
assert_eq!(
|
||||||
assert!(attestation_certificate(&mut env).unwrap().is_none());
|
env.attestation_store().get(&attestation_store::Id::Batch),
|
||||||
|
Ok(None)
|
||||||
|
);
|
||||||
|
|
||||||
// Make sure the persistent keys are initialized to dummy values.
|
// Make sure the persistent keys are initialized to dummy values.
|
||||||
let dummy_key = [0x41u8; key_material::ATTESTATION_PRIVATE_KEY_LENGTH];
|
let dummy_attestation = Attestation {
|
||||||
let dummy_cert = [0xddu8; 20];
|
private_key: [0x41; key_material::ATTESTATION_PRIVATE_KEY_LENGTH],
|
||||||
set_attestation_private_key(&mut env, &dummy_key).unwrap();
|
certificate: vec![0xdd; 20],
|
||||||
set_attestation_certificate(&mut env, &dummy_cert).unwrap();
|
};
|
||||||
|
env.attestation_store()
|
||||||
|
.set(&attestation_store::Id::Batch, Some(&dummy_attestation))
|
||||||
|
.unwrap();
|
||||||
assert_eq!(&aaguid(&mut env).unwrap(), key_material::AAGUID);
|
assert_eq!(&aaguid(&mut env).unwrap(), key_material::AAGUID);
|
||||||
|
|
||||||
// The persistent keys stay initialized and preserve their value after a reset.
|
// The persistent keys stay initialized and preserve their value after a reset.
|
||||||
reset(&mut env).unwrap();
|
reset(&mut env).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&attestation_private_key(&mut env).unwrap().unwrap(),
|
env.attestation_store().get(&attestation_store::Id::Batch),
|
||||||
&dummy_key
|
Ok(Some(dummy_attestation))
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
attestation_certificate(&mut env).unwrap().unwrap(),
|
|
||||||
&dummy_cert
|
|
||||||
);
|
);
|
||||||
assert_eq!(&aaguid(&mut env).unwrap(), key_material::AAGUID);
|
assert_eq!(&aaguid(&mut env).unwrap(), key_material::AAGUID);
|
||||||
}
|
}
|
||||||
@@ -1187,17 +1136,13 @@ mod test {
|
|||||||
fn test_enterprise_attestation() {
|
fn test_enterprise_attestation() {
|
||||||
let mut env = TestEnv::new();
|
let mut env = TestEnv::new();
|
||||||
|
|
||||||
assert!(!enterprise_attestation(&mut env).unwrap());
|
let dummy_attestation = Attestation {
|
||||||
assert_eq!(
|
private_key: [0x41; key_material::ATTESTATION_PRIVATE_KEY_LENGTH],
|
||||||
enable_enterprise_attestation(&mut env),
|
certificate: vec![0xdd; 20],
|
||||||
Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)
|
};
|
||||||
);
|
env.attestation_store()
|
||||||
assert!(!enterprise_attestation(&mut env).unwrap());
|
.set(&attestation_store::Id::Batch, Some(&dummy_attestation))
|
||||||
|
.unwrap();
|
||||||
let dummy_key = [0x41u8; key_material::ATTESTATION_PRIVATE_KEY_LENGTH];
|
|
||||||
let dummy_cert = [0xddu8; 20];
|
|
||||||
set_attestation_private_key(&mut env, &dummy_key).unwrap();
|
|
||||||
set_attestation_certificate(&mut env, &dummy_cert).unwrap();
|
|
||||||
|
|
||||||
assert!(!enterprise_attestation(&mut env).unwrap());
|
assert!(!enterprise_attestation(&mut env).unwrap());
|
||||||
assert_eq!(enable_enterprise_attestation(&mut env), Ok(()));
|
assert_eq!(enable_enterprise_attestation(&mut env), Ok(()));
|
||||||
|
|||||||
@@ -61,11 +61,8 @@ make_partition! {
|
|||||||
|
|
||||||
// WARNING: Keys should not be deleted but prefixed with `_` to avoid accidentally reusing them.
|
// WARNING: Keys should not be deleted but prefixed with `_` to avoid accidentally reusing them.
|
||||||
|
|
||||||
/// The attestation private key.
|
/// Reserved for the attestation store implementation of the environment.
|
||||||
ATTESTATION_PRIVATE_KEY = 1;
|
_RESERVED_ATTESTATION_STORE = 1..3;
|
||||||
|
|
||||||
/// The attestation certificate.
|
|
||||||
ATTESTATION_CERTIFICATE = 2;
|
|
||||||
|
|
||||||
/// The aaguid.
|
/// The aaguid.
|
||||||
AAGUID = 3;
|
AAGUID = 3;
|
||||||
|
|||||||
3
src/env/mod.rs
vendored
3
src/env/mod.rs
vendored
@@ -12,6 +12,7 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
|
use crate::api::attestation_store::AttestationStore;
|
||||||
use crate::api::connection::HidConnection;
|
use crate::api::connection::HidConnection;
|
||||||
use crate::api::customization::Customization;
|
use crate::api::customization::Customization;
|
||||||
use crate::api::firmware_protection::FirmwareProtection;
|
use crate::api::firmware_protection::FirmwareProtection;
|
||||||
@@ -36,11 +37,13 @@ pub trait Env {
|
|||||||
type Write: core::fmt::Write;
|
type Write: core::fmt::Write;
|
||||||
type Customization: Customization;
|
type Customization: Customization;
|
||||||
type HidConnection: HidConnection;
|
type HidConnection: HidConnection;
|
||||||
|
type AttestationStore: AttestationStore;
|
||||||
|
|
||||||
fn rng(&mut self) -> &mut Self::Rng;
|
fn rng(&mut self) -> &mut Self::Rng;
|
||||||
fn user_presence(&mut self) -> &mut Self::UserPresence;
|
fn user_presence(&mut self) -> &mut Self::UserPresence;
|
||||||
fn store(&mut self) -> &mut Store<Self::Storage>;
|
fn store(&mut self) -> &mut Store<Self::Storage>;
|
||||||
fn key_store(&mut self) -> &mut Self::KeyStore;
|
fn key_store(&mut self) -> &mut Self::KeyStore;
|
||||||
|
fn attestation_store(&mut self) -> &mut Self::AttestationStore;
|
||||||
|
|
||||||
/// Returns the upgrade storage instance.
|
/// Returns the upgrade storage instance.
|
||||||
///
|
///
|
||||||
|
|||||||
8
src/env/test/mod.rs
vendored
8
src/env/test/mod.rs
vendored
@@ -16,8 +16,8 @@ use self::upgrade_storage::BufferUpgradeStorage;
|
|||||||
use crate::api::connection::{HidConnection, SendOrRecvResult, SendOrRecvStatus};
|
use crate::api::connection::{HidConnection, SendOrRecvResult, SendOrRecvStatus};
|
||||||
use crate::api::customization::DEFAULT_CUSTOMIZATION;
|
use crate::api::customization::DEFAULT_CUSTOMIZATION;
|
||||||
use crate::api::firmware_protection::FirmwareProtection;
|
use crate::api::firmware_protection::FirmwareProtection;
|
||||||
use crate::api::key_store;
|
|
||||||
use crate::api::user_presence::{UserPresence, UserPresenceResult};
|
use crate::api::user_presence::{UserPresence, UserPresenceResult};
|
||||||
|
use crate::api::{attestation_store, key_store};
|
||||||
use crate::clock::ClockInt;
|
use crate::clock::ClockInt;
|
||||||
use crate::env::Env;
|
use crate::env::Env;
|
||||||
use customization::TestCustomization;
|
use customization::TestCustomization;
|
||||||
@@ -149,12 +149,14 @@ impl FirmwareProtection for TestEnv {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl key_store::Helper for TestEnv {}
|
impl key_store::Helper for TestEnv {}
|
||||||
|
impl attestation_store::Helper for TestEnv {}
|
||||||
|
|
||||||
impl Env for TestEnv {
|
impl Env for TestEnv {
|
||||||
type Rng = TestRng256;
|
type Rng = TestRng256;
|
||||||
type UserPresence = TestUserPresence;
|
type UserPresence = TestUserPresence;
|
||||||
type Storage = BufferStorage;
|
type Storage = BufferStorage;
|
||||||
type KeyStore = Self;
|
type KeyStore = Self;
|
||||||
|
type AttestationStore = Self;
|
||||||
type UpgradeStorage = BufferUpgradeStorage;
|
type UpgradeStorage = BufferUpgradeStorage;
|
||||||
type FirmwareProtection = Self;
|
type FirmwareProtection = Self;
|
||||||
type Write = TestWrite;
|
type Write = TestWrite;
|
||||||
@@ -177,6 +179,10 @@ impl Env for TestEnv {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn attestation_store(&mut self) -> &mut Self {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
fn upgrade_storage(&mut self) -> Option<&mut Self::UpgradeStorage> {
|
fn upgrade_storage(&mut self) -> Option<&mut Self::UpgradeStorage> {
|
||||||
self.upgrade_storage.as_mut()
|
self.upgrade_storage.as_mut()
|
||||||
}
|
}
|
||||||
|
|||||||
8
src/env/tock/mod.rs
vendored
8
src/env/tock/mod.rs
vendored
@@ -16,8 +16,8 @@ pub use self::storage::{TockStorage, TockUpgradeStorage};
|
|||||||
use crate::api::connection::{HidConnection, SendOrRecvError, SendOrRecvResult, SendOrRecvStatus};
|
use crate::api::connection::{HidConnection, SendOrRecvError, SendOrRecvResult, SendOrRecvStatus};
|
||||||
use crate::api::customization::{CustomizationImpl, DEFAULT_CUSTOMIZATION};
|
use crate::api::customization::{CustomizationImpl, DEFAULT_CUSTOMIZATION};
|
||||||
use crate::api::firmware_protection::FirmwareProtection;
|
use crate::api::firmware_protection::FirmwareProtection;
|
||||||
use crate::api::key_store;
|
|
||||||
use crate::api::user_presence::{UserPresence, UserPresenceError, UserPresenceResult};
|
use crate::api::user_presence::{UserPresence, UserPresenceError, UserPresenceResult};
|
||||||
|
use crate::api::{attestation_store, key_store};
|
||||||
use crate::clock::{ClockInt, KEEPALIVE_DELAY_MS};
|
use crate::clock::{ClockInt, KEEPALIVE_DELAY_MS};
|
||||||
use crate::env::Env;
|
use crate::env::Env;
|
||||||
use core::cell::Cell;
|
use core::cell::Cell;
|
||||||
@@ -195,12 +195,14 @@ impl FirmwareProtection for TockEnv {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl key_store::Helper for TockEnv {}
|
impl key_store::Helper for TockEnv {}
|
||||||
|
impl attestation_store::Helper for TockEnv {}
|
||||||
|
|
||||||
impl Env for TockEnv {
|
impl Env for TockEnv {
|
||||||
type Rng = TockRng256;
|
type Rng = TockRng256;
|
||||||
type UserPresence = Self;
|
type UserPresence = Self;
|
||||||
type Storage = TockStorage;
|
type Storage = TockStorage;
|
||||||
type KeyStore = Self;
|
type KeyStore = Self;
|
||||||
|
type AttestationStore = Self;
|
||||||
type UpgradeStorage = TockUpgradeStorage;
|
type UpgradeStorage = TockUpgradeStorage;
|
||||||
type FirmwareProtection = Self;
|
type FirmwareProtection = Self;
|
||||||
type Write = Console;
|
type Write = Console;
|
||||||
@@ -223,6 +225,10 @@ impl Env for TockEnv {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn attestation_store(&mut self) -> &mut Self {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
fn upgrade_storage(&mut self) -> Option<&mut Self::UpgradeStorage> {
|
fn upgrade_storage(&mut self) -> Option<&mut Self::UpgradeStorage> {
|
||||||
self.upgrade_storage.as_mut()
|
self.upgrade_storage.as_mut()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user