Skip to content

Commit

Permalink
Add largeBlobKeys-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 da44190 commit fbdb685
Show file tree
Hide file tree
Showing 16 changed files with 706 additions and 24 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,5 @@ env_logger = "^0.6"
getopts = "^0.2"
assert_matches = "1.2"
rpassword = "5.0"
flate3 = "1"
aes-gcm = "0.10"
3 changes: 3 additions & 0 deletions examples/ctap2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@ fn main() {
Ok(StatusUpdate::SelectResultNotice(_, _)) => {
panic!("Unexpected select device notice")
}
Ok(StatusUpdate::LargeBlobData(_, _)) => {
panic!("Unexpected large blob data request")
}
Err(RecvError) => {
println!("STATUS: end");
return;
Expand Down
90 changes: 83 additions & 7 deletions examples/ctap2_discoverable_creds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,29 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use aes_gcm::{
aead::{Aead, AeadCore, KeyInit, OsRng, Payload},
Aes256Gcm, Key,
};
use authenticator::{
authenticatorservice::{AuthenticatorService, RegisterArgs, SignArgs},
crypto::COSEAlgorithm,
ctap2::server::{
AuthenticationExtensionsClientInputs, AuthenticatorExtensionsCredBlob,
PublicKeyCredentialDescriptor, PublicKeyCredentialParameters,
PublicKeyCredentialUserEntity, RelyingParty, ResidentKeyRequirement, Transport,
UserVerificationRequirement,
ctap2::{
commands::large_blobs::LargeBlobArrayElement,
server::{
AuthenticationExtensionsClientInputs, AuthenticatorExtensionsCredBlob,
PublicKeyCredentialDescriptor, PublicKeyCredentialParameters,
PublicKeyCredentialUserEntity, RelyingParty, ResidentKeyRequirement, Transport,
UserVerificationRequirement,
},
},
statecallback::StateCallback,
Pin, StatusPinUv, StatusUpdate,
};
use getopts::{Matches, Options};
use sha2::{Digest, Sha256};
use std::io::Write;
use std::sync::mpsc::{channel, RecvError};
use std::{convert::TryInto, io::Write};
use std::{env, io, thread};

fn print_usage(program: &str, opts: Options) {
Expand Down Expand Up @@ -82,6 +89,8 @@ fn register_user(
);
let chall_bytes = Sha256::digest(challenge_str.as_bytes()).into();

let has_large_blob = matches.opt_present("large_blob_key");
let name = username.to_string();
let (status_tx, status_rx) = channel::<StatusUpdate>();
thread::spawn(move || loop {
match status_rx.recv() {
Expand Down Expand Up @@ -137,6 +146,37 @@ fn register_user(
Ok(StatusUpdate::SelectResultNotice(_, _)) => {
panic!("Unexpected select result notice")
}
Ok(StatusUpdate::LargeBlobData(tx, key)) => {
if has_large_blob {
// Let origData equal the opaque large-blob data.
let orig_data = format!("This is the large blob for {name}").into_bytes();
// Let origSize be the length, in bytes, of origData.
let orig_size = orig_data.len() as u64;
// Let plaintext equal origData after compression with DEFLATE [RFC1951].
let plaintext = flate3::deflate(&orig_data);
// Let nonce be a fresh, random, 12-byte value.
let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
// Let ciphertext be the AEAD_AES_256_GCM authenticated encryption of plaintext using key, nonce, and the associated data as specified above.
let gcm_key = Key::<Aes256Gcm>::from_slice(&key);
let cipher = Aes256Gcm::new(gcm_key);
let mut payload = Payload::from(plaintext.as_ref());
// Associated data: The value 0x626c6f62 ("blob") || uint64LittleEndian(origSize).
let mut aad = b"blob".to_vec();
aad.extend_from_slice(&orig_size.to_le_bytes());
payload.aad = &aad;
let ciphertext = cipher
.encrypt(&nonce, payload)
.expect("Failed to encrypt plaintext large blob");
let elem = LargeBlobArrayElement {
ciphertext,
nonce: nonce.to_vec().try_into().unwrap(),
orig_size,
};
tx.send(elem).expect("Failed to send large blob element");
} else {
panic!("Unexpected large blob data request");
}
}
Err(RecvError) => {
println!("STATUS: end");
return;
Expand Down Expand Up @@ -177,6 +217,7 @@ fn register_user(
cred_blob: matches.opt_present("cred_blob").then(|| {
AuthenticatorExtensionsCredBlob::AsBytes("My short credBlob".as_bytes().to_vec())
}),
large_blob_key: matches.opt_present("large_blob_key").then_some(true),
..Default::default()
},
pin: None,
Expand Down Expand Up @@ -208,6 +249,30 @@ fn register_user(
}

println!("Register result: {:?}", &attestation_object);

if matches.opt_present("large_blob_key") {
println!("Adding large blob key");
}
}

fn extract_associated_large_blobs(key: Vec<u8>, array: Vec<LargeBlobArrayElement>) -> Vec<String> {
let valid_elements = array
.iter()
.filter_map(|e| {
let gcm_key = Key::<Aes256Gcm>::from_slice(&key);
let cipher = Aes256Gcm::new(gcm_key);
let mut payload = Payload::from(e.ciphertext.as_slice());
// Associated data: The value 0x626c6f62 ("blob") || uint64LittleEndian(origSize).
let mut aad = b"blob".to_vec();
aad.extend_from_slice(&e.orig_size.to_le_bytes());
payload.aad = &aad;
let plaintext = cipher.decrypt(e.nonce.as_slice().into(), payload).ok();
plaintext
})
.map(|d| flate3::inflate(&d))
.map(|d| String::from_utf8_lossy(&d).to_string())
.collect();
valid_elements
}

fn main() {
Expand All @@ -225,7 +290,7 @@ fn main() {
);
opts.optflag("s", "skip_reg", "Skip registration");
opts.optflag("b", "cred_blob", "With credBlob");

opts.optflag("l", "large_blob_key", "With largeBlobKey-extension");
opts.optflag("h", "help", "print this help menu");
let matches = match opts.parse(&args[1..]) {
Ok(m) => m,
Expand Down Expand Up @@ -329,6 +394,9 @@ fn main() {
let idx = ask_user_choice(&users);
index_sender.send(idx).expect("Failed to send choice");
}
Ok(StatusUpdate::LargeBlobData(..)) => {
panic!("Unexpected large blob data request")
}
Err(RecvError) => {
println!("STATUS: end");
return;
Expand All @@ -348,6 +416,7 @@ fn main() {
cred_blob: matches
.opt_present("cred_blob")
.then_some(AuthenticatorExtensionsCredBlob::AsBool(true)),
large_blob_key: matches.opt_present("large_blob_key").then_some(true),
..Default::default()
},
pin: None,
Expand Down Expand Up @@ -385,6 +454,13 @@ fn main() {
.unwrap() // Unwrapping here, as these shouldn't fail
);
println!("-----------------------------------------------------------------");
if matches.opt_present("large_blob_key") {
let large_blobs = extract_associated_large_blobs(
assertion_object.large_blob_key.unwrap(),
assertion_object.large_blob_array.unwrap(),
);
println!("Associated large blobs: {large_blobs:?}");
}
println!("Done.");
break;
}
Expand Down
3 changes: 3 additions & 0 deletions examples/interactive_management.rs
Original file line number Diff line number Diff line change
Expand Up @@ -730,6 +730,9 @@ fn interactive_status_callback(status_rx: Receiver<StatusUpdate>) {
Ok(StatusUpdate::SelectResultNotice(_, _)) => {
panic!("Unexpected select device notice")
}
Ok(StatusUpdate::LargeBlobData(_, _)) => {
panic!("Unexpected large blob data request")
}
Err(RecvError) => {
println!("STATUS: end");
return;
Expand Down
3 changes: 3 additions & 0 deletions examples/set_pin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ fn main() {
Ok(StatusUpdate::SelectResultNotice(_, _)) => {
panic!("Unexpected select device notice")
}
Ok(StatusUpdate::LargeBlobData(_, _)) => {
panic!("Unexpected large blob data request")
}
Err(RecvError) => {
println!("STATUS: end");
return;
Expand Down
3 changes: 3 additions & 0 deletions examples/test_exclude_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,9 @@ fn main() {
Ok(StatusUpdate::SelectResultNotice(_, _)) => {
panic!("Unexpected select device notice")
}
Ok(StatusUpdate::LargeBlobData(_, _)) => {
panic!("Unexpected large blob data request")
}
Err(RecvError) => {
println!("STATUS: end");
return;
Expand Down
Loading

0 comments on commit fbdb685

Please sign in to comment.