Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add credBlob-extension #329

Open
wants to merge 1 commit into
base: ctap2-2021
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
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
Loading