Skip to content

Commit

Permalink
Add credBlob-extension
Browse files Browse the repository at this point in the history
  • Loading branch information
Martin Sirringhaus committed Feb 1, 2024
1 parent b21f8fa commit 3a6fd8f
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 21 deletions.
44 changes: 31 additions & 13 deletions examples/ctap2_discoverable_creds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,19 @@ use authenticator::{
authenticatorservice::{AuthenticatorService, RegisterArgs, SignArgs},
crypto::COSEAlgorithm,
ctap2::server::{
AuthenticationExtensionsClientInputs, PublicKeyCredentialDescriptor,
PublicKeyCredentialParameters, PublicKeyCredentialUserEntity, RelyingParty,
ResidentKeyRequirement, Transport, UserVerificationRequirement,
AuthenticationExtensionsClientInputs, AuthenticatorExtensionsCredBlob,
PublicKeyCredentialDescriptor, PublicKeyCredentialParameters,
PublicKeyCredentialUserEntity, RelyingParty, ResidentKeyRequirement, Transport,
UserVerificationRequirement,
},
statecallback::StateCallback,
Pin, StatusPinUv, StatusUpdate,
};
use getopts::Options;
use getopts::{Matches, Options};
use sha2::{Digest, Sha256};
use std::io::Write;
use std::sync::mpsc::{channel, RecvError};
use std::{env, io, thread};
use std::io::Write;

fn print_usage(program: &str, opts: Options) {
println!("------------------------------------------------------------------------");
Expand Down Expand Up @@ -60,7 +61,12 @@ fn ask_user_choice(choices: &[PublicKeyCredentialUserEntity]) -> Option<usize> {
}
}

