Skip to content

Commit c2adc07

Browse files
Martin Sirringhausmsirringhaus
authored andcommitted
Add credBlob-extension
1 parent b04ce2f commit c2adc07

File tree

5 files changed

+108
-21
lines changed

5 files changed

+108
-21
lines changed

examples/ctap2_discoverable_creds.rs

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,19 @@ use authenticator::{
66
authenticatorservice::{AuthenticatorService, RegisterArgs, SignArgs},
77
crypto::COSEAlgorithm,
88
ctap2::server::{
9-
AuthenticationExtensionsClientInputs, PublicKeyCredentialDescriptor,
10-
PublicKeyCredentialParameters, PublicKeyCredentialUserEntity, RelyingParty,
11-
ResidentKeyRequirement, Transport, UserVerificationRequirement,
9+
AuthenticationExtensionsClientInputs, AuthenticatorExtensionsCredBlob,
10+
PublicKeyCredentialDescriptor, PublicKeyCredentialParameters,
11+
PublicKeyCredentialUserEntity, RelyingParty, ResidentKeyRequirement, Transport,
12+
UserVerificationRequirement,
1213
},
1314
statecallback::StateCallback,
1415
Pin, StatusPinUv, StatusUpdate,
1516
};
16-
use getopts::Options;
17+
use getopts::{Matches, Options};
1718
use sha2::{Digest, Sha256};
19+
use std::io::Write;
1820
use std::sync::mpsc::{channel, RecvError};
1921
use std::{env, io, thread};
20-
use std::io::Write;
2122

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

