Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ features = [
path = "libraries/opensk"
default-features = false

[dependencies.sk-cbor]
path = "libraries/cbor"

[features]
config-command = ["opensk/config_command"]
ctap1 = ["opensk/ctap1"]
Expand All @@ -44,6 +47,3 @@ ed25519 = ["opensk/ed25519", "wasefire/api-crypto-ed25519"]
fingerprint = ["dep:wasefire-common", "opensk/fingerprint", "wasefire/api-fingerprint-matcher"]
led-1 = []
test = ["opensk/std", "wasefire/test"]

[build-dependencies]
uuid = { version = "0.8", features = ["v4"] }
37 changes: 0 additions & 37 deletions build.rs

This file was deleted.

37 changes: 25 additions & 12 deletions docs/customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,26 +27,39 @@ If you delete the key material, it will be randomly regenerated by `setup.sh`.

If you either use the `ctap1` feature or set `use_batch_attestation` in the
customization, OpenSK needs to send an attestation when you register a
credential on a website. By default, this attestation will be randomly
generated on each registration attempt. When you inject a batch attestation key,
it will instead be used.
credential on a website. By default, OpenSK uses

This attestation should be proof for a website that the security key is indeed
a hardware security key. A self-made OpenSK with a randomly generated batch
attestation key will not prove anything. In practise, a few more relying parties
can be used with OpenSK because of this trick. Also, U2F requires batch
attestation key material to work.
- an all-zero AAGUID,
- a randomly generated batch attestation key,
- an empty batch attestation certificate.

You can inject your own batch attestation key into the firmware. Tooling for
that is currently being reworked after the move to Wasefire. Before you do that,
a warning:
You can inject the above data into OpenSK with a custom vendor command.
However, this means that all registered credentials using this batch attestation
data can be correlated. For locked hardware security keys, this feature gives
relying parties proof that they speak to secure hardware. They compare the
AAGUID to those registered with the FIDO Alliance.

Usually, the attestation private key is shared between a batch of at least
100,000 security keys of the same model. If you build your own OpenSK, your
private key is unique to you. This makes you identifiable across registrations:
Two websites could collaborate to track if registrations were attested with the
same key material. If you use OpenSK beyond experimentation, please consider
carefully if you want to take this privacy risk.
carefully if you want to take the privacy risk of using the configure tool.

The default randomly generated, ephemeral batch attestation keys, are helpful in
practise: Without a key, U2F does not work. Also a few more relying parties
accept OpenSK responses because of this trick. It is not meant to proof any
Comment thread
kaczmarczyck marked this conversation as resolved.
Outdated
hardware security properties.

To inject your own batch attestation key and AAGUID into the firmware, run:

```sh
# Read the privacy warning above!
uv run tools/configure.py \
--certificate=crypto_data/opensk_cert.pem \
--private-key=crypto_data/opensk.key \
--aaguid=crypto_data/aaguid.txt
```

### Personalization

Expand Down
8 changes: 8 additions & 0 deletions docs/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,11 @@ connected to R13.
```sh
./flash.sh opentitan
```

## Configuring the firmware

After flashing the firmware, you can configure it.
You can use a custom AAGUID, batch attestation key and certificate.
Only perform this step if you understand the privacy implications. Read the
[certificate section in Customization](customization.md#Certificate-considerations)
to find the necessary command.
13 changes: 0 additions & 13 deletions libraries/opensk/src/api/customization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,7 @@ use crate::ctap::data_formats::{CredentialProtectionPolicy, EnterpriseAttestatio
use alloc::string::String;
use alloc::vec::Vec;

pub const AAGUID_LENGTH: usize = 16;

pub trait Customization {
/// Authenticator Attestation Global Unique Identifier
fn aaguid(&self) -> &'static [u8; AAGUID_LENGTH];

// ###########################################################################
// Constants for adjusting privacy and protection levels.
// ###########################################################################
Expand Down Expand Up @@ -298,7 +293,6 @@ pub trait Customization {

#[derive(Clone)]
pub struct CustomizationImpl {
pub aaguid: &'static [u8; AAGUID_LENGTH],
pub allows_pin_protocol_v1: bool,
pub default_cred_protect: Option<CredentialProtectionPolicy>,
pub default_min_pin_length: u8,
Expand Down Expand Up @@ -326,7 +320,6 @@ pub struct CustomizationImpl {
}

pub const DEFAULT_CUSTOMIZATION: CustomizationImpl = CustomizationImpl {
aaguid: &[0; AAGUID_LENGTH],
allows_pin_protocol_v1: true,
default_cred_protect: None,
default_min_pin_length: 4,
Expand Down Expand Up @@ -354,10 +347,6 @@ pub const DEFAULT_CUSTOMIZATION: CustomizationImpl = CustomizationImpl {
};

impl Customization for CustomizationImpl {
fn aaguid(&self) -> &'static [u8; AAGUID_LENGTH] {
self.aaguid
}

fn allows_pin_protocol_v1(&self) -> bool {
self.allows_pin_protocol_v1
}
Expand Down Expand Up @@ -565,7 +554,6 @@ mod test {
#[test]
fn test_accessors() {
let customization = CustomizationImpl {
aaguid: &[0; AAGUID_LENGTH],
allows_pin_protocol_v1: true,
default_cred_protect: None,
default_min_pin_length: 4,
Expand All @@ -591,7 +579,6 @@ mod test {
#[cfg(feature = "fingerprint")]
max_template_friendly_name: 64,
};
assert_eq!(customization.aaguid(), &[0; AAGUID_LENGTH]);
assert!(customization.allows_pin_protocol_v1());
assert!(customization.default_cred_protect().is_none());
assert_eq!(customization.default_min_pin_length(), 4);
Expand Down
32 changes: 32 additions & 0 deletions libraries/opensk/src/api/persist.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ use enum_iterator::IntoEnumIterator;
use sk_cbor as cbor;
use sk_cbor::destructure_cbor_map;

pub const AAGUID_LENGTH: usize = 16;

pub type PersistIter<'a> = Box<dyn Iterator<Item = CtapResult<usize>> + 'a>;
pub type PersistCredentialIter<'a> = Box<dyn Iterator<Item = CtapResult<(usize, Vec<u8>)>> + 'a>;
pub type LargeBlobBuffer = Vec<u8>;
Expand Down Expand Up @@ -581,6 +583,24 @@ pub trait Persist {
Ok(())
}

/// Returns the programmed AAGUID, defaulting to all zeros if not set.
fn aaguid(&self) -> CtapResult<[u8; AAGUID_LENGTH]> {
match self.find(keys::AAGUID)? {
None => Ok([0; AAGUID_LENGTH]),
Some(value) if value.len() == AAGUID_LENGTH => {
let mut aaguid = [0; AAGUID_LENGTH];
aaguid.copy_from_slice(&value);
Ok(aaguid)
}
_ => Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR),
}
}

/// Sets the AAGUID.
fn set_aaguid(&mut self, aaguid: &[u8; AAGUID_LENGTH]) -> CtapResult<()> {
self.insert(keys::AAGUID, aaguid)
}

fn key_store_bytes(&self) -> CtapResult<Option<Secret<[u8]>>> {
let bytes = self.find(keys::KEY_STORE)?;
Ok(bytes.map(|b| {
Expand Down Expand Up @@ -871,4 +891,16 @@ mod test {
};
assert_eq!(returned_attestation, expected_attestation);
}

#[test]
fn test_aaguid() {
let mut env = TestEnv::default();
let persist = env.persist();

assert_eq!(persist.aaguid(), Ok([0; AAGUID_LENGTH]));

let test_aaguid = [1; AAGUID_LENGTH];
assert_eq!(persist.set_aaguid(&test_aaguid), Ok(()));
assert_eq!(persist.aaguid(), Ok(test_aaguid));
}
}
4 changes: 2 additions & 2 deletions libraries/opensk/src/api/persist/keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ make_partition! {
/// Type of attestation used.
ATTESTATION_ID = 4;

/// Used for the AAGUID before, but deprecated.
_AAGUID = 3;
/// Authenticator Attestation Global Unique Identifier.
AAGUID = 3;

// This is the persistent key limit:
// - When adding a (persistent) key above this message, make sure its value is smaller than
Expand Down
14 changes: 8 additions & 6 deletions libraries/opensk/src/ctap/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -978,7 +978,7 @@ impl<E: Env> CtapState<E> {
};

let mut auth_data = self.generate_auth_data(env, &rp_id_hash, flags)?;
auth_data.extend(env.customization().aaguid());
auth_data.extend(env.persist().aaguid()?);
// The length is fixed to 0x20 or 0x80 and fits one byte.
if credential_id.len() > 0xFF {
return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
Expand Down Expand Up @@ -1390,7 +1390,7 @@ impl<E: Env> CtapState<E> {
String::from("credBlob"),
String::from("largeBlobKey"),
]),
aaguid: *env.customization().aaguid(),
aaguid: env.persist().aaguid()?,
options: Some(options),
max_msg_size: Some(env.customization().max_msg_size() as u64),
// The order implies preference. We favor the new V2.
Expand Down Expand Up @@ -1536,7 +1536,7 @@ mod test {
expected_credential_id_size: u8,
expected_extension_cbor: &[u8],
) {
let expected_aaguid = env.customization().aaguid();
let expected_aaguid = env.persist().aaguid().unwrap();
let signature_counter = env.persist().global_signature_counter().unwrap();
match make_credential_response.as_ref().unwrap() {
ResponseData::AuthenticatorMakeCredential(make_credential_response) => {
Expand Down Expand Up @@ -1577,6 +1577,7 @@ mod test {
#[cfg(feature = "fingerprint")]
fn test_get_info() {
let mut env = TestEnv::default();
let aaguid = env.persist().aaguid().unwrap();
let mut ctap_state = CtapState::<TestEnv>::new(&mut env);
let info_reponse = ctap_state.process_command(&mut env, &[0x04], DUMMY_CHANNEL);

Expand All @@ -1596,7 +1597,7 @@ mod test {
String::from("credBlob"),
String::from("largeBlobKey"),
],
0x03 => env.customization().aaguid(),
0x03 => &aaguid[..],
0x04 => cbor_map_options! {
"ep" => env.customization().enterprise_attestation_mode().map(|_| false),
"rk" => true,
Expand Down Expand Up @@ -1643,6 +1644,7 @@ mod test {
#[cfg(not(feature = "fingerprint"))]
fn test_get_info() {
let mut env = TestEnv::default();
let aaguid = env.persist().aaguid().unwrap();
let mut ctap_state = CtapState::<TestEnv>::new(&mut env);
let info_reponse = ctap_state.process_command(&mut env, &[0x04], DUMMY_CHANNEL);

Expand All @@ -1662,7 +1664,7 @@ mod test {
String::from("credBlob"),
String::from("largeBlobKey"),
],
0x03 => env.customization().aaguid(),
0x03 => &aaguid[..],
0x04 => cbor_map_options! {
"ep" => env.customization().enterprise_attestation_mode().map(|_| false),
"rk" => true,
Expand Down Expand Up @@ -1783,7 +1785,7 @@ mod test {
match make_credential_response {
ResponseData::AuthenticatorMakeCredential(make_credential_response) => {
let auth_data = make_credential_response.auth_data;
let offset = 37 + env.customization().aaguid().len();
let offset = 37 + env.persist().aaguid().unwrap().len();
assert_eq!(auth_data[offset], 0x00);
assert_eq!(auth_data[offset + 1] as usize, CBOR_CREDENTIAL_ID_SIZE);
auth_data[offset + 2..offset + 2 + CBOR_CREDENTIAL_ID_SIZE].to_vec()
Expand Down
9 changes: 1 addition & 8 deletions libraries/opensk/src/env/test/customization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use crate::api::customization::{AAGUID_LENGTH, Customization, CustomizationImpl};
use crate::api::customization::{Customization, CustomizationImpl};
use crate::ctap::data_formats::{CredentialProtectionPolicy, EnterpriseAttestationMode};
use alloc::string::String;
use alloc::vec::Vec;

pub struct TestCustomization {
aaguid: &'static [u8; AAGUID_LENGTH],
allows_pin_protocol_v1: bool,
default_cred_protect: Option<CredentialProtectionPolicy>,
default_min_pin_length: u8,
Expand Down Expand Up @@ -63,10 +62,6 @@ impl TestCustomization {
}

impl Customization for TestCustomization {
fn aaguid(&self) -> &'static [u8; AAGUID_LENGTH] {
self.aaguid
}

fn allows_pin_protocol_v1(&self) -> bool {
self.allows_pin_protocol_v1
}
Expand Down Expand Up @@ -159,7 +154,6 @@ impl Customization for TestCustomization {
impl From<CustomizationImpl> for TestCustomization {
fn from(c: CustomizationImpl) -> Self {
let CustomizationImpl {
aaguid,
allows_pin_protocol_v1,
default_cred_protect,
default_min_pin_length,
Expand Down Expand Up @@ -197,7 +191,6 @@ impl From<CustomizationImpl> for TestCustomization {
.collect::<Vec<_>>();

Self {
aaguid,
allows_pin_protocol_v1,
default_cred_protect,
default_min_pin_length,
Expand Down
Loading
Loading