Skip to content

Commit

Permalink
Add credBlob-extension
Browse files Browse the repository at this point in the history
  • Loading branch information
Martin Sirringhaus authored and msirringhaus committed Jul 30, 2024
1 parent b04ce2f commit c2adc07
Show file tree
Hide file tree
Showing 5 changed files with 108 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 @@ -168,6 +174,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 @@ -214,10 +223,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 @@ -247,7 +254,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 @@ -337,7 +344,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 @@ -364,7 +376,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, CryptoError, SharedSecret};
use crate::ctap2::server::{CredentialProtectionPolicy, HMACGetSecretOutput, RpIdHash};
Expand Down Expand Up @@ -119,11 +120,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
22 changes: 19 additions & 3 deletions src/ctap2/commands/get_assertion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ use crate::ctap2::commands::make_credentials::UserVerification;
use crate::ctap2::server::{
AuthenticationExtensionsClientInputs, AuthenticationExtensionsClientOutputs,
AuthenticationExtensionsPRFInputs, AuthenticationExtensionsPRFOutputs, AuthenticatorAttachment,
PublicKeyCredentialDescriptor, PublicKeyCredentialUserEntity, RelyingParty, RpIdHash,
UserVerificationRequirement,
AuthenticatorExtensionsCredBlob, PublicKeyCredentialDescriptor, PublicKeyCredentialUserEntity,
RelyingParty, RpIdHash, UserVerificationRequirement,
};
use crate::ctap2::utils::{read_be_u32, read_byte};
use crate::errors::AuthenticatorError;
Expand Down Expand Up @@ -253,6 +253,8 @@ pub struct GetAssertionExtensions {
skip_serializing_if = "HmacGetSecretOrPrf::skip_serializing"
)]
pub hmac_secret: Option<HmacGetSecretOrPrf>,
#[serde(rename = "credBlob", skip_serializing_if = "Option::is_none")]
pub cred_blob: Option<bool>,
}

impl From<AuthenticationExtensionsClientInputs> for GetAssertionExtensions {
Expand All @@ -271,13 +273,17 @@ impl From<AuthenticationExtensionsClientInputs> for GetAssertionExtensions {
.or_else(
|| prf.map(HmacGetSecretOrPrf::PrfUninitialized), // Cannot calculate hmac-secret inputs here because we don't yet know which eval or evalByCredential entry to use
),
cred_blob: match input.cred_blob {
Some(AuthenticatorExtensionsCredBlob::AsBool(x)) => Some(x),
_ => None,
},
}
}
}

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 @@ -432,6 +438,11 @@ impl GetAssertion {
}
None => {}
}

// 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.assertion.auth_data.extensions.cred_blob.clone();
}
}

Expand Down Expand Up @@ -1048,6 +1059,7 @@ pub mod test {
None,
),
)),
cred_blob: None,
},
options: GetAssertionOptions {
user_presence: Some(true),
Expand Down Expand Up @@ -1105,6 +1117,7 @@ pub mod test {
Some(2),
),
)),
cred_blob: None,
},
options: GetAssertionOptions {
user_presence: None,
Expand Down Expand Up @@ -1150,6 +1163,7 @@ pub mod test {
eval_by_credential: None,
},
)),
cred_blob: None,
},
options: GetAssertionOptions {
user_presence: None,
Expand All @@ -1171,6 +1185,7 @@ pub mod test {
extensions: GetAssertionExtensions {
app_id: None,
hmac_secret: Some(HmacGetSecretOrPrf::PrfUnmatched),
cred_blob: None,
},
options: GetAssertionOptions {
user_presence: None,
Expand Down Expand Up @@ -2828,6 +2843,7 @@ pub mod test {
cred_protect: None,
hmac_secret: hmac_secret_response,
min_pin_length: None,
cred_blob: None,
},
},
signature: vec![],
Expand Down
20 changes: 16 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,
AuthenticationExtensionsPRFOutputs, AuthenticatorAttachment, CredentialProtectionPolicy,
PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, PublicKeyCredentialUserEntity,
RelyingParty, RpIdHash, UserVerificationRequirement,
AuthenticationExtensionsPRFOutputs, 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 @@ -242,6 +242,8 @@ pub struct MakeCredentialsExtensions {
pub hmac_secret: Option<HmacCreateSecretOrPrf>,
#[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>,
}

#[derive(Debug, Clone)]
Expand All @@ -264,7 +266,10 @@ impl Serialize for HmacCreateSecretOrPrf {

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 @@ -281,6 +286,7 @@ impl From<AuthenticationExtensionsClientInputs> for MakeCredentialsExtensions {
}
},
min_pin_length: input.min_pin_length,
cred_blob: input.cred_blob,
}
}
}
Expand Down Expand Up @@ -409,6 +415,11 @@ impl MakeCredentials {
}
None | Some(HmacCreateSecretOrPrf::HmacCreateSecret(false)) => {}
}

// 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 Expand Up @@ -755,6 +766,7 @@ pub mod test {
),
hmac_secret: Some(HmacCreateSecretOrPrf::HmacCreateSecret(true)),
min_pin_length: Some(true),
cred_blob: None,
},
options: MakeCredentialsOptions {
resident_key: Some(true),
Expand Down
35 changes: 35 additions & 0 deletions src/ctap2/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,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 @@ -364,6 +393,9 @@ pub struct AuthenticationExtensionsClientInputs {
pub hmac_get_secret: Option<HMACGetSecretInput>,
pub min_pin_length: Option<bool>,
pub prf: Option<AuthenticationExtensionsPRFInputs>,
/// MakeCredential-requests use AsBytes
/// GetAssertion-requests use AsBool
pub cred_blob: Option<AuthenticatorExtensionsCredBlob>,
}

#[derive(Clone, Debug, Default, Eq, PartialEq)]
Expand Down Expand Up @@ -495,6 +527,9 @@ pub struct AuthenticationExtensionsClientOutputs {
pub hmac_create_secret: Option<bool>,
pub hmac_get_secret: Option<HMACGetSecretOutput>,
pub prf: Option<AuthenticationExtensionsPRFOutputs>,
/// MakeCredential-responses use AsBool
/// GetAssertion-responses use AsBytes
pub cred_blob: Option<AuthenticatorExtensionsCredBlob>,
}

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

0 comments on commit c2adc07

Please sign in to comment.