Bug 1853711 - vendor authenticator-rs v0.4.0-alpha.22. r=keeler,supply-chain-reviewers

Differential Revision: https://phabricator.services.mozilla.com/D188766
This commit is contained in:
John Schanck 2023-09-21 16:07:45 +00:00
Родитель bff78bdbe5
Коммит cb1b6afb63
25 изменённых файлов: 755 добавлений и 457 удалений

4
Cargo.lock сгенерированный
Просмотреть файл

@ -296,9 +296,9 @@ dependencies = [
[[package]]
name = "authenticator"
version = "0.4.0-alpha.21"
version = "0.4.0-alpha.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0e925f7f169517290d746d6080fccc3d49605eb7ea8f0394fc3fbd6e2c31ab3"
checksum = "2a46aebc9de6a88556552bc40e5ffbd479705cc0ab46fee0dec476dc2886eb13"
dependencies = [
"base64 0.21.3",
"bitflags 1.3.2",

Просмотреть файл

@ -5,7 +5,7 @@ edition = "2021"
authors = ["Martin Sirringhaus", "John Schanck"]
[dependencies]
authenticator = { version = "0.4.0-alpha.21", features = ["gecko"] }
authenticator = { version = "0.4.0-alpha.22", features = ["gecko"] }
base64 = "^0.21"
log = "0.4"
moz_task = { path = "../../../xpcom/rust/moz_task" }

Просмотреть файл

@ -10,13 +10,12 @@ extern crate xpcom;
use authenticator::{
authenticatorservice::{RegisterArgs, SignArgs},
crypto::COSEKeyType,
ctap2::attestation::AttestationObject,
ctap2::commands::get_info::AuthenticatorVersion,
ctap2::server::{
AuthenticationExtensionsClientInputs, PublicKeyCredentialDescriptor,
PublicKeyCredentialParameters, RelyingParty, ResidentKeyRequirement, User,
UserVerificationRequirement,
PublicKeyCredentialParameters, PublicKeyCredentialUserEntity, RelyingParty,
ResidentKeyRequirement, UserVerificationRequirement,
},
errors::{AuthenticatorError, PinError, U2FTokenError},
statecallback::StateCallback,
@ -171,11 +170,11 @@ impl WebAuthnAttObj {
let Some(credential_data) = &self.att_obj.auth_data.credential_data else {
return Err(NS_ERROR_FAILURE);
};
// We only support encoding (some) EC2 keys in DER SPKI format.
let COSEKeyType::EC2(ref key) = credential_data.credential_public_key.key else {
return Err(NS_ERROR_NOT_AVAILABLE);
};
Ok(key.der_spki().or(Err(NS_ERROR_NOT_AVAILABLE))?.into())
Ok(credential_data
.credential_public_key
.der_spki()
.or(Err(NS_ERROR_NOT_AVAILABLE))?
.into())
}
xpcom_method!(get_public_key_algorithm => GetPublicKeyAlgorithm() -> i32);
@ -333,14 +332,12 @@ impl Controller {
.query_interface::<nsICtapSignResult>(),
),
Ok(result) => {
for assertion in result.assertions {
assertions.push(
CtapSignResult::allocate(InitCtapSignResult {
result: Ok(assertion),
})
.query_interface::<nsICtapSignResult>(),
);
}
assertions.push(
CtapSignResult::allocate(InitCtapSignResult {
result: Ok(result.assertion),
})
.query_interface::<nsICtapSignResult>(),
);
}
}
@ -440,6 +437,9 @@ fn status_callback(
Ok(StatusUpdate::InteractiveManagement(_)) => {
debug!("STATUS: interactive management");
}
Ok(StatusUpdate::SelectResultNotice(_, _)) => {
// The selection prompt will be added in Bug 1854016
}
Err(RecvError) => {
debug!("STATUS: end");
return;
@ -605,7 +605,7 @@ impl AuthrsTransport {
name: None,
},
origin: origin.to_string(),
user: User {
user: PublicKeyCredentialUserEntity {
id: user_id.to_vec(),
name: Some(user_name.to_string()),
display_name: None,
@ -783,10 +783,8 @@ impl AuthrsTransport {
// In CTAP 2.0, but not CTAP 2.1, the assertion object's credential field
// "May be omitted if the allowList has exactly one credential." If we had
// a unique allowed credential, then copy its descriptor to the output.
if let Ok(Some(assertion)) =
result.as_mut().map(|result| result.assertions.first_mut())
{
assertion.credentials = uniq_allowed_cred;
if let Ok(inner) = result.as_mut() {
inner.assertion.credentials = uniq_allowed_cred;
}
}
let _ = controller.finish_sign(tid, result);

Просмотреть файл

@ -12,7 +12,7 @@ use authenticator::ctap2::{
client_data::ClientDataHash,
commands::{
client_pin::{ClientPIN, ClientPinResponse, PINSubcommand},
get_assertion::{Assertion, GetAssertion, GetAssertionResponse, GetAssertionResult},
get_assertion::{GetAssertion, GetAssertionResponse, GetAssertionResult},
get_info::{AuthenticatorInfo, AuthenticatorOptions, AuthenticatorVersion},
get_version::{GetVersion, U2FInfo},
make_credentials::{MakeCredentials, MakeCredentialsResult},
@ -21,7 +21,10 @@ use authenticator::ctap2::{
RequestCtap1, RequestCtap2, StatusCode,
},
preflight::CheckKeyHandle,
server::{PublicKeyCredentialDescriptor, RelyingParty, RelyingPartyWrapper, User},
server::{
PublicKeyCredentialDescriptor, PublicKeyCredentialUserEntity, RelyingParty,
RelyingPartyWrapper,
},
};
use authenticator::errors::{AuthenticatorError, CommandError, HIDError, U2FTokenError};
use authenticator::{ctap2, statecallback::StateCallback};
@ -75,7 +78,7 @@ impl TestTokenCredential {
extensions: Extension::default(),
};
let user = Some(User {
let user = Some(PublicKeyCredentialUserEntity {
id: self.user_handle.clone(),
..Default::default()
});
@ -311,7 +314,7 @@ impl VirtualFidoDevice for TestToken {
}
}
fn get_assertion(&self, req: &GetAssertion) -> Result<GetAssertionResult, HIDError> {
fn get_assertion(&self, req: &GetAssertion) -> Result<Vec<GetAssertionResult>, HIDError> {
// Algorithm 6.2.2 from CTAP 2.1
// https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#sctn-makeCred-authnr-alg
@ -368,34 +371,40 @@ impl VirtualFidoDevice for TestToken {
// 10. Extensions
// (not implemented)
let mut assertions: Vec<Assertion> = vec![];
let mut assertions: Vec<GetAssertionResult> = vec![];
if !req.allow_list.is_empty() {
// 11. Non-discoverable credential case
// return at most one assertion matching an allowed credential ID
for credential in eligible_cred_iter {
if req.allow_list.iter().any(|x| x.id == credential.id) {
let assertion = credential.assert(&req.client_data_hash, flags)?.into();
assertions.push(assertion);
assertions.push(GetAssertionResult {
assertion,
extensions: Default::default(),
});
break;
}
}
} else {
// 12. Discoverable credential case
// return any number of assertions from credentials bound to this RP ID
// TODO(Bug 1838932) Until we have conditional mediation we actually don't want to
// return a list of credentials here. The UI to select one of the results blocks
// testing.
for credential in eligible_cred_iter.filter(|x| x.is_discoverable_credential) {
let assertion = credential.assert(&req.client_data_hash, flags)?.into();
assertions.push(assertion);
break;
assertions.push(GetAssertionResult {
assertion,
extensions: Default::default(),
});
}
}
Ok(GetAssertionResult {
assertions,
extensions: Default::default(),
})
if assertions.is_empty() {
return Err(HIDError::Command(CommandError::StatusCode(
StatusCode::NoCredentials,
None,
)));
}
Ok(assertions)
}
fn get_info(&self) -> Result<AuthenticatorInfo, HIDError> {

Просмотреть файл

@ -50,6 +50,13 @@ user-id = 175410
user-login = "jschanck"
user-name = "John Schanck"
[[publisher.authenticator]]
version = "0.4.0-alpha.22"
when = "2023-09-19"
user-id = 175410
user-login = "jschanck"
user-name = "John Schanck"
[[publisher.bhttp]]
version = "0.3.1"
when = "2023-02-23"

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

2
third_party/rust/authenticator/Cargo.lock сгенерированный поставляемый
Просмотреть файл

@ -39,7 +39,7 @@ dependencies = [
[[package]]
name = "authenticator"
version = "0.4.0-alpha.21"
version = "0.4.0-alpha.22"
dependencies = [
"assert_matches",
"base64",

2
third_party/rust/authenticator/Cargo.toml поставляемый
Просмотреть файл

@ -12,7 +12,7 @@
[package]
edition = "2018"
name = "authenticator"
version = "0.4.0-alpha.21"
version = "0.4.0-alpha.22"
authors = [
"J.C. Jones <jc@mozilla.com>",
"Tim Taubert <ttaubert@mozilla.com>",

Просмотреть файл

@ -7,8 +7,9 @@ use authenticator::{
crypto::COSEAlgorithm,
ctap2::server::{
AuthenticationExtensionsClientInputs, CredentialProtectionPolicy,
PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty,
ResidentKeyRequirement, Transport, User, UserVerificationRequirement,
PublicKeyCredentialDescriptor, PublicKeyCredentialParameters,
PublicKeyCredentialUserEntity, RelyingParty, ResidentKeyRequirement, Transport,
UserVerificationRequirement,
},
statecallback::StateCallback,
Pin, StatusPinUv, StatusUpdate,
@ -132,6 +133,9 @@ fn main() {
Ok(StatusUpdate::PinUvError(e)) => {
panic!("Unexpected error: {:?}", e)
}
Ok(StatusUpdate::SelectResultNotice(_, _)) => {
panic!("Unexpected select device notice")
}
Err(RecvError) => {
println!("STATUS: end");
return;
@ -139,7 +143,7 @@ fn main() {
}
});
let user = User {
let user = PublicKeyCredentialUserEntity {
id: "user_id".as_bytes().to_vec(),
name: Some("A. User".to_string()),
display_name: None,

Просмотреть файл

@ -7,8 +7,8 @@ use authenticator::{
crypto::COSEAlgorithm,
ctap2::server::{
AuthenticationExtensionsClientInputs, PublicKeyCredentialDescriptor,
PublicKeyCredentialParameters, RelyingParty, ResidentKeyRequirement, Transport, User,
UserVerificationRequirement,
PublicKeyCredentialParameters, PublicKeyCredentialUserEntity, RelyingParty,
ResidentKeyRequirement, Transport, UserVerificationRequirement,
},
statecallback::StateCallback,
Pin, StatusPinUv, StatusUpdate,
@ -16,7 +16,8 @@ use authenticator::{
use getopts::Options;
use sha2::{Digest, Sha256};
use std::sync::mpsc::{channel, RecvError};
use std::{env, thread};
use std::{env, io, thread};
use std::io::Write;
fn print_usage(program: &str, opts: Options) {
println!("------------------------------------------------------------------------");
@ -28,6 +29,37 @@ fn print_usage(program: &str, opts: Options) {
print!("{}", opts.usage(&brief));
}
fn ask_user_choice(choices: &[PublicKeyCredentialUserEntity]) -> Option<usize> {
for (idx, op) in choices.iter().enumerate() {
println!("({idx}) \"{}\"", op.name.as_ref().unwrap());
}
println!("({}) Cancel", choices.len());
let mut input = String::new();
loop {
input.clear();
print!("Your choice: ");
io::stdout()
.lock()
.flush()
.expect("Failed to flush stdout!");
io::stdin()
.read_line(&mut input)
.expect("error: unable to read user input");
if let Ok(idx) = input.trim().parse::<usize>() {
if idx < choices.len() {
// Add a newline in case of success for better separation of in/output
println!();
return Some(idx);
} else if idx == choices.len() {
println!();
return None;
}
println!("invalid input");
}
}
}
fn register_user(manager: &mut AuthenticatorService, username: &str, timeout_ms: u64) {
println!();
println!("*********************************************************************");
@ -98,6 +130,9 @@ fn register_user(manager: &mut AuthenticatorService, username: &str, timeout_ms:
Ok(StatusUpdate::PinUvError(e)) => {
panic!("Unexpected error: {:?}", e)
}
Ok(StatusUpdate::SelectResultNotice(_, _)) => {
panic!("Unexpected select result notice")
}
Err(RecvError) => {
println!("STATUS: end");
return;
@ -105,7 +140,7 @@ fn register_user(manager: &mut AuthenticatorService, username: &str, timeout_ms:
}
});
let user = User {
let user = PublicKeyCredentialUserEntity {
id: username.as_bytes().to_vec(),
name: Some(username.to_string()),
display_name: None,
@ -181,6 +216,10 @@ fn main() {
"timeout in seconds",
"SEC",
);
opts.optflag(
"s",
"skip_reg",
"Skip registration");
opts.optflag("h", "help", "print this help menu");
let matches = match opts.parse(&args[1..]) {
@ -208,8 +247,10 @@ fn main() {
}
};
for username in &["A. User", "A. Nother", "Dr. Who"] {
register_user(&mut manager, username, timeout_ms)
if !matches.opt_present("skip_reg") {
for username in &["A. User", "A. Nother", "Dr. Who"] {
register_user(&mut manager, username, timeout_ms)
}
}
println!();
@ -278,6 +319,11 @@ fn main() {
Ok(StatusUpdate::PinUvError(e)) => {
panic!("Unexpected error: {:?}", e)
}
Ok(StatusUpdate::SelectResultNotice(index_sender, users)) => {
println!("Multiple signatures returned. Select one or cancel.");
let idx = ask_user_choice(&users);
index_sender.send(idx).expect("Failed to send choice");
}
Err(RecvError) => {
println!("STATUS: end");
return;
@ -322,11 +368,7 @@ fn main() {
println!("Found credentials:");
println!(
"{:?}",
assertion_object
.assertions
.iter()
.map(|x| x.user.clone().unwrap().name.unwrap()) // Unwrapping here, as these shouldn't fail
.collect::<Vec<_>>()
assertion_object.assertion.user.clone().unwrap().name.unwrap() // Unwrapping here, as these shouldn't fail
);
println!("-----------------------------------------------------------------");
println!("Done.");

Просмотреть файл

@ -727,6 +727,9 @@ fn interactive_status_callback(status_rx: Receiver<StatusUpdate>) {
Ok(StatusUpdate::PinUvError(e)) => {
panic!("Unexpected error: {:?}", e)
}
Ok(StatusUpdate::SelectResultNotice(_, _)) => {
panic!("Unexpected select device notice")
}
Err(RecvError) => {
println!("STATUS: end");
return;

Просмотреть файл

@ -114,6 +114,9 @@ fn main() {
Ok(StatusUpdate::PinUvError(e)) => {
panic!("Unexpected error: {:?}", e)
}
Ok(StatusUpdate::SelectResultNotice(_, _)) => {
panic!("Unexpected select device notice")
}
Err(RecvError) => {
println!("STATUS: end");
return;

Просмотреть файл

@ -7,8 +7,9 @@ use authenticator::{
crypto::COSEAlgorithm,
ctap2::commands::StatusCode,
ctap2::server::{
PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty,
ResidentKeyRequirement, Transport, User, UserVerificationRequirement,
PublicKeyCredentialDescriptor, PublicKeyCredentialParameters,
PublicKeyCredentialUserEntity, RelyingParty, ResidentKeyRequirement, Transport,
UserVerificationRequirement,
},
errors::{AuthenticatorError, CommandError, HIDError, UnsupportedOption},
statecallback::StateCallback,
@ -127,6 +128,9 @@ fn main() {
Ok(StatusUpdate::PinUvError(e)) => {
panic!("Unexpected error: {:?}", e)
}
Ok(StatusUpdate::SelectResultNotice(_, _)) => {
panic!("Unexpected select device notice")
}
Err(RecvError) => {
println!("STATUS: end");
return;
@ -134,7 +138,7 @@ fn main() {
}
});
let user = User {
let user = PublicKeyCredentialUserEntity {
id: "user_id".as_bytes().to_vec(),
name: Some("A. User".to_string()),
display_name: None,

Просмотреть файл

@ -5,8 +5,8 @@
use crate::ctap2::commands::client_pin::Pin;
use crate::ctap2::server::{
AuthenticationExtensionsClientInputs, PublicKeyCredentialDescriptor,
PublicKeyCredentialParameters, RelyingParty, ResidentKeyRequirement, User,
UserVerificationRequirement,
PublicKeyCredentialParameters, PublicKeyCredentialUserEntity, RelyingParty,
ResidentKeyRequirement, UserVerificationRequirement,
};
use crate::errors::*;
use crate::manager::Manager;
@ -18,7 +18,7 @@ pub struct RegisterArgs {
pub client_data_hash: [u8; 32],
pub relying_party: RelyingParty,
pub origin: String,
pub user: User,
pub user: PublicKeyCredentialUserEntity,
pub pub_cred_params: Vec<PublicKeyCredentialParameters>,
pub exclude_list: Vec<PublicKeyCredentialDescriptor>,
pub user_verification_req: UserVerificationRequirement,
@ -318,7 +318,8 @@ mod tests {
use super::{AuthenticatorService, AuthenticatorTransport, Pin, RegisterArgs, SignArgs};
use crate::consts::PARAMETER_SIZE;
use crate::ctap2::server::{
RelyingParty, ResidentKeyRequirement, User, UserVerificationRequirement,
PublicKeyCredentialUserEntity, RelyingParty, ResidentKeyRequirement,
UserVerificationRequirement,
};
use crate::errors::AuthenticatorError;
use crate::statecallback::StateCallback;
@ -439,7 +440,7 @@ mod tests {
name: None,
},
origin: "example.com".to_string(),
user: User {
user: PublicKeyCredentialUserEntity {
id: "user_id".as_bytes().to_vec(),
name: Some("A. User".to_string()),
display_name: None,
@ -515,7 +516,7 @@ mod tests {
name: None,
},
origin: "example.com".to_string(),
user: User {
user: PublicKeyCredentialUserEntity {
id: "user_id".as_bytes().to_vec(),
name: Some("A. User".to_string()),
display_name: None,
@ -610,7 +611,7 @@ mod tests {
name: None,
},
origin: "example.com".to_string(),
user: User {
user: PublicKeyCredentialUserEntity {
id: "user_id".as_bytes().to_vec(),
name: Some("A. User".to_string()),
display_name: None,

185
third_party/rust/authenticator/src/crypto/der.rs поставляемый Normal file
Просмотреть файл

@ -0,0 +1,185 @@
use super::CryptoError;
pub const TAG_INTEGER: u8 = 0x02;
pub const TAG_BIT_STRING: u8 = 0x03;
#[cfg(all(test, feature = "crypto_nss"))]
pub const TAG_OCTET_STRING: u8 = 0x04;
pub const TAG_NULL: u8 = 0x05;
pub const TAG_OBJECT_ID: u8 = 0x06;
pub const TAG_SEQUENCE: u8 = 0x30;
// Object identifiers in DER tag-length-value form
pub const OID_EC_PUBLIC_KEY_BYTES: &[u8] = &[
/* RFC 5480 (id-ecPublicKey) */
0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01,
];
pub const OID_SECP256R1_BYTES: &[u8] = &[
/* RFC 5480 (secp256r1) */
0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07,
];
pub const OID_ED25519_BYTES: &[u8] = &[/* RFC 8410 (id-ed25519) */ 0x2b, 0x65, 0x70];
pub const OID_RS256_BYTES: &[u8] = &[
/* RFC 4055 (sha256WithRSAEncryption) */
0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b,
];
pub type Result<T> = std::result::Result<T, CryptoError>;
const MAX_TAG_AND_LENGTH_BYTES: usize = 4;
fn write_tag_and_length(out: &mut Vec<u8>, tag: u8, len: usize) -> Result<()> {
if len > 0xFFFF {
return Err(CryptoError::LibraryFailure);
}
out.push(tag);
if len > 0xFF {
out.push(0x82);
out.push((len >> 8) as u8);
out.push(len as u8);
} else if len > 0x7F {
out.push(0x81);
out.push(len as u8);
} else {
out.push(len as u8);
}
Ok(())
}
pub fn integer(val: &[u8]) -> Result<Vec<u8>> {
if val.is_empty() {
return Err(CryptoError::MalformedInput);
}
// trim leading zeros, leaving a single zero if the input is the zero vector.
let mut val = val;
while val.len() > 1 && val[0] == 0 {
val = &val[1..];
}
let mut out = Vec::with_capacity(MAX_TAG_AND_LENGTH_BYTES + 1 + val.len());
if val[0] & 0x80 != 0 {
// needs zero prefix
write_tag_and_length(&mut out, TAG_INTEGER, 1 + val.len())?;
out.push(0x00);
out.extend_from_slice(val);
} else {
write_tag_and_length(&mut out, TAG_INTEGER, val.len())?;
out.extend_from_slice(val);
}
Ok(out)
}
pub fn bit_string(val: &[u8]) -> Result<Vec<u8>> {
let mut out = Vec::with_capacity(MAX_TAG_AND_LENGTH_BYTES + 1 + val.len());
write_tag_and_length(&mut out, TAG_BIT_STRING, 1 + val.len())?;
out.push(0x00); // trailing bits aren't supported
out.extend_from_slice(val);
Ok(out)
}
pub fn null() -> Result<Vec<u8>> {
let mut out = Vec::with_capacity(MAX_TAG_AND_LENGTH_BYTES);
write_tag_and_length(&mut out, TAG_NULL, 0)?;
Ok(out)
}
pub fn object_id(val: &[u8]) -> Result<Vec<u8>> {
let mut out = Vec::with_capacity(MAX_TAG_AND_LENGTH_BYTES + val.len());
write_tag_and_length(&mut out, TAG_OBJECT_ID, val.len())?;
out.extend_from_slice(val);
Ok(out)
}
pub fn sequence(items: &[&[u8]]) -> Result<Vec<u8>> {
let len = items.iter().map(|i| i.len()).sum();
let mut out = Vec::with_capacity(MAX_TAG_AND_LENGTH_BYTES + len);
write_tag_and_length(&mut out, TAG_SEQUENCE, len)?;
for item in items {
out.extend_from_slice(item);
}
Ok(out)
}
#[cfg(all(test, feature = "crypto_nss"))]
pub fn octet_string(val: &[u8]) -> Result<Vec<u8>> {
let mut out = Vec::with_capacity(MAX_TAG_AND_LENGTH_BYTES + val.len());
write_tag_and_length(&mut out, TAG_OCTET_STRING, val.len())?;
out.extend_from_slice(val);
Ok(out)
}
#[cfg(all(test, feature = "crypto_nss"))]
pub fn context_specific_explicit_tag(tag: u8, content: &[u8]) -> Result<Vec<u8>> {
let mut out = Vec::with_capacity(MAX_TAG_AND_LENGTH_BYTES + content.len());
write_tag_and_length(&mut out, 0xa0 + tag, content.len())?;
out.extend_from_slice(content);
Ok(out)
}
// Given "tag || len || value || rest" where tag and len are of length one, len is in [0, 127],
// and value is of length len, returns (value, rest)
#[cfg(all(test, feature = "crypto_nss"))]
fn expect_tag_with_short_len(tag: u8, z: &[u8]) -> Result<(&[u8], &[u8])> {
if z.is_empty() {
return Err(CryptoError::MalformedInput);
}
let (h, z) = z.split_at(1);
if h[0] != tag || z.is_empty() {
return Err(CryptoError::MalformedInput);
}
let (h, z) = z.split_at(1);
if h[0] >= 0x80 || h[0] as usize > z.len() {
return Err(CryptoError::MalformedInput);
}
Ok(z.split_at(h[0] as usize))
}
// Given a DER encoded RFC 3279 Ecdsa-Sig-Value,
// Ecdsa-Sig-Value ::= SEQUENCE {
// r INTEGER,
// s INTEGER },
// with r and s < 2^256, returns a 64 byte array containing
// r and s encoded as 32 byte zero-padded big endian unsigned
// integers
#[cfg(all(test, feature = "crypto_nss"))]
pub fn read_p256_sig(z: &[u8]) -> Result<Vec<u8>> {
// Strip the tag and length.
let (z, rest) = expect_tag_with_short_len(TAG_SEQUENCE, z)?;
// The input should not have any trailing data.
if !rest.is_empty() {
return Err(CryptoError::MalformedInput);
}
let read_u256 = |z| -> Result<(&[u8], &[u8])> {
let (r, z) = expect_tag_with_short_len(TAG_INTEGER, z)?;
// We're expecting r < 2^256, so no more than 33 bytes as a signed integer.
if r.is_empty() || r.len() > 33 {
return Err(CryptoError::MalformedInput);
}
// If it is 33 bytes the leading byte must be zero.
if r.len() == 33 && r[0] != 0 {
return Err(CryptoError::MalformedInput);
}
// Ensure r is no more than 32 bytes.
if r.len() == 33 {
Ok((&r[1..], z))
} else {
Ok((r, z))
}
};
let (r, z) = read_u256(z)?;
let (s, z) = read_u256(z)?;
// We should have consumed the entire buffer
if !z.is_empty() {
return Err(CryptoError::MalformedInput);
}
// Left pad each integer with zeros to length 32 and concatenate the results
let mut out = vec![0u8; 64];
{
let (r_out, s_out) = out.split_at_mut(32);
r_out[32 - r.len()..].copy_from_slice(r);
s_out[32 - s.len()..].copy_from_slice(s);
}
Ok(out)
}

Просмотреть файл

@ -35,19 +35,9 @@ use backend::{
random_bytes, sha256,
};
pub use backend::ecdsa_p256_sha256_sign_raw;
mod der;
// Object identifiers in DER tag-length-value form
const DER_OID_EC_PUBLIC_KEY_BYTES: &[u8] = &[
0x06, 0x07,
/* {iso(1) member-body(2) us(840) ansi-x962(10045) keyType(2) ecPublicKey(1)} */
0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01,
];
const DER_OID_P256_BYTES: &[u8] = &[
0x06, 0x08,
/* {iso(1) member-body(2) us(840) ansi-x962(10045) curves(3) prime(1) prime256v1(7)} */
0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07,
];
pub use backend::ecdsa_p256_sha256_sign_raw;
pub struct PinUvAuthProtocol(Box<dyn PinProtocolImpl + Send + Sync>);
impl PinUvAuthProtocol {
@ -726,37 +716,25 @@ impl COSEEC2Key {
}
pub fn der_spki(&self) -> Result<Vec<u8>, CryptoError> {
let (curve_oid, seq_len, alg_len, spk_len) = match self.curve {
Curve::SECP256R1 => (
DER_OID_P256_BYTES,
[0x59].as_slice(),
[0x13].as_slice(),
[0x42].as_slice(),
),
x => return Err(CryptoError::UnsupportedCurve(x)),
};
if self.curve != Curve::SECP256R1 {
return Err(CryptoError::UnsupportedCurve(self.curve));
}
// [RFC 5280]
let mut spki: Vec<u8> = vec![];
// SubjectPublicKeyInfo
spki.push(0x30);
spki.extend_from_slice(seq_len);
// AlgorithmIdentifier
spki.push(0x30);
spki.extend_from_slice(alg_len);
// ObjectIdentifier
spki.extend_from_slice(DER_OID_EC_PUBLIC_KEY_BYTES);
// RFC 5480 ECParameters
spki.extend_from_slice(curve_oid);
// BIT STRING encoding uncompressed SEC1 public point
spki.push(0x03);
spki.extend_from_slice(spk_len);
spki.push(0x0); // no trailing zeros
spki.push(0x04); // SEC 1 encoded uncompressed point
spki.extend_from_slice(&self.x);
spki.extend_from_slice(&self.y);
Ok(spki)
der::sequence(&[
// algorithm: AlgorithmIdentifier
&der::sequence(&[
// algorithm
&der::object_id(der::OID_EC_PUBLIC_KEY_BYTES)?,
// parameters
&der::object_id(der::OID_SECP256R1_BYTES)?,
])?,
// subjectPublicKey
&der::bit_string(
// SEC 1 uncompressed format
&[&[0x04], self.x.as_slice(), self.y.as_slice()].concat(),
)?,
])
}
}
@ -771,6 +749,30 @@ pub struct COSEOKPKey {
pub x: Vec<u8>,
}
impl COSEOKPKey {
pub fn der_spki(&self) -> Result<Vec<u8>, CryptoError> {
if self.curve != Curve::Ed25519 {
return Err(CryptoError::UnsupportedCurve(self.curve));
}
// SubjectPublicKeyInfo
der::sequence(&[
// algorithm: AlgorithmIdentifier
&der::sequence(&[
// algorithm
&der::object_id(der::OID_ED25519_BYTES)?,
// parameters
// (absent as per RFC 8410)
])?,
// subjectPublicKey
&der::bit_string(
// RFC 8410
self.x.as_slice(),
)?,
])
}
}
/// A COSE RSA PublicKey. This is a provided credential from a registered authenticator.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct COSERSAKey {
@ -780,6 +782,26 @@ pub struct COSERSAKey {
pub e: Vec<u8>,
}
impl COSERSAKey {
pub fn der_spki(&self) -> Result<Vec<u8>, CryptoError> {
// SubjectPublicKeyInfo
der::sequence(&[
// algorithm: AlgorithmIdentifier
&der::sequence(&[
// algorithm
&der::object_id(der::OID_RS256_BYTES)?,
// parameters
&der::null()?,
])?,
// subjectPublicKey
&der::bit_string(
// RFC 4055 RSAPublicKey
&der::sequence(&[&der::integer(&self.n)?, &der::integer(&self.e)?])?,
)?,
])
}
}
// https://tools.ietf.org/html/rfc8152#section-13
#[allow(non_camel_case_types)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
@ -820,10 +842,10 @@ impl TryFrom<i64> for COSEKeyTypeId {
#[allow(non_camel_case_types)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum COSEKeyType {
/// Identifies this as an Elliptic Curve octet key pair
OKP(COSEOKPKey), // Not used here
/// Identifies this as an Elliptic Curve EC2 key
EC2(COSEEC2Key),
/// Identifies this as an Elliptic Curve octet key pair
OKP(COSEOKPKey),
/// Identifies this as an RSA key
RSA(COSERSAKey),
}
@ -854,6 +876,14 @@ impl COSEKey {
};
Ok((private, public))
}
pub fn der_spki(&self) -> Result<Vec<u8>, CryptoError> {
match &self.key {
COSEKeyType::EC2(ec2_key) => ec2_key.der_spki(),
COSEKeyType::OKP(okp_key) => okp_key.der_spki(),
COSEKeyType::RSA(rsa_key) => rsa_key.der_spki(),
}
}
}
impl<'de> Deserialize<'de> for COSEKey {
@ -993,7 +1023,7 @@ impl Serialize for COSEKey {
S: Serializer,
{
let map_len = match &self.key {
COSEKeyType::OKP(_) => 3,
COSEKeyType::OKP(_) => 4,
COSEKeyType::EC2(_) => 5,
COSEKeyType::RSA(_) => 4,
};
@ -1127,7 +1157,7 @@ mod test {
Curve, PinProtocolImpl, PinUvAuth1, PinUvAuth2, PinUvAuthProtocol, PublicInputs,
SharedSecret,
};
use crate::crypto::{COSEEC2Key, COSEKeyType, COSERSAKey};
use crate::crypto::{COSEEC2Key, COSEKeyType, COSEOKPKey, COSERSAKey};
use crate::ctap2::attestation::AAGuid;
use crate::ctap2::commands::client_pin::Pin;
use crate::ctap2::commands::get_info::{
@ -1137,87 +1167,196 @@ mod test {
use crate::AuthenticatorInfo;
use serde_cbor::de::from_slice;
// Extracted from RFC 8410 Example 10.1
const SAMPLE_ED25519_KEY: &[u8] = &[
0x19, 0xbf, 0x44, 0x09, 0x69, 0x84, 0xcd, 0xfe, 0x85, 0x41, 0xba, 0xc1, 0x67, 0xdc, 0x3b,
0x96, 0xc8, 0x50, 0x86, 0xaa, 0x30, 0xb6, 0xb6, 0xcb, 0x0c, 0x5c, 0x38, 0xad, 0x70, 0x31,
0x66, 0xe1,
];
const SAMPLE_P256_X: &[u8] = &[
0xfc, 0x9e, 0xd3, 0x6f, 0x7c, 0x1a, 0xa9, 0x15, 0xce, 0x3e, 0xa1, 0x77, 0xf0, 0x75, 0x67,
0xf0, 0x7f, 0x16, 0xf9, 0x47, 0x9d, 0x95, 0xad, 0x8e, 0xd4, 0x97, 0x1d, 0x33, 0x05, 0xe3,
0x1a, 0x80,
];
const SAMPLE_P256_Y: &[u8] = &[
0x50, 0xb7, 0x33, 0xaf, 0x8c, 0x0b, 0x0e, 0xe1, 0xda, 0x8d, 0xe0, 0xac, 0xf9, 0xd8, 0xe1,
0x32, 0x82, 0xf0, 0x63, 0xb7, 0xb3, 0x0d, 0x73, 0xd4, 0xd3, 0x2c, 0x9a, 0xad, 0x6d, 0xfa,
0x8b, 0x27,
];
const SAMPLE_RSA_MODULUS: &[u8] = &[
0xd4, 0xd2, 0x53, 0xed, 0x7a, 0x69, 0xb1, 0x84, 0xc9, 0xfb, 0x70, 0x30, 0x0c, 0x51, 0xb1,
0x8f, 0x89, 0x6c, 0xb1, 0x31, 0x6d, 0x87, 0xbe, 0xe1, 0xc7, 0xf7, 0xb0, 0x4f, 0xe7, 0x27,
0xa7, 0xb7, 0x7c, 0x55, 0x20, 0x37, 0xa8, 0xac, 0x40, 0xf4, 0xbc, 0x59, 0xc4, 0x92, 0x8f,
0x13, 0x5b, 0x5e, 0xa7, 0x18, 0x05, 0xcc, 0xd7, 0x9c, 0xfb, 0x88, 0x6c, 0xf1, 0xbc, 0x6b,
0x1b, 0x8d, 0xb7, 0x8d, 0x2d, 0xaa, 0xcb, 0xee, 0xdb, 0xab, 0x49, 0x36, 0x77, 0xe5, 0xd1,
0x84, 0xa1, 0x40, 0x3f, 0xf6, 0xf7, 0x98, 0x6c, 0xaa, 0x24, 0x48, 0x30, 0x44, 0xdc, 0x68,
0xbd, 0x9e, 0x74, 0x37, 0xaf, 0x27, 0x12, 0x90, 0x74, 0x0d, 0x9e, 0x3c, 0xa5, 0x3a, 0x1d,
0xb8, 0x54, 0x92, 0xd4, 0x6d, 0x1f, 0xf9, 0x39, 0xb8, 0x1d, 0x8a, 0x5e, 0xbe, 0x12, 0xbd,
0xe2, 0x9c, 0xf2, 0x5a, 0x48, 0x5d, 0x71, 0x2c, 0x71, 0x72, 0x6d, 0xd2, 0xcb, 0x37, 0xb1,
0xe6, 0x2f, 0x76, 0x43, 0xda, 0xca, 0x44, 0x30, 0x7b, 0x28, 0xe7, 0xe4, 0xec, 0xa9, 0xc9,
0x1a, 0x5f, 0xe5, 0x51, 0x03, 0x25, 0x60, 0x7c, 0x5a, 0x69, 0x12, 0x4d, 0x50, 0xfd, 0xb2,
0xb8, 0x6e, 0x13, 0xb2, 0x92, 0xda, 0x0e, 0x31, 0xc9, 0xf1, 0x9c, 0xde, 0x17, 0x63, 0xe4,
0xcb, 0xac, 0xd5, 0xee, 0x84, 0x06, 0xde, 0x67, 0x2d, 0xb8, 0xd2, 0xe1, 0x4b, 0xbb, 0x49,
0xea, 0x45, 0xd4, 0xa1, 0x7f, 0x46, 0xf2, 0xd6, 0x0c, 0x05, 0x9d, 0x1d, 0x1a, 0x99, 0x41,
0x20, 0x5e, 0x1a, 0xa4, 0xcc, 0x21, 0x44, 0x58, 0x8b, 0xcd, 0x98, 0xe4, 0x3d, 0x53, 0x20,
0xfc, 0xfc, 0x7b, 0x9f, 0x43, 0x35, 0xfb, 0x38, 0x37, 0x23, 0xd0, 0x76, 0xe3, 0x3d, 0x4f,
0x89, 0x9b, 0x89, 0x32, 0x81, 0x89, 0xed, 0x58, 0xc0, 0x80, 0x18, 0x83, 0x5b, 0xaf, 0x5a,
0xa5,
];
#[test]
fn test_serialize_rsa_key() {
let data: [u8; 272] = [
0xa4, 0x01, 0x03, 0x03, 0x39, 0x01, 0x00, 0x20, 0x59, 0x01, 0x00, 0xd4, 0xd2, 0x53,
0xed, 0x7a, 0x69, 0xb1, 0x84, 0xc9, 0xfb, 0x70, 0x30, 0x0c, 0x51, 0xb1, 0x8f, 0x89,
0x6c, 0xb1, 0x31, 0x6d, 0x87, 0xbe, 0xe1, 0xc7, 0xf7, 0xb0, 0x4f, 0xe7, 0x27, 0xa7,
0xb7, 0x7c, 0x55, 0x20, 0x37, 0xa8, 0xac, 0x40, 0xf4, 0xbc, 0x59, 0xc4, 0x92, 0x8f,
0x13, 0x5b, 0x5e, 0xa7, 0x18, 0x05, 0xcc, 0xd7, 0x9c, 0xfb, 0x88, 0x6c, 0xf1, 0xbc,
0x6b, 0x1b, 0x8d, 0xb7, 0x8d, 0x2d, 0xaa, 0xcb, 0xee, 0xdb, 0xab, 0x49, 0x36, 0x77,
0xe5, 0xd1, 0x84, 0xa1, 0x40, 0x3f, 0xf6, 0xf7, 0x98, 0x6c, 0xaa, 0x24, 0x48, 0x30,
0x44, 0xdc, 0x68, 0xbd, 0x9e, 0x74, 0x37, 0xaf, 0x27, 0x12, 0x90, 0x74, 0x0d, 0x9e,
0x3c, 0xa5, 0x3a, 0x1d, 0xb8, 0x54, 0x92, 0xd4, 0x6d, 0x1f, 0xf9, 0x39, 0xb8, 0x1d,
0x8a, 0x5e, 0xbe, 0x12, 0xbd, 0xe2, 0x9c, 0xf2, 0x5a, 0x48, 0x5d, 0x71, 0x2c, 0x71,
0x72, 0x6d, 0xd2, 0xcb, 0x37, 0xb1, 0xe6, 0x2f, 0x76, 0x43, 0xda, 0xca, 0x44, 0x30,
0x7b, 0x28, 0xe7, 0xe4, 0xec, 0xa9, 0xc9, 0x1a, 0x5f, 0xe5, 0x51, 0x03, 0x25, 0x60,
0x7c, 0x5a, 0x69, 0x12, 0x4d, 0x50, 0xfd, 0xb2, 0xb8, 0x6e, 0x13, 0xb2, 0x92, 0xda,
0x0e, 0x31, 0xc9, 0xf1, 0x9c, 0xde, 0x17, 0x63, 0xe4, 0xcb, 0xac, 0xd5, 0xee, 0x84,
0x06, 0xde, 0x67, 0x2d, 0xb8, 0xd2, 0xe1, 0x4b, 0xbb, 0x49, 0xea, 0x45, 0xd4, 0xa1,
0x7f, 0x46, 0xf2, 0xd6, 0x0c, 0x05, 0x9d, 0x1d, 0x1a, 0x99, 0x41, 0x20, 0x5e, 0x1a,
0xa4, 0xcc, 0x21, 0x44, 0x58, 0x8b, 0xcd, 0x98, 0xe4, 0x3d, 0x53, 0x20, 0xfc, 0xfc,
0x7b, 0x9f, 0x43, 0x35, 0xfb, 0x38, 0x37, 0x23, 0xd0, 0x76, 0xe3, 0x3d, 0x4f, 0x89,
0x9b, 0x89, 0x32, 0x81, 0x89, 0xed, 0x58, 0xc0, 0x80, 0x18, 0x83, 0x5b, 0xaf, 0x5a,
0xa5, 0x21, 0x43, 0x01, 0x00, 0x01,
fn test_rsa_key_to_der_spki() {
// $ ascii2der | xxd -i
// SEQUENCE {
// SEQUENCE {
// # sha256WithRSAEncryption
// OBJECT_IDENTIFIER { 1.2.840.113549.1.1.11 }
// NULL {}
// }
// BIT_STRING {
// `00`
// SEQUENCE {
// INTEGER { `00d4d253ed7a69b184c9fb70300c51b18f896cb1316d87bee1c7f7b04fe727a7b77c552037a8ac40f4bc59c4928f135b5ea71805ccd79cfb886cf1bc6b1b8db78d2daacbeedbab493677e5d184a1403ff6f7986caa24483044dc68bd9e7437af271290740d9e3ca53a1db85492d46d1ff939b81d8a5ebe12bde29cf25a485d712c71726dd2cb37b1e62f7643daca44307b28e7e4eca9c91a5fe5510325607c5a69124d50fdb2b86e13b292da0e31c9f19cde1763e4cbacd5ee8406de672db8d2e14bbb49ea45d4a17f46f2d60c059d1d1a9941205e1aa4cc2144588bcd98e43d5320fcfc7b9f4335fb383723d076e33d4f899b89328189ed58c08018835baf5aa5` }
// INTEGER { 65537 }
// }
// }
// }
let expected: &[u8] = &[
0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a,
0x02, 0x82, 0x01, 0x01, 0x00, 0xd4, 0xd2, 0x53, 0xed, 0x7a, 0x69, 0xb1, 0x84, 0xc9,
0xfb, 0x70, 0x30, 0x0c, 0x51, 0xb1, 0x8f, 0x89, 0x6c, 0xb1, 0x31, 0x6d, 0x87, 0xbe,
0xe1, 0xc7, 0xf7, 0xb0, 0x4f, 0xe7, 0x27, 0xa7, 0xb7, 0x7c, 0x55, 0x20, 0x37, 0xa8,
0xac, 0x40, 0xf4, 0xbc, 0x59, 0xc4, 0x92, 0x8f, 0x13, 0x5b, 0x5e, 0xa7, 0x18, 0x05,
0xcc, 0xd7, 0x9c, 0xfb, 0x88, 0x6c, 0xf1, 0xbc, 0x6b, 0x1b, 0x8d, 0xb7, 0x8d, 0x2d,
0xaa, 0xcb, 0xee, 0xdb, 0xab, 0x49, 0x36, 0x77, 0xe5, 0xd1, 0x84, 0xa1, 0x40, 0x3f,
0xf6, 0xf7, 0x98, 0x6c, 0xaa, 0x24, 0x48, 0x30, 0x44, 0xdc, 0x68, 0xbd, 0x9e, 0x74,
0x37, 0xaf, 0x27, 0x12, 0x90, 0x74, 0x0d, 0x9e, 0x3c, 0xa5, 0x3a, 0x1d, 0xb8, 0x54,
0x92, 0xd4, 0x6d, 0x1f, 0xf9, 0x39, 0xb8, 0x1d, 0x8a, 0x5e, 0xbe, 0x12, 0xbd, 0xe2,
0x9c, 0xf2, 0x5a, 0x48, 0x5d, 0x71, 0x2c, 0x71, 0x72, 0x6d, 0xd2, 0xcb, 0x37, 0xb1,
0xe6, 0x2f, 0x76, 0x43, 0xda, 0xca, 0x44, 0x30, 0x7b, 0x28, 0xe7, 0xe4, 0xec, 0xa9,
0xc9, 0x1a, 0x5f, 0xe5, 0x51, 0x03, 0x25, 0x60, 0x7c, 0x5a, 0x69, 0x12, 0x4d, 0x50,
0xfd, 0xb2, 0xb8, 0x6e, 0x13, 0xb2, 0x92, 0xda, 0x0e, 0x31, 0xc9, 0xf1, 0x9c, 0xde,
0x17, 0x63, 0xe4, 0xcb, 0xac, 0xd5, 0xee, 0x84, 0x06, 0xde, 0x67, 0x2d, 0xb8, 0xd2,
0xe1, 0x4b, 0xbb, 0x49, 0xea, 0x45, 0xd4, 0xa1, 0x7f, 0x46, 0xf2, 0xd6, 0x0c, 0x05,
0x9d, 0x1d, 0x1a, 0x99, 0x41, 0x20, 0x5e, 0x1a, 0xa4, 0xcc, 0x21, 0x44, 0x58, 0x8b,
0xcd, 0x98, 0xe4, 0x3d, 0x53, 0x20, 0xfc, 0xfc, 0x7b, 0x9f, 0x43, 0x35, 0xfb, 0x38,
0x37, 0x23, 0xd0, 0x76, 0xe3, 0x3d, 0x4f, 0x89, 0x9b, 0x89, 0x32, 0x81, 0x89, 0xed,
0x58, 0xc0, 0x80, 0x18, 0x83, 0x5b, 0xaf, 0x5a, 0xa5, 0x02, 0x03, 0x01, 0x00, 0x01,
];
let expected: COSEKey = COSEKey {
alg: COSEAlgorithm::RS256,
key: COSEKeyType::RSA(COSERSAKey {
e: vec![1, 0, 1],
n: vec![
0xd4, 0xd2, 0x53, 0xed, 0x7a, 0x69, 0xb1, 0x84, 0xc9, 0xfb, 0x70, 0x30, 0x0c,
0x51, 0xb1, 0x8f, 0x89, 0x6c, 0xb1, 0x31, 0x6d, 0x87, 0xbe, 0xe1, 0xc7, 0xf7,
0xb0, 0x4f, 0xe7, 0x27, 0xa7, 0xb7, 0x7c, 0x55, 0x20, 0x37, 0xa8, 0xac, 0x40,
0xf4, 0xbc, 0x59, 0xc4, 0x92, 0x8f, 0x13, 0x5b, 0x5e, 0xa7, 0x18, 0x05, 0xcc,
0xd7, 0x9c, 0xfb, 0x88, 0x6c, 0xf1, 0xbc, 0x6b, 0x1b, 0x8d, 0xb7, 0x8d, 0x2d,
0xaa, 0xcb, 0xee, 0xdb, 0xab, 0x49, 0x36, 0x77, 0xe5, 0xd1, 0x84, 0xa1, 0x40,
0x3f, 0xf6, 0xf7, 0x98, 0x6c, 0xaa, 0x24, 0x48, 0x30, 0x44, 0xdc, 0x68, 0xbd,
0x9e, 0x74, 0x37, 0xaf, 0x27, 0x12, 0x90, 0x74, 0x0d, 0x9e, 0x3c, 0xa5, 0x3a,
0x1d, 0xb8, 0x54, 0x92, 0xd4, 0x6d, 0x1f, 0xf9, 0x39, 0xb8, 0x1d, 0x8a, 0x5e,
0xbe, 0x12, 0xbd, 0xe2, 0x9c, 0xf2, 0x5a, 0x48, 0x5d, 0x71, 0x2c, 0x71, 0x72,
0x6d, 0xd2, 0xcb, 0x37, 0xb1, 0xe6, 0x2f, 0x76, 0x43, 0xda, 0xca, 0x44, 0x30,
0x7b, 0x28, 0xe7, 0xe4, 0xec, 0xa9, 0xc9, 0x1a, 0x5f, 0xe5, 0x51, 0x03, 0x25,
0x60, 0x7c, 0x5a, 0x69, 0x12, 0x4d, 0x50, 0xfd, 0xb2, 0xb8, 0x6e, 0x13, 0xb2,
0x92, 0xda, 0x0e, 0x31, 0xc9, 0xf1, 0x9c, 0xde, 0x17, 0x63, 0xe4, 0xcb, 0xac,
0xd5, 0xee, 0x84, 0x06, 0xde, 0x67, 0x2d, 0xb8, 0xd2, 0xe1, 0x4b, 0xbb, 0x49,
0xea, 0x45, 0xd4, 0xa1, 0x7f, 0x46, 0xf2, 0xd6, 0x0c, 0x05, 0x9d, 0x1d, 0x1a,
0x99, 0x41, 0x20, 0x5e, 0x1a, 0xa4, 0xcc, 0x21, 0x44, 0x58, 0x8b, 0xcd, 0x98,
0xe4, 0x3d, 0x53, 0x20, 0xfc, 0xfc, 0x7b, 0x9f, 0x43, 0x35, 0xfb, 0x38, 0x37,
0x23, 0xd0, 0x76, 0xe3, 0x3d, 0x4f, 0x89, 0x9b, 0x89, 0x32, 0x81, 0x89, 0xed,
0x58, 0xc0, 0x80, 0x18, 0x83, 0x5b, 0xaf, 0x5a, 0xa5,
],
}),
let rsa_key = COSERSAKey {
e: vec![1, 0, 1],
n: SAMPLE_RSA_MODULUS.to_vec(),
};
let actual: COSEKey = from_slice(&data).unwrap();
assert_eq!(actual, expected);
assert_eq!(&data[..], &serde_cbor::to_vec(&actual).unwrap());
let cose_key: COSEKey = COSEKey {
alg: COSEAlgorithm::RS256,
key: COSEKeyType::RSA(rsa_key),
};
let actual = cose_key.der_spki().expect("Failed to serialize to SPKI");
assert_eq!(expected, &actual);
}
#[test]
fn test_serialize_ec2_key() {
let x = [
0xfc, 0x9e, 0xd3, 0x6f, 0x7c, 0x1a, 0xa9, 0x15, 0xce, 0x3e, 0xa1, 0x77, 0xf0, 0x75,
0x67, 0xf0, 0x7f, 0x16, 0xf9, 0x47, 0x9d, 0x95, 0xad, 0x8e, 0xd4, 0x97, 0x1d, 0x33,
0x05, 0xe3, 0x1a, 0x80,
];
let y = [
0x50, 0xb7, 0x33, 0xaf, 0x8c, 0x0b, 0x0e, 0xe1, 0xda, 0x8d, 0xe0, 0xac, 0xf9, 0xd8,
0xe1, 0x32, 0x82, 0xf0, 0x63, 0xb7, 0xb3, 0x0d, 0x73, 0xd4, 0xd3, 0x2c, 0x9a, 0xad,
0x6d, 0xfa, 0x8b, 0x27,
];
let serialized_key = [
0x04, 0xfc, 0x9e, 0xd3, 0x6f, 0x7c, 0x1a, 0xa9, 0x15, 0xce, 0x3e, 0xa1, 0x77, 0xf0,
0x75, 0x67, 0xf0, 0x7f, 0x16, 0xf9, 0x47, 0x9d, 0x95, 0xad, 0x8e, 0xd4, 0x97, 0x1d,
0x33, 0x05, 0xe3, 0x1a, 0x80, 0x50, 0xb7, 0x33, 0xaf, 0x8c, 0x0b, 0x0e, 0xe1, 0xda,
0x8d, 0xe0, 0xac, 0xf9, 0xd8, 0xe1, 0x32, 0x82, 0xf0, 0x63, 0xb7, 0xb3, 0x0d, 0x73,
0xd4, 0xd3, 0x2c, 0x9a, 0xad, 0x6d, 0xfa, 0x8b, 0x27,
];
fn test_rsa_key_to_cbor() {
let key = COSERSAKey {
e: vec![1, 0, 1],
n: SAMPLE_RSA_MODULUS.to_vec(),
};
let cose_key: COSEKey = COSEKey {
alg: COSEAlgorithm::RS256,
key: COSEKeyType::RSA(key),
};
let cose_key_cbor = serde_cbor::to_vec(&cose_key).expect("Failed to serialize key");
let actual = serde_cbor::from_slice(&cose_key_cbor).expect("Failed to deserialize key");
assert_eq!(cose_key, actual);
}
let ec2_key = COSEEC2Key::from_sec1_uncompressed(Curve::SECP256R1, &serialized_key)
.expect("Failed to decode SEC 1 key");
assert_eq!(ec2_key.x, x);
assert_eq!(ec2_key.y, y);
#[test]
fn test_ec2_key_to_der_spki() {
// $ ascii2der | xxd -i
// SEQUENCE {
// SEQUENCE {
// # ecPublicKey
// OBJECT_IDENTIFIER { 1.2.840.10045.2.1 }
// # secp256r1
// OBJECT_IDENTIFIER { 1.2.840.10045.3.1.7 }
// }
// BIT_STRING { `00` `04fc9ed36f7c1aa915ce3ea177f07567f07f16f9479d95ad8ed4971d3305e31a8050b733af8c0b0ee1da8de0acf9d8e13282f063b7b30d73d4d32c9aad6dfa8b27` }
// }
let expected = [
0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06,
0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, 0x04, 0xfc,
0x9e, 0xd3, 0x6f, 0x7c, 0x1a, 0xa9, 0x15, 0xce, 0x3e, 0xa1, 0x77, 0xf0, 0x75, 0x67,
0xf0, 0x7f, 0x16, 0xf9, 0x47, 0x9d, 0x95, 0xad, 0x8e, 0xd4, 0x97, 0x1d, 0x33, 0x05,
0xe3, 0x1a, 0x80, 0x50, 0xb7, 0x33, 0xaf, 0x8c, 0x0b, 0x0e, 0xe1, 0xda, 0x8d, 0xe0,
0xac, 0xf9, 0xd8, 0xe1, 0x32, 0x82, 0xf0, 0x63, 0xb7, 0xb3, 0x0d, 0x73, 0xd4, 0xd3,
0x2c, 0x9a, 0xad, 0x6d, 0xfa, 0x8b, 0x27,
];
let ec2_key = COSEEC2Key {
curve: Curve::SECP256R1,
x: SAMPLE_P256_X.to_vec(),
y: SAMPLE_P256_Y.to_vec(),
};
let cose_key = COSEKey {
alg: COSEAlgorithm::EDDSA,
key: COSEKeyType::EC2(ec2_key),
};
let actual = cose_key.der_spki().expect("Failed to serialize key");
assert_eq!(actual, expected);
}
#[test]
fn test_ec2_key_to_cbor() {
let ec2_key = COSEEC2Key {
curve: Curve::SECP256R1,
x: SAMPLE_P256_X.to_vec(),
y: SAMPLE_P256_Y.to_vec(),
};
let cose_key = COSEKey {
alg: COSEAlgorithm::EDDSA,
key: COSEKeyType::EC2(ec2_key),
};
let cose_key_cbor = serde_cbor::to_vec(&cose_key).expect("Failed to serialize key");
let actual = serde_cbor::from_slice(&cose_key_cbor).expect("Failed to deserialize key");
assert_eq!(cose_key, actual);
}
#[test]
fn test_okp_key_to_der_spki() {
// RFC 8410 Example 10.1
let expected = [
0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x03, 0x21, 0x00, 0x19, 0xbf,
0x44, 0x09, 0x69, 0x84, 0xcd, 0xfe, 0x85, 0x41, 0xba, 0xc1, 0x67, 0xdc, 0x3b, 0x96,
0xc8, 0x50, 0x86, 0xaa, 0x30, 0xb6, 0xb6, 0xcb, 0x0c, 0x5c, 0x38, 0xad, 0x70, 0x31,
0x66, 0xe1,
];
let okp_key = COSEOKPKey {
curve: Curve::Ed25519,
x: SAMPLE_ED25519_KEY.to_vec(),
};
let cose_key = COSEKey {
alg: COSEAlgorithm::EDDSA,
key: COSEKeyType::OKP(okp_key),
};
let actual = cose_key.der_spki().expect("Failed to serialize key");
assert_eq!(actual, expected);
}
#[test]
fn test_okp_key_to_cbor() {
let okp_key = COSEOKPKey {
curve: Curve::Ed25519,
x: SAMPLE_ED25519_KEY.to_vec(),
};
let cose_key = COSEKey {
alg: COSEAlgorithm::EDDSA,
key: COSEKeyType::OKP(okp_key),
};
let cose_key_cbor = serde_cbor::to_vec(&cose_key).expect("Failed to serialize key");
let actual = serde_cbor::from_slice(&cose_key_cbor).expect("Failed to deserialize key");
assert_eq!(cose_key, actual);
}
#[test]

Просмотреть файл

@ -1,4 +1,4 @@
use super::{CryptoError, DER_OID_P256_BYTES};
use super::CryptoError;
use nss_gk_api::p11::{
PK11Origin, PK11_CreateContextBySymKey, PK11_Decrypt, PK11_DigestFinal, PK11_DigestOp,
PK11_Encrypt, PK11_ExportDERPrivateKeyInfo, PK11_GenerateKeyPairWithOpFlags,
@ -17,11 +17,7 @@ use std::convert::TryFrom;
use std::os::raw::{c_int, c_uint};
use std::ptr;
const DER_TAG_INTEGER: u8 = 0x02;
const DER_TAG_SEQUENCE: u8 = 0x30;
#[cfg(test)]
use super::DER_OID_EC_PUBLIC_KEY_BYTES;
use super::der;
#[cfg(test)]
use nss_gk_api::p11::PK11_VerifyWithMechanism;
@ -34,118 +30,6 @@ impl From<nss_gk_api::Error> for CryptoError {
pub type Result<T> = std::result::Result<T, CryptoError>;
// DER encode a pair of 32 byte unsigned integers as an RFC 3279 Ecdsa-Sig-Value.
// Ecdsa-Sig-Value ::= SEQUENCE {
// r INTEGER,
// s INTEGER }.
fn encode_der_p256_sig(r: &[u8], s: &[u8]) -> Result<Vec<u8>> {
if r.len() != 32 || s.len() != 32 {
return Err(CryptoError::MalformedInput);
}
// Each of the inputs is no more than 32 bytes as an unsigned integer. So each is no more than
// 33 bytes as a signed integer and no more than 35 bytes with tag and length. The surrounding
// tag and length for the SEQUENCE has length 2, so the output is no more than 72 bytes.
let mut out = Vec::with_capacity(72);
out.push(DER_TAG_SEQUENCE);
out.push(0xaa); // placeholder for final length
let encode_u256 = |out: &mut Vec<u8>, r: &[u8]| {
// trim leading zeros, leaving a single zero if the input is the zero vector.
let mut r = r;
while r.len() > 1 && r[0] == 0 {
r = &r[1..];
}
out.push(DER_TAG_INTEGER);
if r[0] & 0x80 != 0 {
// Pad with a zero byte to avoid r being interpreted as a negative value.
out.push((r.len() + 1) as u8);
out.push(0x00);
} else {
out.push(r.len() as u8);
}
out.extend_from_slice(r);
};
encode_u256(&mut out, r);
encode_u256(&mut out, s);
// Write the length of the sequence
out[1] = (out.len() - 2) as u8;
Ok(out)
}
// Given "tag || len || value || rest" where tag and len are of length one, len is in [0, 127],
// and value is of length len, returns (value, rest)
#[cfg(test)]
fn der_expect_tag_with_short_len(tag: u8, z: &[u8]) -> Result<(&[u8], &[u8])> {
if z.is_empty() {
return Err(CryptoError::MalformedInput);
}
let (h, z) = z.split_at(1);
if h[0] != tag || z.is_empty() {
return Err(CryptoError::MalformedInput);
}
let (h, z) = z.split_at(1);
if h[0] >= 0x80 || h[0] as usize > z.len() {
return Err(CryptoError::MalformedInput);
}
Ok(z.split_at(h[0] as usize))
}
// Given a DER encoded RFC 3279 Ecdsa-Sig-Value,
// Ecdsa-Sig-Value ::= SEQUENCE {
// r INTEGER,
// s INTEGER },
// with r and s < 2^256, returns a 64 byte array containing
// r and s encoded as 32 byte zero-padded big endian unsigned
// integers
#[cfg(test)]
fn decode_der_p256_sig(z: &[u8]) -> Result<Vec<u8>> {
// Strip the tag and length.
let (z, rest) = der_expect_tag_with_short_len(DER_TAG_SEQUENCE, z)?;
// The input should not have any trailing data.
if !rest.is_empty() {
return Err(CryptoError::MalformedInput);
}
let read_u256 = |z| -> Result<(&[u8], &[u8])> {
let (r, z) = der_expect_tag_with_short_len(DER_TAG_INTEGER, z)?;
// We're expecting r < 2^256, so no more than 33 bytes as a signed integer.
if r.is_empty() || r.len() > 33 {
return Err(CryptoError::MalformedInput);
}
// If it is 33 bytes the leading byte must be zero.
if r.len() == 33 && r[0] != 0 {
return Err(CryptoError::MalformedInput);
}
// Ensure r is no more than 32 bytes.
if r.len() == 33 {
Ok((&r[1..], z))
} else {
Ok((r, z))
}
};
let (r, z) = read_u256(z)?;
let (s, z) = read_u256(z)?;
// We should have consumed the entire buffer
if !z.is_empty() {
return Err(CryptoError::MalformedInput);
}
// Left pad each integer with zeros to length 32 and concatenate the results
let mut out = vec![0u8; 64];
{
let (r_out, s_out) = out.split_at_mut(32);
r_out[32 - r.len()..].copy_from_slice(r);
s_out[32 - s.len()..].copy_from_slice(s);
}
Ok(out)
}
fn nss_public_key_from_der_spki(spki: &[u8]) -> Result<PublicKey> {
// TODO: replace this with an nss-gk-api function
// https://github.com/mozilla/nss-gk-api/issues/7
@ -186,7 +70,8 @@ fn generate_p256_nss() -> Result<(PrivateKey, PublicKey)> {
// Hard-coding the P256 OID here is easier than extracting a group name from peer_public and
// comparing it with P256. We'll fail in `PK11_GenerateKeyPairWithOpFlags` if peer_public is on
// the wrong curve.
let mut oid = SECItemBorrowed::wrap(DER_OID_P256_BYTES);
let oid_bytes = der::object_id(der::OID_SECP256R1_BYTES)?;
let mut oid = SECItemBorrowed::wrap(&oid_bytes);
let oid_ptr: *mut SECItem = oid.as_mut();
let slot = Slot::internal()?;
@ -270,7 +155,7 @@ pub fn ecdsa_p256_sha256_sign_raw(private: &[u8], data: &[u8]) -> Result<Vec<u8>
}
let (r, s) = signature_buf.split_at(32);
encode_der_p256_sig(r, s)
der::sequence(&[&der::integer(r)?, &der::integer(s)?])
}
/// Ephemeral ECDH over P256. Takes a DER SubjectPublicKeyInfo that encodes a public key. Generates
@ -498,65 +383,63 @@ pub fn test_ecdh_p256_raw(
let peer_public = nss_public_key_from_der_spki(peer_spki)?;
/* NSS has no mechanism to import a raw elliptic curve coordinate as a private key.
* We need to encode it in a key storage format such as PKCS#8. To avoid a dependency
* on an ASN.1 encoder for this test, we'll do it manually. */
let pkcs8_private_key_info_version = &[0x02, 0x01, 0x00];
let rfc5915_ec_private_key_version = &[0x02, 0x01, 0x01];
// NSS has no mechanism to import a raw elliptic curve coordinate as a private key.
// We need to encode it in an RFC 5208 PrivateKeyInfo:
//
// PrivateKeyInfo ::= SEQUENCE {
// version Version,
// privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
// privateKey PrivateKey,
// attributes [0] IMPLICIT Attributes OPTIONAL }
//
// Version ::= INTEGER
// PrivateKeyAlgorithmIdentifier ::= AlgorithmIdentifier
// PrivateKey ::= OCTET STRING
// Attributes ::= SET OF Attribute
//
// The privateKey field will contain an RFC 5915 ECPrivateKey:
// ECPrivateKey ::= SEQUENCE {
// version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
// privateKey OCTET STRING,
// parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
// publicKey [1] BIT STRING OPTIONAL
// }
let (curve_oid, seq_len, alg_len, attr_len, ecpriv_len, param_len, spk_len) = (
DER_OID_P256_BYTES,
[0x81, 0x87].as_slice(),
[0x13].as_slice(),
[0x6d].as_slice(),
[0x6b].as_slice(),
[0x44].as_slice(),
[0x42].as_slice(),
);
let priv_len = client_private.len() as u8; // < 127
let mut pkcs8_priv: Vec<u8> = vec![];
// RFC 5208 PrivateKeyInfo
pkcs8_priv.push(0x30);
pkcs8_priv.extend_from_slice(seq_len);
// Integer (0)
pkcs8_priv.extend_from_slice(pkcs8_private_key_info_version);
// AlgorithmIdentifier
pkcs8_priv.push(0x30);
pkcs8_priv.extend_from_slice(alg_len);
// ObjectIdentifier
pkcs8_priv.extend_from_slice(DER_OID_EC_PUBLIC_KEY_BYTES);
// RFC 5480 ECParameters
pkcs8_priv.extend_from_slice(curve_oid);
// Attributes
pkcs8_priv.push(0x04);
pkcs8_priv.extend_from_slice(attr_len);
// RFC 5915 ECPrivateKey
pkcs8_priv.push(0x30);
pkcs8_priv.extend_from_slice(ecpriv_len);
pkcs8_priv.extend_from_slice(rfc5915_ec_private_key_version);
pkcs8_priv.push(0x04);
pkcs8_priv.push(priv_len);
pkcs8_priv.extend_from_slice(client_private);
pkcs8_priv.push(0xa1);
pkcs8_priv.extend_from_slice(param_len);
pkcs8_priv.push(0x03);
pkcs8_priv.extend_from_slice(spk_len);
pkcs8_priv.push(0x0);
pkcs8_priv.push(0x04); // SEC 1 encoded uncompressed point
pkcs8_priv.extend_from_slice(client_public_x);
pkcs8_priv.extend_from_slice(client_public_y);
// PrivateKeyInfo
let priv_key_info = der::sequence(&[
// version
&der::integer(&[0x00])?,
// privateKeyAlgorithm
&der::sequence(&[
&der::object_id(der::OID_EC_PUBLIC_KEY_BYTES)?,
&der::object_id(der::OID_SECP256R1_BYTES)?,
])?,
// privateKey
&der::octet_string(
// ECPrivateKey
&der::sequence(&[
// version
&der::integer(&[0x01])?,
// privateKey
&der::octet_string(client_private)?,
// publicKey
&der::context_specific_explicit_tag(
1, // publicKey
&der::bit_string(&[&[0x04], client_public_x, client_public_y].concat())?,
)?,
])?,
)?,
])?;
// Now we can import the private key.
let slot = Slot::internal()?;
let mut pkcs8_priv_item = SECItemBorrowed::wrap(&pkcs8_priv);
let pkcs8_priv_item_ptr: *mut SECItem = pkcs8_priv_item.as_mut();
let mut priv_key_info_item = SECItemBorrowed::wrap(&priv_key_info);
let priv_key_info_item_ptr: *mut SECItem = priv_key_info_item.as_mut();
let mut client_private_ptr = ptr::null_mut();
unsafe {
PK11_ImportDERPrivateKeyInfoAndReturnKey(
*slot,
pkcs8_priv_item_ptr,
priv_key_info_item_ptr,
ptr::null_mut(),
ptr::null_mut(),
PR_FALSE,
@ -581,7 +464,7 @@ pub fn test_ecdsa_p256_sha256_verify_raw(
) -> Result<()> {
nss_gk_api::init();
let signature = decode_der_p256_sig(signature)?;
let signature = der::read_p256_sig(signature)?;
let public = nss_public_key_from_der_spki(public)?;
unsafe {
PK11_VerifyWithMechanism(

Просмотреть файл

@ -2,7 +2,8 @@ use super::{Command, CommandError, PinUvAuthCommand, RequestCtap2, StatusCode};
use crate::{
crypto::{COSEKey, PinUvAuthParam, PinUvAuthToken},
ctap2::server::{
PublicKeyCredentialDescriptor, RelyingParty, RpIdHash, User, UserVerificationRequirement,
PublicKeyCredentialDescriptor, PublicKeyCredentialUserEntity, RelyingParty, RpIdHash,
UserVerificationRequirement,
},
errors::AuthenticatorError,
transport::errors::HIDError,
@ -21,7 +22,7 @@ use std::fmt;
struct CredManagementParams {
rp_id_hash: Option<RpIdHash>, // RP ID SHA-256 hash
credential_id: Option<PublicKeyCredentialDescriptor>, // Credential Identifier
user: Option<User>, // User Entity
user: Option<PublicKeyCredentialUserEntity>, // User Entity
}
impl CredManagementParams {
@ -68,7 +69,7 @@ pub(crate) enum CredManagementCommand {
EnumerateCredentialsBegin(RpIdHash),
EnumerateCredentialsGetNextCredential,
DeleteCredential(PublicKeyCredentialDescriptor),
UpdateUserInformation((PublicKeyCredentialDescriptor, User)),
UpdateUserInformation((PublicKeyCredentialDescriptor, PublicKeyCredentialUserEntity)),
}
impl CredManagementCommand {
@ -157,7 +158,7 @@ pub struct CredentialManagementResponse {
/// Total number of RPs present on the authenticator
pub total_rps: Option<u64>,
/// User Information
pub user: Option<User>,
pub user: Option<PublicKeyCredentialUserEntity>,
/// Credential ID
pub credential_id: Option<PublicKeyCredentialDescriptor>,
/// Public key of the credential.
@ -182,7 +183,7 @@ pub struct CredentialRpListEntry {
#[derive(Debug, PartialEq, Eq, Serialize)]
pub struct CredentialListEntry {
/// User Information
pub user: User,
pub user: PublicKeyCredentialUserEntity,
/// Credential ID
pub credential_id: PublicKeyCredentialDescriptor,
/// Public key of the credential.

Просмотреть файл

@ -13,7 +13,7 @@ use crate::ctap2::commands::get_next_assertion::GetNextAssertion;
use crate::ctap2::commands::make_credentials::UserVerification;
use crate::ctap2::server::{
AuthenticationExtensionsClientInputs, AuthenticationExtensionsClientOutputs,
PublicKeyCredentialDescriptor, RelyingPartyWrapper, RpIdHash, User,
PublicKeyCredentialDescriptor, PublicKeyCredentialUserEntity, RelyingPartyWrapper, RpIdHash,
UserVerificationRequirement,
};
use crate::ctap2::utils::{read_be_u32, read_byte};
@ -195,14 +195,10 @@ impl GetAssertion {
// Handle extensions whose outputs are not encoded in the authenticator data.
// 1. appId
if let Some(app_id) = &self.extensions.app_id {
result.extensions.app_id = result
.assertions
.first()
.map(|assertion| {
assertion.auth_data.rp_id_hash
== RelyingPartyWrapper::from(app_id.as_str()).hash()
})
.or(Some(false));
result.extensions.app_id = Some(
result.assertion.auth_data.rp_id_hash
== RelyingPartyWrapper::from(app_id.as_str()).hash(),
);
}
}
}
@ -307,7 +303,7 @@ impl Serialize for GetAssertion {
}
impl RequestCtap1 for GetAssertion {
type Output = GetAssertionResult;
type Output = Vec<GetAssertionResult>;
type AdditionalInfo = PublicKeyCredentialDescriptor;
fn ctap1_format(&self) -> Result<(Vec<u8>, Self::AdditionalInfo), HIDError> {
@ -358,24 +354,27 @@ impl RequestCtap1 for GetAssertion {
return Err(Retryable::Error(HIDError::ApduStatus(err)));
}
let mut output = GetAssertionResult::from_ctap1(input, &self.rp.hash(), add_info)
let mut result = GetAssertionResult::from_ctap1(input, &self.rp.hash(), add_info)
.map_err(|e| Retryable::Error(HIDError::Command(e)))?;
self.finalize_result(&mut output);
Ok(output)
self.finalize_result(&mut result);
// Although there's only one result, we return a vector for consistency with CTAP2.
Ok(vec![result])
}
fn send_to_virtual_device<Dev: VirtualFidoDevice>(
&self,
dev: &mut Dev,
) -> Result<Self::Output, HIDError> {
let mut output = dev.get_assertion(self)?;
self.finalize_result(&mut output);
Ok(output)
let mut results = dev.get_assertion(self)?;
for result in results.iter_mut() {
self.finalize_result(result);
}
Ok(results)
}
}
impl RequestCtap2 for GetAssertion {
type Output = GetAssertionResult;
type Output = Vec<GetAssertionResult>;
fn command(&self) -> Command {
Command::GetAssertion
@ -411,22 +410,27 @@ impl RequestCtap2 for GetAssertion {
let assertion: GetAssertionResponse =
from_slice(&input[1..]).map_err(CommandError::Deserializing)?;
let number_of_credentials = assertion.number_of_credentials.unwrap_or(1);
let mut assertions = Vec::with_capacity(number_of_credentials);
assertions.push(assertion.into());
let mut results = Vec::with_capacity(number_of_credentials);
results.push(GetAssertionResult {
assertion: assertion.into(),
extensions: Default::default(),
});
let msg = GetNextAssertion;
// We already have one, so skipping 0
for _ in 1..number_of_credentials {
let new_cred = dev.send_cbor(&msg)?;
assertions.push(new_cred.into());
let assertion = dev.send_cbor(&msg)?;
results.push(GetAssertionResult {
assertion: assertion.into(),
extensions: Default::default(),
});
}
let mut output = GetAssertionResult {
assertions,
extensions: Default::default(),
};
self.finalize_result(&mut output);
Ok(output)
for result in results.iter_mut() {
self.finalize_result(result);
}
Ok(results)
} else {
let data: Value = from_slice(&input[1..]).map_err(CommandError::Deserializing)?;
Err(CommandError::StatusCode(status, Some(data)).into())
@ -437,9 +441,11 @@ impl RequestCtap2 for GetAssertion {
&self,
dev: &mut Dev,
) -> Result<Self::Output, HIDError> {
let mut output = dev.get_assertion(self)?;
self.finalize_result(&mut output);
Ok(output)
let mut results = dev.get_assertion(self)?;
for result in results.iter_mut() {
self.finalize_result(result);
}
Ok(results)
}
}
@ -449,7 +455,7 @@ pub struct Assertion {
* mandatory in CTAP2.1 */
pub auth_data: AuthenticatorData,
pub signature: Vec<u8>,
pub user: Option<User>,
pub user: Option<PublicKeyCredentialUserEntity>,
}
impl From<GetAssertionResponse> for Assertion {
@ -465,7 +471,7 @@ impl From<GetAssertionResponse> for Assertion {
#[derive(Debug, PartialEq, Eq)]
pub struct GetAssertionResult {
pub assertions: Vec<Assertion>,
pub assertion: Assertion,
pub extensions: AuthenticationExtensionsClientOutputs,
}
@ -501,30 +507,17 @@ impl GetAssertionResult {
};
Ok(GetAssertionResult {
assertions: vec![assertion],
assertion,
extensions: Default::default(),
})
}
pub fn u2f_sign_data(&self) -> Vec<u8> {
if let Some(first) = self.assertions.first() {
let mut res = Vec::new();
res.push(first.auth_data.flags.bits());
res.extend(first.auth_data.counter.to_be_bytes());
res.extend(&first.signature);
res
// first.signature.clone()
} else {
Vec::new()
}
}
}
pub struct GetAssertionResponse {
pub credentials: Option<PublicKeyCredentialDescriptor>,
pub auth_data: AuthenticatorData,
pub signature: Vec<u8>,
pub user: Option<User>,
pub user: Option<PublicKeyCredentialUserEntity>,
pub number_of_credentials: Option<usize>,
}
@ -628,7 +621,8 @@ pub mod test {
do_credential_list_filtering_ctap1, do_credential_list_filtering_ctap2,
};
use crate::ctap2::server::{
PublicKeyCredentialDescriptor, RelyingParty, RelyingPartyWrapper, RpIdHash, Transport, User,
PublicKeyCredentialDescriptor, PublicKeyCredentialUserEntity, RelyingParty,
RelyingPartyWrapper, RpIdHash, Transport,
};
use crate::transport::device_selector::Device;
use crate::transport::hid::HIDDevice;
@ -778,7 +772,7 @@ pub mod test {
0x47, 0xf1, 0x8d, 0xb4, 0x74, 0xc7, 0x47, 0x90, 0xea, 0xab, 0xb1, 0x44, 0x11, 0xe7,
0xa0,
],
user: Some(User {
user: Some(PublicKeyCredentialUserEntity {
id: vec![
0x30, 0x82, 0x01, 0x93, 0x30, 0x82, 0x01, 0x38, 0xa0, 0x03, 0x02, 0x01, 0x02,
0x30, 0x82, 0x01, 0x93, 0x30, 0x82, 0x01, 0x38, 0xa0, 0x03, 0x02, 0x01, 0x02,
@ -790,10 +784,10 @@ pub mod test {
auth_data: expected_auth_data,
};
let expected = GetAssertionResult {
assertions: vec![expected_assertion],
let expected = vec![GetAssertionResult {
assertion: expected_assertion,
extensions: Default::default(),
};
}];
let response = device.send_cbor(&assertion).unwrap();
assert_eq!(response, expected);
}
@ -925,10 +919,10 @@ pub mod test {
auth_data: expected_auth_data,
};
let expected = GetAssertionResult {
assertions: vec![expected_assertion],
let expected = vec![GetAssertionResult {
assertion: expected_assertion,
extensions: Default::default(),
};
}];
assert_eq!(response, expected);
}
@ -1069,10 +1063,10 @@ pub mod test {
auth_data: expected_auth_data,
};
let expected = GetAssertionResult {
assertions: vec![expected_assertion],
let expected = vec![GetAssertionResult {
assertion: expected_assertion,
extensions: Default::default(),
};
}];
assert_eq!(response, expected);
}
@ -1337,7 +1331,7 @@ pub mod test {
let resp = GetAssertionResult::from_ctap1(&sample, &rp_hash, &add_info)
.expect("could not handle response");
assert_eq!(
resp.assertions[0].auth_data.flags,
resp.assertion.auth_data.flags,
AuthenticatorDataFlags::USER_PRESENT | AuthenticatorDataFlags::RESERVED_1
);
}

Просмотреть файл

@ -15,7 +15,8 @@ use crate::ctap2::client_data::ClientDataHash;
use crate::ctap2::server::{
AuthenticationExtensionsClientInputs, AuthenticationExtensionsClientOutputs,
CredentialProtectionPolicy, PublicKeyCredentialDescriptor, PublicKeyCredentialParameters,
RelyingParty, RelyingPartyWrapper, RpIdHash, User, UserVerificationRequirement,
PublicKeyCredentialUserEntity, RelyingParty, RelyingPartyWrapper, RpIdHash,
UserVerificationRequirement,
};
use crate::ctap2::utils::{read_byte, serde_parse_err};
use crate::errors::AuthenticatorError;
@ -260,7 +261,7 @@ pub struct MakeCredentials {
pub client_data_hash: ClientDataHash,
pub rp: RelyingPartyWrapper,
// Note(baloo): If none -> ctap1
pub user: Option<User>,
pub user: Option<PublicKeyCredentialUserEntity>,
pub pub_cred_params: Vec<PublicKeyCredentialParameters>,
pub exclude_list: Vec<PublicKeyCredentialDescriptor>,
@ -281,7 +282,7 @@ impl MakeCredentials {
pub fn new(
client_data_hash: ClientDataHash,
rp: RelyingPartyWrapper,
user: Option<User>,
user: Option<PublicKeyCredentialUserEntity>,
pub_cred_params: Vec<PublicKeyCredentialParameters>,
exclude_list: Vec<PublicKeyCredentialDescriptor>,
options: MakeCredentialsOptions,
@ -564,7 +565,7 @@ pub(crate) fn dummy_make_credentials_cmd() -> MakeCredentials {
id: String::from("make.me.blink"),
..Default::default()
}),
Some(User {
Some(PublicKeyCredentialUserEntity {
id: vec![0],
name: Some(String::from("make.me.blink")),
..Default::default()
@ -597,7 +598,8 @@ pub mod test {
use crate::ctap2::commands::{RequestCtap1, RequestCtap2};
use crate::ctap2::server::RpIdHash;
use crate::ctap2::server::{
PublicKeyCredentialParameters, RelyingParty, RelyingPartyWrapper, User,
PublicKeyCredentialParameters, PublicKeyCredentialUserEntity, RelyingParty,
RelyingPartyWrapper,
};
use crate::transport::device_selector::Device;
use crate::transport::hid::HIDDevice;
@ -620,7 +622,7 @@ pub mod test {
id: String::from("example.com"),
name: Some(String::from("Acme")),
}),
Some(User {
Some(PublicKeyCredentialUserEntity {
id: base64::engine::general_purpose::URL_SAFE
.decode("MIIBkzCCATigAwIBAjCCAZMwggE4oAMCAQIwggGTMII=")
.unwrap(),
@ -677,7 +679,7 @@ pub mod test {
id: String::from("example.com"),
name: Some(String::from("Acme")),
}),
Some(User {
Some(PublicKeyCredentialUserEntity {
id: base64::engine::general_purpose::URL_SAFE
.decode("MIIBkzCCATigAwIBAjCCAZMwggE4oAMCAQIwggGTMII=")
.unwrap(),

Просмотреть файл

@ -680,15 +680,34 @@ pub fn sign<Dev: FidoDevice>(
debug!("{get_assertion:?} using {pin_uv_auth_result:?}");
debug!("------------------------------------------------------------------");
send_status(&status, crate::StatusUpdate::PresenceRequired);
let resp = dev.send_msg_cancellable(&get_assertion, alive);
match resp {
Ok(result) => {
callback.call(Ok(result));
return true;
}
let mut results = match dev.send_msg_cancellable(&get_assertion, alive) {
Ok(results) => results,
Err(e) => {
handle_errors!(e, status, callback, pin_uv_auth_result, skip_uv);
}
};
if results.len() == 1 {
callback.call(Ok(results.swap_remove(0)));
return true;
}
let (tx, rx) = channel();
let user_entities = results
.iter()
.filter_map(|x| x.assertion.user.clone())
.collect();
send_status(
&status,
crate::StatusUpdate::SelectResultNotice(tx, user_entities),
);
match rx.recv() {
Ok(Some(index)) if index < results.len() => {
callback.call(Ok(results.swap_remove(index)));
return true;
}
_ => {
callback.call(Err(AuthenticatorError::CancelledByUser));
return true;
}
}
}
false
@ -886,8 +905,10 @@ pub(crate) fn bio_enrollment(
Some(PinUvAuthResult::SuccessGetPinToken(t))
| Some(PinUvAuthResult::SuccessGetPinUvAuthTokenUsingUvWithPermissions(t))
| Some(PinUvAuthResult::SuccessGetPinUvAuthTokenUsingPinWithPermissions(t))
if t.permissions
.contains(PinUvAuthTokenPermission::BioEnrollment) =>
if !authinfo.versions.contains(&AuthenticatorVersion::FIDO_2_1) // Only 2.1 has a permission-system
|| use_legacy_preview // Preview doesn't use permissions
|| t.permissions
.contains(PinUvAuthTokenPermission::BioEnrollment) =>
{
skip_puap = true;
cached_puat = true;
@ -1154,7 +1175,10 @@ pub(crate) fn credential_management(
Some(PinUvAuthResult::SuccessGetPinToken(t))
| Some(PinUvAuthResult::SuccessGetPinUvAuthTokenUsingUvWithPermissions(t))
| Some(PinUvAuthResult::SuccessGetPinUvAuthTokenUsingPinWithPermissions(t))
if t.permissions == PinUvAuthTokenPermission::CredentialManagement =>
if !authinfo.versions.contains(&AuthenticatorVersion::FIDO_2_1) // Only 2.1 has a permission-system
|| use_legacy_preview // Preview doesn't use permissions
|| t.permissions
.contains(PinUvAuthTokenPermission::CredentialManagement) =>
{
skip_puap = true;
cached_puat = true;
@ -1432,7 +1456,8 @@ pub(crate) fn configure_authenticator(
Some(PinUvAuthResult::SuccessGetPinToken(t))
| Some(PinUvAuthResult::SuccessGetPinUvAuthTokenUsingUvWithPermissions(t))
| Some(PinUvAuthResult::SuccessGetPinUvAuthTokenUsingPinWithPermissions(t))
if t.permissions == PinUvAuthTokenPermission::AuthenticatorConfiguration =>
if t.permissions
.contains(PinUvAuthTokenPermission::AuthenticatorConfiguration) =>
{
skip_puap = true;
cached_puat = true;

Просмотреть файл

@ -161,13 +161,12 @@ pub(crate) fn do_credential_list_filtering_ctap2<Dev: FidoDevice>(
);
silent_assert.set_pin_uv_auth_param(pin_uv_auth_token.clone())?;
match dev.send_msg(&silent_assert) {
Ok(response) => {
Ok(mut response) => {
// This chunk contains a key_handle that is already known to the device.
// Filter out all credentials the device returned. Those are valid.
let credential_ids = response
.assertions
.iter()
.filter_map(|a| a.credentials.clone())
.iter_mut()
.filter_map(|result| result.assertion.credentials.take())
.collect();
// Replace credential_id_list with the valid credentials
final_list = credential_ids;

Просмотреть файл

@ -40,12 +40,9 @@ impl RpIdHash {
}
}
// NOTE: WebAuthn requires all fields and CTAP2 does not.
#[derive(Debug, Serialize, Clone, Default, Deserialize, PartialEq, Eq)]
pub struct RelyingParty {
// TODO(baloo): spec is wrong !!!!111
// https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#commands
// in the example "A PublicKeyCredentialRpEntity DOM object defined as follows:"
// inconsistent with https://w3c.github.io/webauthn/#sctn-rp-credential-params
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
@ -94,9 +91,9 @@ impl RelyingPartyWrapper {
}
}
// TODO(baloo): should we rename this PublicKeyCredentialUserEntity ?
// NOTE: WebAuthn requires all fields and CTAP2 does not.
#[derive(Debug, Serialize, Clone, Eq, PartialEq, Deserialize, Default)]
pub struct User {
pub struct PublicKeyCredentialUserEntity {
#[serde(with = "serde_bytes")]
pub id: Vec<u8>,
pub name: Option<String>,
@ -406,13 +403,13 @@ pub struct AuthenticationExtensionsClientOutputs {
#[cfg(test)]
mod test {
use super::{
COSEAlgorithm, PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty,
Transport, User,
COSEAlgorithm, PublicKeyCredentialDescriptor, PublicKeyCredentialParameters,
PublicKeyCredentialUserEntity, RelyingParty, Transport,
};
use serde_cbor::from_slice;
fn create_user() -> User {
User {
fn create_user() -> PublicKeyCredentialUserEntity {
PublicKeyCredentialUserEntity {
id: vec![
0x30, 0x82, 0x01, 0x93, 0x30, 0x82, 0x01, 0x38, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x30,
0x82, 0x01, 0x93, 0x30, 0x82, 0x01, 0x38, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x30, 0x82,
@ -479,7 +476,7 @@ mod test {
0x69, 0x74, 0x68, // ...
];
let expected = create_user();
let actual: User = from_slice(&input).unwrap();
let actual: PublicKeyCredentialUserEntity = from_slice(&input).unwrap();
assert_eq!(expected, actual);
}
@ -519,7 +516,7 @@ mod test {
#[test]
fn serialize_user_nodisplayname() {
let user = User {
let user = PublicKeyCredentialUserEntity {
id: vec![
0x30, 0x82, 0x01, 0x93, 0x30, 0x82, 0x01, 0x38, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x30,
0x82, 0x01, 0x93, 0x30, 0x82, 0x01, 0x38, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x30, 0x82,

Просмотреть файл

@ -7,7 +7,7 @@ use crate::{
get_info::AuthenticatorInfo,
PinUvAuthResult,
},
server::{PublicKeyCredentialDescriptor, User},
server::{PublicKeyCredentialDescriptor, PublicKeyCredentialUserEntity},
},
BioEnrollmentResult, CredentialManagementResult,
};
@ -18,7 +18,7 @@ use std::sync::mpsc::Sender;
pub enum CredManagementCmd {
GetCredentials,
DeleteCredential(PublicKeyCredentialDescriptor),
UpdateUserInformation(PublicKeyCredentialDescriptor, User),
UpdateUserInformation(PublicKeyCredentialDescriptor, PublicKeyCredentialUserEntity),
}
#[derive(Debug, Deserialize, DeriveSer)]
@ -105,6 +105,8 @@ pub enum StatusUpdate {
SelectDeviceNotice,
/// Sent when a token was selected for interactive management
InteractiveManagement(InteractiveUpdate),
/// Sent when a token returns multiple results for a getAssertion request
SelectResultNotice(Sender<Option<usize>>, Vec<PublicKeyCredentialUserEntity>),
}
pub(crate) fn send_status(status: &Sender<StatusUpdate>, msg: StatusUpdate) {

Просмотреть файл

@ -345,7 +345,7 @@ where
pub trait VirtualFidoDevice: FidoDevice {
fn check_key_handle(&self, req: &CheckKeyHandle) -> Result<(), HIDError>;
fn client_pin(&self, req: &ClientPIN) -> Result<ClientPinResponse, HIDError>;
fn get_assertion(&self, req: &GetAssertion) -> Result<GetAssertionResult, HIDError>;
fn get_assertion(&self, req: &GetAssertion) -> Result<Vec<GetAssertionResult>, HIDError>;
fn get_info(&self) -> Result<AuthenticatorInfo, HIDError>;
fn get_version(&self, req: &GetVersion) -> Result<U2FInfo, HIDError>;
fn make_credentials(&self, req: &MakeCredentials) -> Result<MakeCredentialsResult, HIDError>;