fn register_user(manager: &mut AuthenticatorService, username: &str, timeout_ms: u64) {
fn register_user(
manager: &mut AuthenticatorService,
username: &str,
timeout_ms: u64,
matches: &Matches,
) {
println!();
println!("*********************************************************************");
println!("Asking a security key to register now with user: {username}");
Expand Down Expand Up @@ -170,6 +176,9 @@ fn register_user(manager: &mut AuthenticatorService, username: &str, timeout_ms:
resident_key_req: ResidentKeyRequirement::Required,
extensions: AuthenticationExtensionsClientInputs {
cred_props: Some(true),
cred_blob: matches.opt_present("cred_blob").then(|| {
AuthenticatorExtensionsCredBlob::AsBytes("My short credBlob".as_bytes().to_vec())
}),
..Default::default()
},
pin: None,
Expand Down Expand Up @@ -216,10 +225,8 @@ fn main() {
"timeout in seconds",
"SEC",
);
opts.optflag(
"s",
"skip_reg",
"Skip registration");
opts.optflag("s", "skip_reg", "Skip registration");
opts.optflag("b", "cred_blob", "With credBlob");

opts.optflag("h", "help", "print this help menu");
let matches = match opts.parse(&args[1..]) {
Expand Down Expand Up @@ -249,7 +256,7 @@ fn main() {

if !matches.opt_present("skip_reg") {
for username in &["A. User", "A. Nother", "Dr. Who"] {
register_user(&mut manager, username, timeout_ms)
register_user(&mut manager, username, timeout_ms, &matches)
}
}

Expand Down Expand Up @@ -341,7 +348,12 @@ fn main() {
allow_list,
user_verification_req: UserVerificationRequirement::Required,
user_presence_req: true,
extensions: Default::default(),
extensions: AuthenticationExtensionsClientInputs {
cred_blob: matches
.opt_present("cred_blob")
.then_some(AuthenticatorExtensionsCredBlob::AsBool(true)),
..Default::default()
},
pin: None,
use_ctap1_fallback: false,
};
Expand All @@ -368,7 +380,13 @@ fn main() {
println!("Found credentials:");
println!(
"{:?}",
assertion_object.assertion.user.clone().unwrap().name.unwrap() // Unwrapping here, as these shouldn't fail
assertion_object
.assertion
.user
.clone()
.unwrap()
.name
.unwrap() // Unwrapping here, as these shouldn't fail
);
println!("-----------------------------------------------------------------");
println!("Done.");
Expand Down
8 changes: 7 additions & 1 deletion src/ctap2/attestation.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use super::server::AuthenticatorExtensionsCredBlob;
use super::utils::{from_slice_stream, read_be_u16, read_be_u32, read_byte};
use crate::crypto::COSEAlgorithm;
use crate::ctap2::server::{CredentialProtectionPolicy, RpIdHash};
Expand Down Expand Up @@ -74,11 +75,16 @@ pub struct Extension {
pub hmac_secret: Option<HmacSecretResponse>,
#[serde(rename = "minPinLength", skip_serializing_if = "Option::is_none")]
pub min_pin_length: Option<u64>,
#[serde(rename = "credBlob", skip_serializing_if = "Option::is_none")]
pub cred_blob: Option<AuthenticatorExtensionsCredBlob>,
}

impl Extension {
pub fn has_some(&self) -> bool {
self.min_pin_length.is_some() || self.hmac_secret.is_some() || self.cred_protect.is_some()
self.min_pin_length.is_some()
|| self.hmac_secret.is_some()
|| self.cred_protect.is_some()
|| self.cred_blob.is_some()
}
}

Expand Down
17 changes: 14 additions & 3 deletions src/ctap2/commands/get_assertion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ use crate::ctap2::commands::get_next_assertion::GetNextAssertion;
use crate::ctap2::commands::make_credentials::UserVerification;
use crate::ctap2::server::{
AuthenticationExtensionsClientInputs, AuthenticationExtensionsClientOutputs,
AuthenticatorAttachment, PublicKeyCredentialDescriptor, PublicKeyCredentialUserEntity,
RelyingParty, RpIdHash, UserVerificationRequirement,
AuthenticatorAttachment, AuthenticatorExtensionsCredBlob, PublicKeyCredentialDescriptor,
PublicKeyCredentialUserEntity, RelyingParty, RpIdHash, UserVerificationRequirement,
};
use crate::ctap2::utils::{read_be_u32, read_byte};
use crate::errors::AuthenticatorError;
Expand Down Expand Up @@ -140,20 +140,26 @@ pub struct GetAssertionExtensions {
pub app_id: Option<String>,
#[serde(rename = "hmac-secret", skip_serializing_if = "Option::is_none")]
pub hmac_secret: Option<HmacSecretExtension>,
#[serde(rename = "credBlob", skip_serializing_if = "Option::is_none")]
pub cred_blob: Option<bool>,
}

impl From<AuthenticationExtensionsClientInputs> for GetAssertionExtensions {
fn from(input: AuthenticationExtensionsClientInputs) -> Self {
Self {
app_id: input.app_id,
cred_blob: match input.cred_blob {
Some(AuthenticatorExtensionsCredBlob::AsBool(x)) => Some(x),
_ => None,
},
..Default::default()
}
}
}

impl GetAssertionExtensions {
fn has_content(&self) -> bool {
self.hmac_secret.is_some()
self.hmac_secret.is_some() || self.cred_blob.is_some()
}
}

Expand Down Expand Up @@ -205,6 +211,11 @@ impl GetAssertion {
result.extensions.app_id =
Some(result.assertion.auth_data.rp_id_hash == RelyingParty::from(app_id).hash());
}

// 2. credBlob
// The extension returns a flag in the authenticator data which we need to mirror as a
// client output.
result.extensions.cred_blob = result.assertion.auth_data.extensions.cred_blob.clone();
}
}

Expand Down
19 changes: 15 additions & 4 deletions src/ctap2/commands/make_credentials.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ use crate::ctap2::attestation::{
use crate::ctap2::client_data::ClientDataHash;
use crate::ctap2::server::{
AuthenticationExtensionsClientInputs, AuthenticationExtensionsClientOutputs,
AuthenticatorAttachment, CredentialProtectionPolicy, PublicKeyCredentialDescriptor,
PublicKeyCredentialParameters, PublicKeyCredentialUserEntity, RelyingParty, RpIdHash,
UserVerificationRequirement,
AuthenticatorAttachment, AuthenticatorExtensionsCredBlob, CredentialProtectionPolicy,
PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, PublicKeyCredentialUserEntity,
RelyingParty, RpIdHash, UserVerificationRequirement,
};
use crate::ctap2::utils::{read_byte, serde_parse_err};
use crate::errors::AuthenticatorError;
Expand Down Expand Up @@ -243,11 +243,16 @@ pub struct MakeCredentialsExtensions {
pub hmac_secret: Option<bool>,
#[serde(rename = "minPinLength", skip_serializing_if = "Option::is_none")]
pub min_pin_length: Option<bool>,
#[serde(rename = "credBlob", skip_serializing_if = "Option::is_none")]
pub cred_blob: Option<AuthenticatorExtensionsCredBlob>,
}

impl MakeCredentialsExtensions {
fn has_content(&self) -> bool {
self.cred_protect.is_some() || self.hmac_secret.is_some() || self.min_pin_length.is_some()
self.cred_protect.is_some()
|| self.hmac_secret.is_some()
|| self.min_pin_length.is_some()
|| self.cred_blob.is_some()
}
}

Expand All @@ -258,6 +263,7 @@ impl From<AuthenticationExtensionsClientInputs> for MakeCredentialsExtensions {
cred_protect: input.credential_protection_policy,
hmac_secret: input.hmac_create_secret,
min_pin_length: input.min_pin_length,
cred_blob: input.cred_blob,
}
}
}
Expand Down Expand Up @@ -350,6 +356,11 @@ impl MakeCredentials {
result.extensions.hmac_create_secret = Some(flag);
}
}

// 3. credBlob
// The extension returns a flag in the authenticator data which we need to mirror as a
// client output.
result.extensions.cred_blob = result.att_obj.auth_data.extensions.cred_blob.clone();
}
}

