Improved documentation for customization (#287)
* move constants to new file, and update documentation * documentation improvements, deploy checks tests * fix pylint * improved code style * swap build and check
This commit is contained in:
52
README.md
52
README.md
@@ -92,45 +92,19 @@ If you build your own security key, depending on the hardware you use, there are
|
|||||||
a few things you can personalize:
|
a few things you can personalize:
|
||||||
|
|
||||||
1. If you have multiple buttons, choose the buttons responsible for user
|
1. If you have multiple buttons, choose the buttons responsible for user
|
||||||
presence in `main.rs`.
|
presence in `src/main.rs`.
|
||||||
1. Decide whether you want to use batch attestation. There is a boolean flag in
|
1. If you have colored LEDs, like different blinking patterns and want to play
|
||||||
`ctap/mod.rs`. It is mandatory for U2F, and you can create your own
|
around with the code in `src/main.rs` more, take a look at e.g. `wink_leds`.
|
||||||
self-signed certificate. The flag is used for FIDO2 and has some privacy
|
1. You find more options and documentation in `src/ctap/customization.rs`,
|
||||||
implications. Please check
|
including:
|
||||||
[WebAuthn](https://www.w3.org/TR/webauthn/#attestation) for more
|
- The default level for the credProtect extension.
|
||||||
information.
|
- The default minimum PIN length, and what relying parties can set it.
|
||||||
1. Decide whether you want to use signature counters. Currently, only global
|
- Whether you want to enforce alwaysUv.
|
||||||
signature counters are implemented, as they are the default option for U2F.
|
- Settings for enterprise attestation.
|
||||||
The flag in `ctap/mod.rs` only turns them off for FIDO2. The most privacy
|
- The maximum PIN retries.
|
||||||
preserving solution is individual or no signature counters. Again, please
|
- Whether you want to use batch attestation.
|
||||||
check [WebAuthn](https://www.w3.org/TR/webauthn/#signature-counter) for
|
- Whether you want to use signature counters.
|
||||||
documentation.
|
- Various constants to adapt to different hardware.
|
||||||
1. Depending on your available flash storage, choose an appropriate maximum
|
|
||||||
number of supported resident keys and number of pages in `ctap/storage.rs`.
|
|
||||||
1. Change the default level for the credProtect extension in `ctap/mod.rs`.
|
|
||||||
When changing the default, resident credentials become undiscoverable without
|
|
||||||
user verification. This helps privacy, but can make usage less comfortable
|
|
||||||
for credentials that need less protection.
|
|
||||||
1. Increase the default minimum length for PINs in `ctap/storage.rs`.
|
|
||||||
The current minimum is 4. Values from 4 to 63 are allowed. Requiring longer
|
|
||||||
PINs can help establish trust between users and relying parties. It makes
|
|
||||||
user verification harder to break, but less convenient.
|
|
||||||
NIST recommends at least 6-digit PINs in section 5.1.9.1:
|
|
||||||
https://pages.nist.gov/800-63-3/sp800-63b.html
|
|
||||||
You can add relying parties to the list of readers of the minimum PIN length.
|
|
||||||
1. In an enterprise setting, you can adapt `DEFAULT_MIN_PIN_LENGTH_RP_IDS` and
|
|
||||||
`MAX_RP_IDS_LENGTH` for tuning the `minPinLength` extension. The former
|
|
||||||
allows some relying parties to read the minimum PIN length by default. The
|
|
||||||
latter allows storing more relying parties that may check the minimum PIN
|
|
||||||
length.
|
|
||||||
1. Increase the `MAX_CRED_BLOB_LENGTH` in `ctap/mod.rs`, if you expect blobs
|
|
||||||
bigger than the default value.
|
|
||||||
1. Implement enterprise attestation. This can be as easy as setting
|
|
||||||
ENTERPRISE_ATTESTATION_MODE in `ctap/mod.rs`. If you want to use a different
|
|
||||||
attestation type than batch attestation, you have to implement it first.
|
|
||||||
1. If a certification (additional to FIDO's) requires that all requests are
|
|
||||||
protected with user verification, set `ENFORCE_ALWAYS_UV` in
|
|
||||||
`ctap/config_mod.rs` to `true`.
|
|
||||||
|
|
||||||
### 3D printed enclosure
|
### 3D printed enclosure
|
||||||
|
|
||||||
|
|||||||
@@ -352,6 +352,7 @@ class OpenSKInstaller:
|
|||||||
|
|
||||||
def build_opensk(self):
|
def build_opensk(self):
|
||||||
info("Building OpenSK application")
|
info("Building OpenSK application")
|
||||||
|
self._check_invariants()
|
||||||
self._build_app_or_example(is_example=False)
|
self._build_app_or_example(is_example=False)
|
||||||
|
|
||||||
def _build_app_or_example(self, is_example):
|
def _build_app_or_example(self, is_example):
|
||||||
@@ -390,6 +391,11 @@ class OpenSKInstaller:
|
|||||||
# Create a TAB file
|
# Create a TAB file
|
||||||
self.create_tab_file({props.arch: app_path})
|
self.create_tab_file({props.arch: app_path})
|
||||||
|
|
||||||
|
def _check_invariants(self):
|
||||||
|
print("Testing invariants in customization.rs...")
|
||||||
|
self.checked_command_output(
|
||||||
|
["cargo", "test", "--features=std", "--lib", "customization"])
|
||||||
|
|
||||||
def generate_crypto_materials(self, force_regenerate):
|
def generate_crypto_materials(self, force_regenerate):
|
||||||
has_error = subprocess.call([
|
has_error = subprocess.call([
|
||||||
os.path.join("tools", "gen_key_materials.sh"),
|
os.path.join("tools", "gen_key_materials.sh"),
|
||||||
|
|||||||
@@ -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 super::customization::{MAX_CREDENTIAL_COUNT_IN_LIST, MAX_LARGE_BLOB_ARRAY_SIZE};
|
||||||
use super::data_formats::{
|
use super::data_formats::{
|
||||||
extract_array, extract_bool, extract_byte_string, extract_map, extract_text_string,
|
extract_array, extract_bool, extract_byte_string, extract_map, extract_text_string,
|
||||||
extract_unsigned, ok_or_missing, ClientPinSubCommand, ConfigSubCommand, ConfigSubCommandParams,
|
extract_unsigned, ok_or_missing, ClientPinSubCommand, ConfigSubCommand, ConfigSubCommandParams,
|
||||||
@@ -22,18 +23,12 @@ use super::data_formats::{
|
|||||||
};
|
};
|
||||||
use super::key_material;
|
use super::key_material;
|
||||||
use super::status_code::Ctap2StatusCode;
|
use super::status_code::Ctap2StatusCode;
|
||||||
use super::storage::MAX_LARGE_BLOB_ARRAY_SIZE;
|
|
||||||
use alloc::string::String;
|
use alloc::string::String;
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use arrayref::array_ref;
|
use arrayref::array_ref;
|
||||||
use cbor::destructure_cbor_map;
|
use cbor::destructure_cbor_map;
|
||||||
use core::convert::TryFrom;
|
use core::convert::TryFrom;
|
||||||
|
|
||||||
// Depending on your memory, you can use Some(n) to limit request sizes in
|
|
||||||
// MakeCredential and GetAssertion. This affects allowList and excludeList.
|
|
||||||
// You might also want to set the max credential size in process_get_info then.
|
|
||||||
pub const MAX_CREDENTIAL_COUNT_IN_LIST: Option<usize> = None;
|
|
||||||
|
|
||||||
// This constant is a consequence of the structure of messages.
|
// This constant is a consequence of the structure of messages.
|
||||||
const MIN_LARGE_BLOB_LEN: usize = 17;
|
const MIN_LARGE_BLOB_LEN: usize = 17;
|
||||||
|
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ pub fn process_config(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::ctap::ENFORCE_ALWAYS_UV;
|
use crate::ctap::customization::ENFORCE_ALWAYS_UV;
|
||||||
use crypto::rng256::ThreadRng256;
|
use crypto::rng256::ThreadRng256;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
269
src/ctap/customization.rs
Normal file
269
src/ctap/customization.rs
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
// Copyright 2021 Google LLC
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
//! This file contains all customizable constants.
|
||||||
|
//!
|
||||||
|
//! If you adapt them, make sure to run the tests before flashing the firmware.
|
||||||
|
//! Our deploy script enforces the invariants.
|
||||||
|
|
||||||
|
use crate::ctap::data_formats::{CredentialProtectionPolicy, EnterpriseAttestationMode};
|
||||||
|
|
||||||
|
// ###########################################################################
|
||||||
|
// Constants for adjusting privacy and protection levels.
|
||||||
|
// ###########################################################################
|
||||||
|
|
||||||
|
/// Changes the default level for the credProtect extension.
|
||||||
|
///
|
||||||
|
/// You can change this value to one of the following for more privacy:
|
||||||
|
/// - CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIdList
|
||||||
|
/// - CredentialProtectionPolicy::UserVerificationRequired
|
||||||
|
///
|
||||||
|
/// UserVerificationOptionalWithCredentialIdList
|
||||||
|
/// Resident credentials are discoverable with
|
||||||
|
/// - an allowList,
|
||||||
|
/// - an excludeList,
|
||||||
|
/// - user verification.
|
||||||
|
///
|
||||||
|
/// UserVerificationRequired
|
||||||
|
/// Resident credentials are discoverable with user verification only.
|
||||||
|
///
|
||||||
|
/// This can improve privacy, but can make usage less comfortable.
|
||||||
|
pub const DEFAULT_CRED_PROTECT: Option<CredentialProtectionPolicy> = None;
|
||||||
|
|
||||||
|
/// Sets the initial minimum PIN length in code points.
|
||||||
|
///
|
||||||
|
/// # Invariant
|
||||||
|
///
|
||||||
|
/// - The minimum PIN length must be at least 4.
|
||||||
|
/// - The minimum PIN length must be at most 63.
|
||||||
|
/// - DEFAULT_MIN_PIN_LENGTH_RP_IDS must be non-empty if MAX_RP_IDS_LENGTH is 0.
|
||||||
|
///
|
||||||
|
/// Requiring longer PINs can help establish trust between users and relying
|
||||||
|
/// parties. It makes user verification harder to break, but less convenient.
|
||||||
|
/// NIST recommends at least 6-digit PINs in section 5.1.9.1:
|
||||||
|
/// https://pages.nist.gov/800-63-3/sp800-63b.html
|
||||||
|
///
|
||||||
|
/// Reset reverts the minimum PIN length to this DEFAULT_MIN_PIN_LENGTH.
|
||||||
|
pub const DEFAULT_MIN_PIN_LENGTH: u8 = 4;
|
||||||
|
|
||||||
|
/// Lists relying parties that can read the minimum PIN length.
|
||||||
|
///
|
||||||
|
/// # Invariant
|
||||||
|
///
|
||||||
|
/// - DEFAULT_MIN_PIN_LENGTH_RP_IDS must be non-empty if MAX_RP_IDS_LENGTH is 0
|
||||||
|
///
|
||||||
|
/// Only the RP IDs listed in DEFAULT_MIN_PIN_LENGTH_RP_IDS are allowed to read
|
||||||
|
/// the minimum PIN length with the minPinLength extension.
|
||||||
|
pub const DEFAULT_MIN_PIN_LENGTH_RP_IDS: &[&str] = &[];
|
||||||
|
|
||||||
|
/// Enforces the alwaysUv option.
|
||||||
|
///
|
||||||
|
/// When setting to true, commands require a PIN.
|
||||||
|
/// Also, alwaysUv can not be disabled by commands.
|
||||||
|
///
|
||||||
|
/// A certification (additional to FIDO Alliance's) might require enforcing
|
||||||
|
/// alwaysUv. Otherwise, users should have the choice to configure alwaysUv.
|
||||||
|
/// Calling toggleAlwaysUv is preferred over enforcing alwaysUv here.
|
||||||
|
pub const ENFORCE_ALWAYS_UV: bool = false;
|
||||||
|
|
||||||
|
/// Allows usage of enterprise attestation.
|
||||||
|
///
|
||||||
|
/// # Invariant
|
||||||
|
///
|
||||||
|
/// - Enterprise and batch attestation can not both be active.
|
||||||
|
/// - If the mode is VendorFacilitated, ENTERPRISE_RP_ID_LIST must be non-empty.
|
||||||
|
///
|
||||||
|
/// For privacy reasons, it is disabled by default. You can choose between:
|
||||||
|
/// - EnterpriseAttestationMode::VendorFacilitated
|
||||||
|
/// - EnterpriseAttestationMode::PlatformManaged
|
||||||
|
///
|
||||||
|
/// VendorFacilitated
|
||||||
|
/// Enterprise attestation is restricted to ENTERPRISE_RP_ID_LIST. Add your
|
||||||
|
/// enterprises domain, e.g. "example.com", to the list below.
|
||||||
|
///
|
||||||
|
/// PlatformManaged
|
||||||
|
/// All relying parties can request an enterprise attestation. The authenticator
|
||||||
|
/// trusts the platform to filter requests.
|
||||||
|
///
|
||||||
|
/// To enable the feature, send the subcommand enableEnterpriseAttestation in
|
||||||
|
/// AuthenticatorConfig. An enterprise might want to customize the type of
|
||||||
|
/// attestation that is used. OpenSK defaults to batch attestation. Configuring
|
||||||
|
/// individual certificates then makes authenticators identifiable.
|
||||||
|
///
|
||||||
|
/// OpenSK prevents activating batch and enterprise attestation together. The
|
||||||
|
/// current implementation uses the same key material at the moment, and these
|
||||||
|
/// two modes have conflicting privacy guarantees.
|
||||||
|
/// If you implement your own enterprise attestation mechanism, and you want
|
||||||
|
/// batch attestation at the same time, proceed carefully and remove the
|
||||||
|
/// assertion.
|
||||||
|
pub const ENTERPRISE_ATTESTATION_MODE: Option<EnterpriseAttestationMode> = None;
|
||||||
|
|
||||||
|
/// Lists relying party IDs that can perform enterprise attestation.
|
||||||
|
///
|
||||||
|
/// # Invariant
|
||||||
|
///
|
||||||
|
/// - If the mode is VendorFacilitated, ENTERPRISE_RP_ID_LIST must be non-empty.
|
||||||
|
///
|
||||||
|
/// This list is only considered if the enterprise attestation mode is
|
||||||
|
/// VendorFacilitated.
|
||||||
|
pub const ENTERPRISE_RP_ID_LIST: &[&str] = &[];
|
||||||
|
|
||||||
|
/// Sets the number of consecutive failed PINs before blocking interaction.
|
||||||
|
///
|
||||||
|
/// # Invariant
|
||||||
|
///
|
||||||
|
/// - CTAP2.0: Maximum PIN retries must be 8.
|
||||||
|
/// - CTAP2.1: Maximum PIN retries must be 8 at most.
|
||||||
|
///
|
||||||
|
/// The fail retry counter is reset after entering the correct PIN.
|
||||||
|
pub const MAX_PIN_RETRIES: u8 = 8;
|
||||||
|
|
||||||
|
/// Enables or disables basic attestation for FIDO2.
|
||||||
|
///
|
||||||
|
/// # Invariant
|
||||||
|
///
|
||||||
|
/// - Enterprise and batch attestation can not both be active (see above).
|
||||||
|
///
|
||||||
|
/// The basic attestation uses the signing key configured with a vendor command
|
||||||
|
/// as a batch key. If you turn batch attestation on, be aware that it is your
|
||||||
|
/// responsibility to safely generate and store the key material. Also, the
|
||||||
|
/// batches must have size of at least 100k authenticators before using new key
|
||||||
|
/// material.
|
||||||
|
/// U2F is unaffected by this setting.
|
||||||
|
///
|
||||||
|
/// https://www.w3.org/TR/webauthn/#attestation
|
||||||
|
pub const USE_BATCH_ATTESTATION: bool = false;
|
||||||
|
|
||||||
|
/// Enables or disables signature counters.
|
||||||
|
///
|
||||||
|
/// The signature counter is currently implemented as a global counter.
|
||||||
|
/// The specification strongly suggests to have per-credential counters.
|
||||||
|
/// Implementing those means you can't have an infinite amount of server-side
|
||||||
|
/// credentials anymore. Also, since counters need frequent writes on the
|
||||||
|
/// persistent storage, we might need a flash friendly implementation. This
|
||||||
|
/// solution is a compromise to be compatible with U2F and not wasting storage.
|
||||||
|
///
|
||||||
|
/// https://www.w3.org/TR/webauthn/#signature-counter
|
||||||
|
pub const USE_SIGNATURE_COUNTER: bool = true;
|
||||||
|
|
||||||
|
// ###########################################################################
|
||||||
|
// Constants for performance optimization or adapting to different hardware.
|
||||||
|
//
|
||||||
|
// Those constants may be modified before compilation to tune the behavior of
|
||||||
|
// the key.
|
||||||
|
// ###########################################################################
|
||||||
|
|
||||||
|
/// Sets the maximum blob size stored with the credBlob extension.
|
||||||
|
///
|
||||||
|
/// # Invariant
|
||||||
|
///
|
||||||
|
/// - The length must be at least 32.
|
||||||
|
pub const MAX_CRED_BLOB_LENGTH: usize = 32;
|
||||||
|
|
||||||
|
/// Limits the number of considered entries in credential lists.
|
||||||
|
///
|
||||||
|
/// # Invariant
|
||||||
|
///
|
||||||
|
/// - This value, if present, must be at least 1 (more is preferred).
|
||||||
|
///
|
||||||
|
/// Depending on your memory, you can use Some(n) to limit request sizes in
|
||||||
|
/// MakeCredential and GetAssertion. This affects allowList and excludeList.
|
||||||
|
pub const MAX_CREDENTIAL_COUNT_IN_LIST: Option<usize> = None;
|
||||||
|
|
||||||
|
/// Limits the size of largeBlobs the authenticator stores.
|
||||||
|
///
|
||||||
|
/// # Invariant
|
||||||
|
///
|
||||||
|
/// - The allowed size must be at least 1024.
|
||||||
|
/// - The array must fit into the shards reserved in storage/key.rs.
|
||||||
|
pub const MAX_LARGE_BLOB_ARRAY_SIZE: usize = 2048;
|
||||||
|
|
||||||
|
/// Limits the number of RP IDs that can change the minimum PIN length.
|
||||||
|
///
|
||||||
|
/// # Invariant
|
||||||
|
///
|
||||||
|
/// - If this value is 0, DEFAULT_MIN_PIN_LENGTH_RP_IDS must be non-empty.
|
||||||
|
///
|
||||||
|
/// You can use this constant to have an upper limit in storage requirements.
|
||||||
|
/// This might be useful if you want to more reliably predict the remaining
|
||||||
|
/// storage. Stored string can still be of arbitrary length though, until RP ID
|
||||||
|
/// truncation is implemented.
|
||||||
|
/// Outside of memory considerations, you can set this value to 0 if only RP IDs
|
||||||
|
/// in DEFAULT_MIN_PIN_LENGTH_RP_IDS should be allowed to change the minimum PIN
|
||||||
|
/// length.
|
||||||
|
pub const MAX_RP_IDS_LENGTH: usize = 8;
|
||||||
|
|
||||||
|
/// Sets the number of resident keys you can store.
|
||||||
|
///
|
||||||
|
/// # Invariant
|
||||||
|
///
|
||||||
|
/// - The storage key CREDENTIALS must fit at least this number of credentials.
|
||||||
|
///
|
||||||
|
/// This value has implications on the flash lifetime, please see the
|
||||||
|
/// documentation for NUM_PAGES below.
|
||||||
|
pub const MAX_SUPPORTED_RESIDENT_KEYS: usize = 150;
|
||||||
|
|
||||||
|
/// Sets the number of pages used for persistent storage.
|
||||||
|
///
|
||||||
|
/// The number of pages should be at least 3 and at most what the flash can
|
||||||
|
/// hold. There should be no reason to put a small number here, except that the
|
||||||
|
/// latency of flash operations is linear in the number of pages. This may
|
||||||
|
/// improve in the future. Currently, using 20 pages gives between 20ms and
|
||||||
|
/// 240ms per operation. The rule of thumb is between 1ms and 12ms per
|
||||||
|
/// additional page.
|
||||||
|
///
|
||||||
|
/// Limiting the number of resident keys permits to ensure a minimum number of
|
||||||
|
/// counter increments.
|
||||||
|
/// Let:
|
||||||
|
/// - P the number of pages (NUM_PAGES)
|
||||||
|
/// - K the maximum number of resident keys (MAX_SUPPORTED_RESIDENT_KEYS)
|
||||||
|
/// - S the maximum size of a resident key (about 500)
|
||||||
|
/// - C the number of erase cycles (10000)
|
||||||
|
/// - I the minimum number of counter increments
|
||||||
|
///
|
||||||
|
/// We have: I = (P * 4084 - 5107 - K * S) / 8 * C
|
||||||
|
///
|
||||||
|
/// With P=20 and K=150, we have I=2M which is enough for 500 increments per day
|
||||||
|
/// for 10 years.
|
||||||
|
pub const NUM_PAGES: usize = 20;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[allow(clippy::assertions_on_constants)]
|
||||||
|
fn test_invariants() {
|
||||||
|
// Two invariants are currently tested in different files:
|
||||||
|
// - storage.rs: if MAX_LARGE_BLOB_ARRAY_SIZE fits the shards
|
||||||
|
// - storage/key.rs: if MAX_SUPPORTED_RESIDENT_KEYS fits CREDENTIALS
|
||||||
|
assert!(DEFAULT_MIN_PIN_LENGTH >= 4);
|
||||||
|
assert!(DEFAULT_MIN_PIN_LENGTH <= 63);
|
||||||
|
assert!(!USE_BATCH_ATTESTATION || ENTERPRISE_ATTESTATION_MODE.is_none());
|
||||||
|
if let Some(EnterpriseAttestationMode::VendorFacilitated) = ENTERPRISE_ATTESTATION_MODE {
|
||||||
|
assert!(!ENTERPRISE_RP_ID_LIST.is_empty());
|
||||||
|
} else {
|
||||||
|
assert!(ENTERPRISE_RP_ID_LIST.is_empty());
|
||||||
|
}
|
||||||
|
assert!(MAX_PIN_RETRIES <= 8);
|
||||||
|
assert!(MAX_CRED_BLOB_LENGTH >= 32);
|
||||||
|
if let Some(count) = MAX_CREDENTIAL_COUNT_IN_LIST {
|
||||||
|
assert!(count >= 1);
|
||||||
|
}
|
||||||
|
assert!(MAX_LARGE_BLOB_ARRAY_SIZE >= 1024);
|
||||||
|
if MAX_RP_IDS_LENGTH == 0 {
|
||||||
|
assert!(!DEFAULT_MIN_PIN_LENGTH_RP_IDS.is_empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -504,11 +504,18 @@ impl TryFrom<cbor::Value> for SignatureAlgorithm {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The credProtect extension's policies for resident credentials.
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
|
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
|
||||||
#[cfg_attr(test, derive(IntoEnumIterator))]
|
#[cfg_attr(test, derive(IntoEnumIterator))]
|
||||||
pub enum CredentialProtectionPolicy {
|
pub enum CredentialProtectionPolicy {
|
||||||
|
/// The credential is always discoverable, as if it had no protection level.
|
||||||
UserVerificationOptional = 0x01,
|
UserVerificationOptional = 0x01,
|
||||||
|
/// The credential is discoverable with
|
||||||
|
/// - an allowList,
|
||||||
|
/// - an excludeList,
|
||||||
|
/// - user verification.
|
||||||
UserVerificationOptionalWithCredentialIdList = 0x02,
|
UserVerificationOptionalWithCredentialIdList = 0x02,
|
||||||
|
/// The credentials is discoverable with user verification only.
|
||||||
UserVerificationRequired = 0x03,
|
UserVerificationRequired = 0x03,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -939,9 +946,14 @@ impl From<SetMinPinLengthParams> for cbor::Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The level of enterprise attestation allowed in MakeCredential.
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum EnterpriseAttestationMode {
|
pub enum EnterpriseAttestationMode {
|
||||||
|
/// Enterprise attestation is restricted to a list of RP IDs. Add your
|
||||||
|
/// enterprises domain, e.g. "example.com", to the list below.
|
||||||
VendorFacilitated = 0x01,
|
VendorFacilitated = 0x01,
|
||||||
|
/// All relying parties can request an enterprise attestation. The authenticator
|
||||||
|
/// trusts the platform to filter requests.
|
||||||
PlatformManaged = 0x02,
|
PlatformManaged = 0x02,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ mod config_command;
|
|||||||
mod credential_management;
|
mod credential_management;
|
||||||
#[cfg(feature = "with_ctap1")]
|
#[cfg(feature = "with_ctap1")]
|
||||||
mod ctap1;
|
mod ctap1;
|
||||||
|
mod customization;
|
||||||
pub mod data_formats;
|
pub mod data_formats;
|
||||||
pub mod hid;
|
pub mod hid;
|
||||||
mod key_material;
|
mod key_material;
|
||||||
@@ -31,10 +32,14 @@ mod timed_permission;
|
|||||||
use self::command::{
|
use self::command::{
|
||||||
AuthenticatorClientPinParameters, AuthenticatorGetAssertionParameters,
|
AuthenticatorClientPinParameters, AuthenticatorGetAssertionParameters,
|
||||||
AuthenticatorMakeCredentialParameters, AuthenticatorVendorConfigureParameters, Command,
|
AuthenticatorMakeCredentialParameters, AuthenticatorVendorConfigureParameters, Command,
|
||||||
MAX_CREDENTIAL_COUNT_IN_LIST,
|
|
||||||
};
|
};
|
||||||
use self::config_command::process_config;
|
use self::config_command::process_config;
|
||||||
use self::credential_management::process_credential_management;
|
use self::credential_management::process_credential_management;
|
||||||
|
use self::customization::{
|
||||||
|
DEFAULT_CRED_PROTECT, ENTERPRISE_ATTESTATION_MODE, ENTERPRISE_RP_ID_LIST,
|
||||||
|
MAX_CREDENTIAL_COUNT_IN_LIST, MAX_CRED_BLOB_LENGTH, MAX_LARGE_BLOB_ARRAY_SIZE,
|
||||||
|
MAX_RP_IDS_LENGTH, USE_BATCH_ATTESTATION, USE_SIGNATURE_COUNTER,
|
||||||
|
};
|
||||||
use self::data_formats::{
|
use self::data_formats::{
|
||||||
AuthenticatorTransport, CoseKey, CredentialProtectionPolicy, EnterpriseAttestationMode,
|
AuthenticatorTransport, CoseKey, CredentialProtectionPolicy, EnterpriseAttestationMode,
|
||||||
GetAssertionExtensions, PackedAttestationStatement, PublicKeyCredentialDescriptor,
|
GetAssertionExtensions, PackedAttestationStatement, PublicKeyCredentialDescriptor,
|
||||||
@@ -49,7 +54,7 @@ use self::response::{
|
|||||||
AuthenticatorMakeCredentialResponse, AuthenticatorVendorResponse, ResponseData,
|
AuthenticatorMakeCredentialResponse, AuthenticatorVendorResponse, ResponseData,
|
||||||
};
|
};
|
||||||
use self::status_code::Ctap2StatusCode;
|
use self::status_code::Ctap2StatusCode;
|
||||||
use self::storage::{PersistentStore, MAX_LARGE_BLOB_ARRAY_SIZE, MAX_RP_IDS_LENGTH};
|
use self::storage::PersistentStore;
|
||||||
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;
|
||||||
@@ -74,36 +79,7 @@ use libtock_drivers::console::Console;
|
|||||||
use libtock_drivers::crp;
|
use libtock_drivers::crp;
|
||||||
use libtock_drivers::timer::{ClockValue, Duration};
|
use libtock_drivers::timer::{ClockValue, Duration};
|
||||||
|
|
||||||
// This flag enables or disables basic attestation for FIDO2. U2F is unaffected by
|
|
||||||
// this setting. The basic attestation uses the signing key configured with a
|
|
||||||
// vendor command as a batch key. If you turn batch attestation on, be aware that
|
|
||||||
// it is your responsibility to safely generate and store the key material. Also,
|
|
||||||
// the batches must have size of at least 100k authenticators before using new
|
|
||||||
// key material.
|
|
||||||
const USE_BATCH_ATTESTATION: bool = false;
|
|
||||||
// The signature counter is currently implemented as a global counter, if you set
|
|
||||||
// this flag to true. The spec strongly suggests to have per-credential-counters,
|
|
||||||
// but it means you can't have an infinite amount of credentials anymore. Also,
|
|
||||||
// since this is the only piece of information that needs writing often, we might
|
|
||||||
// need a flash storage friendly way to implement this feature. The implemented
|
|
||||||
// solution is a compromise to be compatible with U2F and not wasting storage.
|
|
||||||
const USE_SIGNATURE_COUNTER: bool = true;
|
|
||||||
pub const INITIAL_SIGNATURE_COUNTER: u32 = 1;
|
pub const INITIAL_SIGNATURE_COUNTER: u32 = 1;
|
||||||
// This flag allows usage of enterprise attestation. For privacy reasons, it is
|
|
||||||
// disabled by default. You can choose between
|
|
||||||
// - EnterpriseAttestationMode::VendorFacilitated,
|
|
||||||
// - EnterpriseAttestationMode::PlatformManaged.
|
|
||||||
// For VendorFacilitated, choose an appriopriate ENTERPRISE_RP_ID_LIST.
|
|
||||||
// To enable the feature, send the subcommand enableEnterpriseAttestation in
|
|
||||||
// AuthenticatorConfig. An enterprise might want to customize the type of
|
|
||||||
// attestation that is used. OpenSK defaults to batch attestation. Configuring
|
|
||||||
// individual certificates then makes authenticators identifiable. Do NOT set
|
|
||||||
// USE_BATCH_ATTESTATION to true at the same time in this case! The code asserts
|
|
||||||
// that you don't use the same key material for batch and enterprise attestation.
|
|
||||||
// If you implement your own enterprise attestation mechanism, and you want batch
|
|
||||||
// attestation at the same time, proceed carefully and remove the assertion.
|
|
||||||
pub const ENTERPRISE_ATTESTATION_MODE: Option<EnterpriseAttestationMode> = None;
|
|
||||||
const ENTERPRISE_RP_ID_LIST: &[&str] = &[];
|
|
||||||
// Our credential ID consists of
|
// Our credential ID consists of
|
||||||
// - 16 byte initialization vector for AES-256,
|
// - 16 byte initialization vector for AES-256,
|
||||||
// - 32 byte ECDSA private key for the credential,
|
// - 32 byte ECDSA private key for the credential,
|
||||||
@@ -141,15 +117,6 @@ pub const ES256_CRED_PARAM: PublicKeyCredentialParameter = PublicKeyCredentialPa
|
|||||||
cred_type: PublicKeyCredentialType::PublicKey,
|
cred_type: PublicKeyCredentialType::PublicKey,
|
||||||
alg: SignatureAlgorithm::ES256,
|
alg: SignatureAlgorithm::ES256,
|
||||||
};
|
};
|
||||||
// You can change this value to one of the following for more privacy.
|
|
||||||
// - Some(CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIdList)
|
|
||||||
// - Some(CredentialProtectionPolicy::UserVerificationRequired)
|
|
||||||
const DEFAULT_CRED_PROTECT: Option<CredentialProtectionPolicy> = None;
|
|
||||||
// Maximum size stored with the credBlob extension. Must be at least 32.
|
|
||||||
const MAX_CRED_BLOB_LENGTH: usize = 32;
|
|
||||||
// Enforce the alwaysUv option. With this constant set to true, commands require
|
|
||||||
// a PIN to be set up. alwaysUv can not be disabled by commands.
|
|
||||||
pub const ENFORCE_ALWAYS_UV: bool = false;
|
|
||||||
|
|
||||||
// Checks the PIN protocol parameter against all supported versions.
|
// Checks the PIN protocol parameter against all supported versions.
|
||||||
pub fn check_pin_uv_auth_protocol(
|
pub fn check_pin_uv_auth_protocol(
|
||||||
@@ -329,11 +296,6 @@ where
|
|||||||
check_user_presence: CheckUserPresence,
|
check_user_presence: CheckUserPresence,
|
||||||
now: ClockValue,
|
now: ClockValue,
|
||||||
) -> CtapState<'a, R, CheckUserPresence> {
|
) -> CtapState<'a, R, CheckUserPresence> {
|
||||||
#[allow(clippy::assertions_on_constants)]
|
|
||||||
{
|
|
||||||
assert!(!USE_BATCH_ATTESTATION || ENTERPRISE_ATTESTATION_MODE.is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
let persistent_store = PersistentStore::new(rng);
|
let persistent_store = PersistentStore::new(rng);
|
||||||
let pin_protocol_v1 = PinProtocolV1::new(rng);
|
let pin_protocol_v1 = PinProtocolV1::new(rng);
|
||||||
CtapState {
|
CtapState {
|
||||||
|
|||||||
@@ -14,14 +14,19 @@
|
|||||||
|
|
||||||
mod key;
|
mod key;
|
||||||
|
|
||||||
|
use crate::ctap::customization::{
|
||||||
|
DEFAULT_MIN_PIN_LENGTH, DEFAULT_MIN_PIN_LENGTH_RP_IDS, ENFORCE_ALWAYS_UV,
|
||||||
|
MAX_LARGE_BLOB_ARRAY_SIZE, MAX_PIN_RETRIES, MAX_RP_IDS_LENGTH, MAX_SUPPORTED_RESIDENT_KEYS,
|
||||||
|
NUM_PAGES,
|
||||||
|
};
|
||||||
use crate::ctap::data_formats::{
|
use crate::ctap::data_formats::{
|
||||||
extract_array, extract_text_string, CredentialProtectionPolicy, PublicKeyCredentialSource,
|
extract_array, extract_text_string, CredentialProtectionPolicy, PublicKeyCredentialSource,
|
||||||
PublicKeyCredentialUserEntity,
|
PublicKeyCredentialUserEntity,
|
||||||
};
|
};
|
||||||
|
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::INITIAL_SIGNATURE_COUNTER;
|
use crate::ctap::INITIAL_SIGNATURE_COUNTER;
|
||||||
use crate::ctap::{key_material, ENFORCE_ALWAYS_UV};
|
|
||||||
use crate::embedded_flash::{new_storage, Storage};
|
use crate::embedded_flash::{new_storage, Storage};
|
||||||
use alloc::string::String;
|
use alloc::string::String;
|
||||||
use alloc::vec;
|
use alloc::vec;
|
||||||
@@ -33,35 +38,6 @@ use core::convert::TryInto;
|
|||||||
use crypto::rng256::Rng256;
|
use crypto::rng256::Rng256;
|
||||||
use persistent_store::{fragment, StoreUpdate};
|
use persistent_store::{fragment, StoreUpdate};
|
||||||
|
|
||||||
// Those constants may be modified before compilation to tune the behavior of the key.
|
|
||||||
//
|
|
||||||
// The number of pages should be at least 3 and at most what the flash can hold. There should be no
|
|
||||||
// reason to put a small number here, except that the latency of flash operations is linear in the
|
|
||||||
// number of pages. This may improve in the future. Currently, using 20 pages gives between 20ms and
|
|
||||||
// 240ms per operation. The rule of thumb is between 1ms and 12ms per additional page.
|
|
||||||
//
|
|
||||||
// Limiting the number of resident keys permits to ensure a minimum number of counter increments.
|
|
||||||
// Let:
|
|
||||||
// - P the number of pages (NUM_PAGES)
|
|
||||||
// - K the maximum number of resident keys (MAX_SUPPORTED_RESIDENT_KEYS)
|
|
||||||
// - S the maximum size of a resident key (about 500)
|
|
||||||
// - C the number of erase cycles (10000)
|
|
||||||
// - I the minimum number of counter increments
|
|
||||||
//
|
|
||||||
// We have: I = (P * 4084 - 5107 - K * S) / 8 * C
|
|
||||||
//
|
|
||||||
// With P=20 and K=150, we have I=2M which is enough for 500 increments per day for 10 years.
|
|
||||||
const NUM_PAGES: usize = 20;
|
|
||||||
const MAX_SUPPORTED_RESIDENT_KEYS: usize = 150;
|
|
||||||
|
|
||||||
const MAX_PIN_RETRIES: u8 = 8;
|
|
||||||
const DEFAULT_MIN_PIN_LENGTH: u8 = 4;
|
|
||||||
const DEFAULT_MIN_PIN_LENGTH_RP_IDS: &[&str] = &[];
|
|
||||||
// This constant is an attempt to limit storage requirements. If you don't set it to 0,
|
|
||||||
// the stored strings can still be unbounded, but that is true for all RP IDs.
|
|
||||||
pub const MAX_RP_IDS_LENGTH: usize = 8;
|
|
||||||
pub const MAX_LARGE_BLOB_ARRAY_SIZE: usize = 2048;
|
|
||||||
|
|
||||||
/// Wrapper for master keys.
|
/// Wrapper for master keys.
|
||||||
pub struct MasterKeys {
|
pub struct MasterKeys {
|
||||||
/// Master encryption key.
|
/// Master encryption key.
|
||||||
@@ -825,7 +801,7 @@ mod test {
|
|||||||
|
|
||||||
let mut credential_ids = vec![];
|
let mut credential_ids = vec![];
|
||||||
for i in 0..MAX_SUPPORTED_RESIDENT_KEYS {
|
for i in 0..MAX_SUPPORTED_RESIDENT_KEYS {
|
||||||
let user_handle = i.to_ne_bytes().to_vec();
|
let user_handle = (i as u32).to_ne_bytes().to_vec();
|
||||||
let credential_source = create_credential_source(&mut rng, "example.com", user_handle);
|
let credential_source = create_credential_source(&mut rng, "example.com", user_handle);
|
||||||
credential_ids.push(credential_source.credential_id.clone());
|
credential_ids.push(credential_source.credential_id.clone());
|
||||||
assert!(persistent_store.store_credential(credential_source).is_ok());
|
assert!(persistent_store.store_credential(credential_source).is_ok());
|
||||||
@@ -899,7 +875,7 @@ mod test {
|
|||||||
assert_eq!(persistent_store.count_credentials().unwrap(), 0);
|
assert_eq!(persistent_store.count_credentials().unwrap(), 0);
|
||||||
|
|
||||||
for i in 0..MAX_SUPPORTED_RESIDENT_KEYS {
|
for i in 0..MAX_SUPPORTED_RESIDENT_KEYS {
|
||||||
let user_handle = i.to_ne_bytes().to_vec();
|
let user_handle = (i as u32).to_ne_bytes().to_vec();
|
||||||
let credential_source = create_credential_source(&mut rng, "example.com", user_handle);
|
let credential_source = create_credential_source(&mut rng, "example.com", user_handle);
|
||||||
assert!(persistent_store.store_credential(credential_source).is_ok());
|
assert!(persistent_store.store_credential(credential_source).is_ok());
|
||||||
assert_eq!(persistent_store.count_credentials().unwrap(), i + 1);
|
assert_eq!(persistent_store.count_credentials().unwrap(), i + 1);
|
||||||
@@ -948,7 +924,7 @@ mod test {
|
|||||||
|
|
||||||
let mut persistent_store = PersistentStore::new(&mut rng);
|
let mut persistent_store = PersistentStore::new(&mut rng);
|
||||||
for i in 0..MAX_SUPPORTED_RESIDENT_KEYS {
|
for i in 0..MAX_SUPPORTED_RESIDENT_KEYS {
|
||||||
let user_handle = i.to_ne_bytes().to_vec();
|
let user_handle = (i as u32).to_ne_bytes().to_vec();
|
||||||
let credential_source = create_credential_source(&mut rng, "example.com", user_handle);
|
let credential_source = create_credential_source(&mut rng, "example.com", user_handle);
|
||||||
assert!(persistent_store.store_credential(credential_source).is_ok());
|
assert!(persistent_store.store_credential(credential_source).is_ok());
|
||||||
assert_eq!(persistent_store.count_credentials().unwrap(), i + 1);
|
assert_eq!(persistent_store.count_credentials().unwrap(), i + 1);
|
||||||
@@ -1247,10 +1223,6 @@ mod test {
|
|||||||
let mut rng = ThreadRng256 {};
|
let mut rng = ThreadRng256 {};
|
||||||
let persistent_store = PersistentStore::new(&mut rng);
|
let persistent_store = PersistentStore::new(&mut rng);
|
||||||
|
|
||||||
#[allow(clippy::assertions_on_constants)]
|
|
||||||
{
|
|
||||||
assert!(MAX_LARGE_BLOB_ARRAY_SIZE >= 1024);
|
|
||||||
}
|
|
||||||
assert!(
|
assert!(
|
||||||
MAX_LARGE_BLOB_ARRAY_SIZE
|
MAX_LARGE_BLOB_ARRAY_SIZE
|
||||||
<= persistent_store.store.max_value_length()
|
<= persistent_store.store.max_value_length()
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn enough_credentials() {
|
fn enough_credentials() {
|
||||||
use super::super::MAX_SUPPORTED_RESIDENT_KEYS;
|
use crate::ctap::customization::MAX_SUPPORTED_RESIDENT_KEYS;
|
||||||
assert!(MAX_SUPPORTED_RESIDENT_KEYS <= CREDENTIALS.end - CREDENTIALS.start);
|
assert!(MAX_SUPPORTED_RESIDENT_KEYS <= CREDENTIALS.end - CREDENTIALS.start);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user