63-
fn register_user(manager: &mut AuthenticatorService, username: &str, timeout_ms: u64) {
64+
fn register_user(
65+
manager: &mut AuthenticatorService,
66+
username: &str,
67+
timeout_ms: u64,
68+
matches: &Matches,
69+
) {
6470
println!();
6571
println!("*********************************************************************");
6672
println!("Asking a security key to register now with user: {username}");
@@ -168,6 +174,9 @@ fn register_user(manager: &mut AuthenticatorService, username: &str, timeout_ms:
168174
resident_key_req: ResidentKeyRequirement::Required,
169175
extensions: AuthenticationExtensionsClientInputs {
170176
cred_props: Some(true),
177+
cred_blob: matches.opt_present("cred_blob").then(|| {
178+
AuthenticatorExtensionsCredBlob::AsBytes("My short credBlob".as_bytes().to_vec())
179+
}),
171180
..Default::default()
172181
},
173182
pin: None,
@@ -214,10 +223,8 @@ fn main() {
214223
"timeout in seconds",
215224
"SEC",
216225
);
217-
opts.optflag(
218-
"s",
219-
"skip_reg",
220-
"Skip registration");
226+
opts.optflag("s", "skip_reg", "Skip registration");
227+
opts.optflag("b", "cred_blob", "With credBlob");
221228

222229
opts.optflag("h", "help", "print this help menu");
223230
let matches = match opts.parse(&args[1..]) {
@@ -247,7 +254,7 @@ fn main() {
247254

248255
if !matches.opt_present("skip_reg") {
249256
for username in &["A. User", "A. Nother", "Dr. Who"] {
250-
register_user(&mut manager, username, timeout_ms)
257+
register_user(&mut manager, username, timeout_ms, &matches)
251258
}
252259
}
253260

@@ -337,7 +344,12 @@ fn main() {
337344
allow_list,
338345
user_verification_req: UserVerificationRequirement::Required,
339346
user_presence_req: true,
340-
extensions: Default::default(),
347+
extensions: AuthenticationExtensionsClientInputs {
348+
cred_blob: matches
349+
.opt_present("cred_blob")
350+
.then_some(AuthenticatorExtensionsCredBlob::AsBool(true)),
351+
..Default::default()
352+
},
341353
pin: None,
342354
use_ctap1_fallback: false,
343355
};
@@ -364,7 +376,13 @@ fn main() {
364376
println!("Found credentials:");
365377
println!(
366378
"{:?}",
367-
assertion_object.assertion.user.clone().unwrap().name.unwrap() // Unwrapping here, as these shouldn't fail
379+
assertion_object
380+
.assertion
381+
.user
382+
.clone()
383+
.unwrap()
384+
.name
385+
.unwrap() // Unwrapping here, as these shouldn't fail
368386
);
369387
println!("-----------------------------------------------------------------");
370388
println!("Done.");

src/ctap2/attestation.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use super::server::AuthenticatorExtensionsCredBlob;
12
use super::utils::{from_slice_stream, read_be_u16, read_be_u32, read_byte};
23
use crate::crypto::{COSEAlgorithm, CryptoError, SharedSecret};
34
use crate::ctap2::server::{CredentialProtectionPolicy, HMACGetSecretOutput, RpIdHash};
@@ -119,11 +120,16 @@ pub struct Extension {
119120
pub hmac_secret: Option<HmacSecretResponse>,
120121
#[serde(rename = "minPinLength", skip_serializing_if = "Option::is_none")]
121122
pub min_pin_length: Option<u64>,
123+
#[serde(rename = "credBlob", skip_serializing_if = "Option::is_none")]
124+
pub cred_blob: Option<AuthenticatorExtensionsCredBlob>,
122125
}
123126

124127
impl Extension {
125128
pub fn has_some(&self) -> bool {
126-
self.min_pin_length.is_some() || self.hmac_secret.is_some() || self.cred_protect.is_some()
129+
self.min_pin_length.is_some()
130+
|| self.hmac_secret.is_some()
131+
|| self.cred_protect.is_some()
132+
|| self.cred_blob.is_some()
127133
}
128134
}
129135

src/ctap2/commands/get_assertion.rs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ use crate::ctap2::commands::make_credentials::UserVerification;
1515
use crate::ctap2::server::{
1616
AuthenticationExtensionsClientInputs, AuthenticationExtensionsClientOutputs,
1717
AuthenticationExtensionsPRFInputs, AuthenticationExtensionsPRFOutputs, AuthenticatorAttachment,
18-
PublicKeyCredentialDescriptor, PublicKeyCredentialUserEntity, RelyingParty, RpIdHash,
19-
UserVerificationRequirement,
18+
AuthenticatorExtensionsCredBlob, PublicKeyCredentialDescriptor, PublicKeyCredentialUserEntity,
19+
RelyingParty, RpIdHash, UserVerificationRequirement,
2020
};
2121
use crate::ctap2::utils::{read_be_u32, read_byte};
2222
use crate::errors::AuthenticatorError;
@@ -253,6 +253,8 @@ pub struct GetAssertionExtensions {
253253
skip_serializing_if = "HmacGetSecretOrPrf::skip_serializing"
254254
)]
255255
pub hmac_secret: Option<HmacGetSecretOrPrf>,
256+
#[serde(rename = "credBlob", skip_serializing_if = "Option::is_none")]
257+
pub cred_blob: Option<bool>,
256258
}
257259

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

278284
impl GetAssertionExtensions {
279285
fn has_content(&self) -> bool {
280-
self.hmac_secret.is_some()
286+
self.hmac_secret.is_some() || self.cred_blob.is_some()
281287
}
282288
}
283289

@@ -432,6 +438,11 @@ impl GetAssertion {
432438
}
433439
None => {}
434440
}
441+
442+
// 3. credBlob
443+
// The extension returns a flag in the authenticator data which we need to mirror as a
444+
// client output.
445+
result.extensions.cred_blob = result.assertion.auth_data.extensions.cred_blob.clone();
435446
}
436447
}
437448

@@ -1048,6 +1059,7 @@ pub mod test {
10481059
None,
10491060
),
10501061
)),
1062+
cred_blob: None,
10511063
},
10521064
options: GetAssertionOptions {
10531065
user_presence: Some(true),
@@ -1105,6 +1117,7 @@ pub mod test {
11051117
Some(2),
11061118
),
11071119
)),
1120+
cred_blob: None,
11081121
},
11091122
options: GetAssertionOptions {
11101123
user_presence: None,
@@ -1150,6 +1163,7 @@ pub mod test {
11501163
eval_by_credential: None,
11511164
},
11521165
)),
1166+
cred_blob: None,
11531167
},
11541168
options: GetAssertionOptions {
11551169
user_presence: None,
@@ -1171,6 +1185,7 @@ pub mod test {
11711185
extensions: GetAssertionExtensions {
11721186
app_id: None,
11731187
hmac_secret: Some(HmacGetSecretOrPrf::PrfUnmatched),
1188+
cred_blob: None,
11741189
},
11751190
options: GetAssertionOptions {
11761191
user_presence: None,
@@ -2828,6 +2843,7 @@ pub mod test {
28282843
cred_protect: None,
28292844
hmac_secret: hmac_secret_response,
28302845
min_pin_length: None,
2846+
cred_blob: None,
28312847
},
28322848
},
28332849
signature: vec![],

src/ctap2/commands/make_credentials.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ use crate::ctap2::attestation::{
1515
use crate::ctap2::client_data::ClientDataHash;
1616
use crate::ctap2::server::{
1717
AuthenticationExtensionsClientInputs, AuthenticationExtensionsClientOutputs,
18-
AuthenticationExtensionsPRFOutputs, AuthenticatorAttachment, CredentialProtectionPolicy,
19-
PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, PublicKeyCredentialUserEntity,
20-
RelyingParty, RpIdHash, UserVerificationRequirement,
18+
AuthenticationExtensionsPRFOutputs, AuthenticatorAttachment, AuthenticatorExtensionsCredBlob,
19+
CredentialProtectionPolicy, PublicKeyCredentialDescriptor, PublicKeyCredentialParameters,
20+
PublicKeyCredentialUserEntity, RelyingParty, RpIdHash, UserVerificationRequirement,
2121
};
2222
use crate::ctap2::utils::{read_byte, serde_parse_err};
2323
use crate::errors::AuthenticatorError;
@@ -242,6 +242,8 @@ pub struct MakeCredentialsExtensions {
242242
pub hmac_secret: Option<HmacCreateSecretOrPrf>,
243243
#[serde(rename = "minPinLength", skip_serializing_if = "Option::is_none")]
244244
pub min_pin_length: Option<bool>,
245+
#[serde(rename = "credBlob", skip_serializing_if = "Option::is_none")]
246+
pub cred_blob: Option<AuthenticatorExtensionsCredBlob>,
245247
}
246248

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

265267
impl MakeCredentialsExtensions {
266268
fn has_content(&self) -> bool {
267-
self.cred_protect.is_some() || self.hmac_secret.is_some() || self.min_pin_length.is_some()
269+
self.cred_protect.is_some()
270+
|| self.hmac_secret.is_some()
271+
|| self.min_pin_length.is_some()
272+
|| self.cred_blob.is_some()
268273
}
269274
}
270275

@@ -281,6 +286,7 @@ impl From<AuthenticationExtensionsClientInputs> for MakeCredentialsExtensions {
281286
}
282287
},
283288
min_pin_length: input.min_pin_length,
289+
cred_blob: input.cred_blob,
284290
}
285291
}
286292
}
@@ -409,6 +415,11 @@ impl MakeCredentials {
409415
}
410416
None | Some(HmacCreateSecretOrPrf::HmacCreateSecret(false)) => {}
411417
}
418+
419+
// 3. credBlob
420+
// The extension returns a flag in the authenticator data which we need to mirror as a
421+
// client output.
422+
result.extensions.cred_blob = result.att_obj.auth_data.extensions.cred_blob.clone();
412423
}
413424
}
414425