Expand Down
35 changes: 35 additions & 0 deletions src/ctap2/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,35 @@ impl<'de> Deserialize<'de> for CredentialProtectionPolicy {
}
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum AuthenticatorExtensionsCredBlob {
/// Used in GetAssertion-requests to request the stored blob,
/// and in MakeCredential-responses to signify if the
/// storing worked.
AsBool(bool),
/// Used in MakeCredential-requests to store a new credBlob,
/// and in GetAssertion-responses when retrieving the
/// stored blob.
#[serde(serialize_with = "vec_to_bytebuf", deserialize_with = "bytebuf_to_vec")]
AsBytes(Vec<u8>),
}

fn vec_to_bytebuf<S>(data: &[u8], s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
ByteBuf::from(data).serialize(s)
}

fn bytebuf_to_vec<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
where
D: Deserializer<'de>,
{
let bytes = <ByteBuf>::deserialize(deserializer)?;
Ok(bytes.to_vec())
}

#[derive(Clone, Debug, Default)]
pub struct AuthenticationExtensionsClientInputs {
pub app_id: Option<String>,
Expand All @@ -365,6 +394,9 @@ pub struct AuthenticationExtensionsClientInputs {
pub enforce_credential_protection_policy: Option<bool>,
pub hmac_create_secret: Option<bool>,
pub min_pin_length: Option<bool>,
/// MakeCredential-requests use AsBytes
/// GetAssertion-requests use AsBool
pub cred_blob: Option<AuthenticatorExtensionsCredBlob>,
}

#[derive(Clone, Debug, Default, Eq, PartialEq)]
Expand All @@ -377,6 +409,9 @@ pub struct AuthenticationExtensionsClientOutputs {
pub app_id: Option<bool>,
pub cred_props: Option<CredentialProperties>,
pub hmac_create_secret: Option<bool>,
/// MakeCredential-responses use AsBool
/// GetAssertion-responses use AsBytes
pub cred_blob: Option<AuthenticatorExtensionsCredBlob>,
}

#[derive(Clone, Debug, PartialEq, Eq)]
Expand Down

0 comments on commit 3a6fd8f

Please sign in to comment.