@@ -755,6 +766,7 @@ pub mod test {
755766
),
756767
hmac_secret: Some(HmacCreateSecretOrPrf::HmacCreateSecret(true)),
757768
min_pin_length: Some(true),
769+
cred_blob: None,
758770
},
759771
options: MakeCredentialsOptions {
760772
resident_key: Some(true),

src/ctap2/server.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,35 @@ impl<'de> Deserialize<'de> for CredentialProtectionPolicy {
354354
}
355355
}
356356

357+
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
358+
#[serde(untagged)]
359+
pub enum AuthenticatorExtensionsCredBlob {
360+
/// Used in GetAssertion-requests to request the stored blob,
361+
/// and in MakeCredential-responses to signify if the
362+
/// storing worked.
363+
AsBool(bool),
364+
/// Used in MakeCredential-requests to store a new credBlob,
365+
/// and in GetAssertion-responses when retrieving the
366+
/// stored blob.
367+
#[serde(serialize_with = "vec_to_bytebuf", deserialize_with = "bytebuf_to_vec")]
368+
AsBytes(Vec<u8>),
369+
}
370+
371+
fn vec_to_bytebuf<S>(data: &[u8], s: S) -> Result<S::Ok, S::Error>
372+
where
373+
S: Serializer,
374+
{
375+
ByteBuf::from(data).serialize(s)
376+
}
377+
378+
fn bytebuf_to_vec<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
379+
where
380+
D: Deserializer<'de>,
381+
{
382+
let bytes = <ByteBuf>::deserialize(deserializer)?;
383+
Ok(bytes.to_vec())
384+
}
385+
357386
#[derive(Clone, Debug, Default)]
358387
pub struct AuthenticationExtensionsClientInputs {
359388
pub app_id: Option<String>,
@@ -364,6 +393,9 @@ pub struct AuthenticationExtensionsClientInputs {
364393
pub hmac_get_secret: Option<HMACGetSecretInput>,
365394
pub min_pin_length: Option<bool>,
366395
pub prf: Option<AuthenticationExtensionsPRFInputs>,
396+
/// MakeCredential-requests use AsBytes
397+
/// GetAssertion-requests use AsBool
398+
pub cred_blob: Option<AuthenticatorExtensionsCredBlob>,
367399
}
368400

369401
#[derive(Clone, Debug, Default, Eq, PartialEq)]
@@ -495,6 +527,9 @@ pub struct AuthenticationExtensionsClientOutputs {
495527
pub hmac_create_secret: Option<bool>,
496528
pub hmac_get_secret: Option<HMACGetSecretOutput>,
497529
pub prf: Option<AuthenticationExtensionsPRFOutputs>,
530+
/// MakeCredential-responses use AsBool
531+
/// GetAssertion-responses use AsBytes
532+
pub cred_blob: Option<AuthenticatorExtensionsCredBlob>,
498533
}
499534

500535
#[derive(Clone, Debug, PartialEq, Eq)]

0 commit comments

Comments
 (0)