Merge branch 'master' into no_wfr

This commit is contained in:
Julien Cretin
2020-04-29 15:12:39 +02:00
11 changed files with 399 additions and 79 deletions

View File

@@ -4,18 +4,16 @@
"owner": "yapf-diff",
"pattern": [
{
"regexp": "^[+-]{3}\\s*([^\\s]*)\\s*\\((original|reformatted)\\)$",
"regexp": "^---\\s*([^\\s]*)\\s*\\(original\\)$",
"file": 1
},
{
"regexp": "^@@\\s*-(\\d+),(\\d+)\\s*\\+(\\d+),(\\d+)\\s*@@$",
"line": 1,
"column": 2
"regexp": "^\\+\\+\\+\\s*([^\\s]*)\\s*\\((.*)\\)$",
"message": 2
},
{
"regexp": "^(\\s|\\+[^+]|\\-[^-]).*$",
"loop": true,
"message": 1
"regexp": "^@@\\s*-(\\d+),(\\d+)\\s*\\+(\\d+),(\\d+)\\s*@@$",
"line": 1
}
]
},

View File

@@ -40,6 +40,12 @@ jobs:
command: check
args: --target thumbv7em-none-eabi --release --features with_ctap1
- name: Check OpenSK with_ctap2_1
uses: actions-rs/cargo@v1
with:
command: check
args: --target thumbv7em-none-eabi --release --features with_ctap2_1
- name: Check OpenSK debug_ctap
uses: actions-rs/cargo@v1
with:
@@ -76,11 +82,17 @@ jobs:
command: check
args: --target thumbv7em-none-eabi --release --features debug_ctap,with_ctap1
- name: Check OpenSK debug_ctap,with_ctap1,panic_console,debug_allocations,verbose
- name: Check OpenSK debug_ctap,with_ctap2_1
uses: actions-rs/cargo@v1
with:
command: check
args: --target thumbv7em-none-eabi --release --features debug_ctap,with_ctap1,panic_console,debug_allocations,verbose
args: --target thumbv7em-none-eabi --release --features debug_ctap,with_ctap2_1
- name: Check OpenSK debug_ctap,with_ctap1,with_ctap2_1,panic_console,debug_allocations,verbose
uses: actions-rs/cargo@v1
with:
command: check
args: --target thumbv7em-none-eabi --release --features debug_ctap,with_ctap1,with_ctap2_1,panic_console,debug_allocations,verbose
- name: Check examples
uses: actions-rs/cargo@v1

View File

@@ -49,3 +49,27 @@ jobs:
command: test
args: --features std,with_ctap1
- name: Unit testing of CTAP2 (release mode + CTAP2.1)
uses: actions-rs/cargo@v1
with:
command: test
args: --release --features std,with_ctap2_1
- name: Unit testing of CTAP2 (debug mode + CTAP2.1)
uses: actions-rs/cargo@v1
with:
command: test
args: --features std,with_ctap2_1
- name: Unit testing of CTAP2 (release mode + CTAP1 + CTAP2.1)
uses: actions-rs/cargo@v1
with:
command: test
args: --release --features std,with_ctap1,with_ctap2_1
- name: Unit testing of CTAP2 (debug mode + CTAP1 + CTAP2.1)
uses: actions-rs/cargo@v1
with:
command: test
args: --features std,with_ctap1,with_ctap2_1

View File

@@ -25,6 +25,7 @@ std = ["cbor/std", "crypto/std", "crypto/derive_debug"]
ram_storage = []
verbose = ["debug_ctap"]
with_ctap1 = ["crypto/with_ctap1"]
with_ctap2_1 = []
[dev-dependencies]
elf2tab = "0.4.0"

View File

@@ -29,6 +29,10 @@ Although we tested and implemented our firmware based on the published
[CTAP2.0 specifications](https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html),
our implementation was not reviewed nor officially tested and doesn't claim to
be FIDO Certified.
We started adding features of the upcoming next version of the
[CTAP2.1 specifications](https://fidoalliance.org/specs/fido2/fido-client-to-authenticator-protocol-v2.1-rd-20191217.html).
The development is currently between 2.0 and 2.1, with updates hidden behind a feature flag.
Please add the flag `--ctap2.1` to the deploy command to include them.
### Cryptography

View File

@@ -251,6 +251,7 @@ class OpenSKInstaller:
self.tockloader_default_args = argparse.Namespace(
arch=board.arch,
board=self.args.board,
bundle_apps=False,
debug=False,
force=False,
jlink_cmd="JLinkExe",
@@ -785,6 +786,14 @@ if __name__ == "__main__":
help=("Compiles the OpenSK application without backward compatible "
"support for U2F/CTAP1 protocol."),
)
main_parser.add_argument(
"--ctap2.1",
action=RemoveConstAction,
const="with_ctap2_1",
dest="features",
help=("Compiles the OpenSK application with backward compatible "
"support for CTAP2.1 protocol."),
)
main_parser.add_argument(
"--regen-keys",
action="store_true",

View File

@@ -30,6 +30,7 @@ cargo build --manifest-path third_party/tock/tools/sha256sum/Cargo.toml
echo "Checking that CTAP2 builds properly..."
cargo check --release --target=thumbv7em-none-eabi
cargo check --release --target=thumbv7em-none-eabi --features with_ctap1
cargo check --release --target=thumbv7em-none-eabi --features with_ctap2_1
cargo check --release --target=thumbv7em-none-eabi --features debug_ctap
cargo check --release --target=thumbv7em-none-eabi --features panic_console
cargo check --release --target=thumbv7em-none-eabi --features debug_allocations
@@ -86,4 +87,16 @@ then
echo "Running unit tests on the desktop (debug mode + CTAP1)..."
cargo test --features std,with_ctap1
echo "Running unit tests on the desktop (release mode + CTAP2.1)..."
cargo test --release --features std,with_ctap2_1
echo "Running unit tests on the desktop (debug mode + CTAP2.1)..."
cargo test --features std,with_ctap2_1
echo "Running unit tests on the desktop (release mode + CTAP1 + CTAP2.1)..."
cargo test --release --features std,with_ctap1,with_ctap2_1
echo "Running unit tests on the desktop (debug mode + CTAP1 + CTAP2.1)..."
cargo test --features std,with_ctap1,with_ctap2_1
fi

View File

@@ -13,16 +13,21 @@
// limitations under the License.
use super::data_formats::{
ok_or_missing, read_array, read_byte_string, read_integer, read_map, read_text_string,
read_unsigned, ClientPinSubCommand, CoseKey, Extensions, GetAssertionOptions,
MakeCredentialOptions, PublicKeyCredentialDescriptor, PublicKeyCredentialRpEntity,
PublicKeyCredentialType, PublicKeyCredentialUserEntity,
ok_or_missing, read_array, read_byte_string, read_map, read_text_string, read_unsigned,
ClientPinSubCommand, CoseKey, Extensions, GetAssertionOptions, MakeCredentialOptions,
PublicKeyCredentialDescriptor, PublicKeyCredentialParameter, PublicKeyCredentialRpEntity,
PublicKeyCredentialUserEntity,
};
use super::status_code::Ctap2StatusCode;
use alloc::string::String;
use alloc::vec::Vec;
use core::convert::TryFrom;
// Depending on your memory, you can use Some(n) to limit request sizes in
// MakeCredential and GetAssertion. This affects allowList and excludeList.
// You might also want to set the max credential size in process_get_info then.
pub const MAX_CREDENTIAL_COUNT_IN_LIST: Option<usize> = None;
// CTAP specification (version 20190130) section 6.1
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
pub enum Command {
@@ -106,7 +111,7 @@ pub struct AuthenticatorMakeCredentialParameters {
pub client_data_hash: Vec<u8>,
pub rp: PublicKeyCredentialRpEntity,
pub user: PublicKeyCredentialUserEntity,
pub pub_key_cred_params: Vec<(PublicKeyCredentialType, i64)>,
pub pub_key_cred_params: Vec<PublicKeyCredentialParameter>,
pub exclude_list: Option<Vec<PublicKeyCredentialDescriptor>>,
pub extensions: Option<Extensions>,
// Even though options are optional, we can use the default if not present.
@@ -132,23 +137,19 @@ impl TryFrom<cbor::Value> for AuthenticatorMakeCredentialParameters {
)?)?;
let cred_param_vec = read_array(ok_or_missing(param_map.get(&cbor_unsigned!(4)))?)?;
let mut pub_key_cred_params = vec![];
for cred_param_map_value in cred_param_vec {
let cred_param_map = read_map(cred_param_map_value)?;
let cred_type = PublicKeyCredentialType::try_from(ok_or_missing(
cred_param_map.get(&cbor_text!("type")),
)?)?;
let alg = read_integer(ok_or_missing(cred_param_map.get(&cbor_text!("alg")))?)?;
pub_key_cred_params.push((cred_type, alg));
}
let pub_key_cred_params = cred_param_vec
.iter()
.map(PublicKeyCredentialParameter::try_from)
.collect::<Result<Vec<PublicKeyCredentialParameter>, Ctap2StatusCode>>()?;
let exclude_list = match param_map.get(&cbor_unsigned!(5)) {
Some(entry) => {
let exclude_list_vec = read_array(entry)?;
let mut exclude_list = vec![];
for exclude_list_value in exclude_list_vec {
exclude_list.push(PublicKeyCredentialDescriptor::try_from(exclude_list_value)?);
}
let exclude_list = exclude_list_vec
.iter()
.take(MAX_CREDENTIAL_COUNT_IN_LIST.unwrap_or(exclude_list_vec.len()))
.map(PublicKeyCredentialDescriptor::try_from)
.collect::<Result<Vec<PublicKeyCredentialDescriptor>, Ctap2StatusCode>>()?;
Some(exclude_list)
}
None => None,
@@ -216,10 +217,11 @@ impl TryFrom<cbor::Value> for AuthenticatorGetAssertionParameters {
let allow_list = match param_map.get(&cbor_unsigned!(3)) {
Some(entry) => {
let allow_list_vec = read_array(entry)?;
let mut allow_list = vec![];
for allow_list_value in allow_list_vec {
allow_list.push(PublicKeyCredentialDescriptor::try_from(allow_list_value)?);
}
let allow_list = allow_list_vec
.iter()
.take(MAX_CREDENTIAL_COUNT_IN_LIST.unwrap_or(allow_list_vec.len()))
.map(PublicKeyCredentialDescriptor::try_from)
.collect::<Result<Vec<PublicKeyCredentialDescriptor>, Ctap2StatusCode>>()?;
Some(allow_list)
}
None => None,
@@ -316,8 +318,10 @@ impl TryFrom<cbor::Value> for AuthenticatorClientPinParameters {
#[cfg(test)]
mod test {
use super::super::data_formats::{
AuthenticatorTransport, PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity,
AuthenticatorTransport, PublicKeyCredentialRpEntity, PublicKeyCredentialType,
PublicKeyCredentialUserEntity,
};
use super::super::ES256_CRED_PARAM;
use super::*;
use alloc::collections::BTreeMap;
@@ -336,10 +340,7 @@ mod test {
"displayName" => "bar",
"icon" => "example.com/foo/icon.png",
},
4 => cbor_array![ cbor_map! {
"type" => "public-key",
"alg" => -7
} ],
4 => cbor_array![ES256_CRED_PARAM],
5 => cbor_array![],
8 => vec![0x12, 0x34],
9 => 1,
@@ -362,7 +363,6 @@ mod test {
user_display_name: Some("bar".to_string()),
user_icon: Some("example.com/foo/icon.png".to_string()),
};
let pub_key_cred_param = (PublicKeyCredentialType::PublicKey, -7);
let options = MakeCredentialOptions {
rk: false,
uv: false,
@@ -371,7 +371,7 @@ mod test {
client_data_hash,
rp,
user,
pub_key_cred_params: vec![pub_key_cred_param],
pub_key_cred_params: vec![ES256_CRED_PARAM],
exclude_list: Some(vec![]),
extensions: None,
options,

View File

@@ -19,6 +19,7 @@ use alloc::vec::Vec;
use core::convert::TryFrom;
use crypto::{ecdh, ecdsa};
// https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialrpentity
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
pub struct PublicKeyCredentialRpEntity {
pub rp_id: String,
@@ -48,6 +49,7 @@ impl TryFrom<&cbor::Value> for PublicKeyCredentialRpEntity {
}
}
// https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialuserentity
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
pub struct PublicKeyCredentialUserEntity {
pub user_id: Vec<u8>,
@@ -94,16 +96,22 @@ impl From<PublicKeyCredentialUserEntity> for cbor::Value {
}
}
// https://www.w3.org/TR/webauthn/#enumdef-publickeycredentialtype
#[derive(Clone, PartialEq)]
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))]
pub enum PublicKeyCredentialType {
PublicKey,
// This is the default for all strings not covered above.
// Unknown types should be ignored, instead of returning errors.
Unknown,
}
impl From<PublicKeyCredentialType> for cbor::Value {
fn from(cred_type: PublicKeyCredentialType) -> Self {
match cred_type {
PublicKeyCredentialType::PublicKey => "public-key",
// We should never create this credential type.
PublicKeyCredentialType::Unknown => "unknown",
}
.into()
}
@@ -116,11 +124,43 @@ impl TryFrom<&cbor::Value> for PublicKeyCredentialType {
let cred_type_string = read_text_string(cbor_value)?;
match &cred_type_string[..] {
"public-key" => Ok(PublicKeyCredentialType::PublicKey),
_ => Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM),
_ => Ok(PublicKeyCredentialType::Unknown),
}
}
}
// https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialparameters
#[derive(PartialEq)]
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))]
pub struct PublicKeyCredentialParameter {
pub cred_type: PublicKeyCredentialType,
pub alg: SignatureAlgorithm,
}
impl TryFrom<&cbor::Value> for PublicKeyCredentialParameter {
type Error = Ctap2StatusCode;
fn try_from(cbor_value: &cbor::Value) -> Result<Self, Ctap2StatusCode> {
let cred_param_map = read_map(cbor_value)?;
let cred_type = PublicKeyCredentialType::try_from(ok_or_missing(
cred_param_map.get(&cbor_text!("type")),
)?)?;
let alg =
SignatureAlgorithm::try_from(ok_or_missing(cred_param_map.get(&cbor_text!("alg")))?)?;
Ok(Self { cred_type, alg })
}
}
impl From<PublicKeyCredentialParameter> for cbor::Value {
fn from(cred_param: PublicKeyCredentialParameter) -> Self {
cbor_map_options! {
"type" => cred_param.cred_type,
"alg" => cred_param.alg as i64,
}
}
}
// https://www.w3.org/TR/webauthn/#enumdef-authenticatortransport
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
pub enum AuthenticatorTransport {
Usb,
@@ -156,6 +196,7 @@ impl TryFrom<&cbor::Value> for AuthenticatorTransport {
}
}
// https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialdescriptor
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
pub struct PublicKeyCredentialDescriptor {
pub key_type: PublicKeyCredentialType,
@@ -175,10 +216,11 @@ impl TryFrom<&cbor::Value> for PublicKeyCredentialDescriptor {
let transports = match cred_desc_map.get(&cbor_text!("transports")) {
Some(exclude_entry) => {
let transport_vec = read_array(exclude_entry)?;
let mut transports = vec![];
for transport_value in transport_vec {
transports.push(AuthenticatorTransport::try_from(transport_value)?);
}
let transports = transport_vec
.iter()
.map(AuthenticatorTransport::try_from)
.collect::<Result<Vec<AuthenticatorTransport>, Ctap2StatusCode>>(
)?;
Some(transports)
}
None => None,
@@ -349,6 +391,7 @@ impl TryFrom<&cbor::Value> for GetAssertionOptions {
}
}
// https://www.w3.org/TR/webauthn/#packed-attestation
#[cfg_attr(test, derive(PartialEq))]
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))]
pub struct PackedAttestationStatement {
@@ -369,12 +412,27 @@ impl From<PackedAttestationStatement> for cbor::Value {
}
}
#[cfg_attr(test, derive(PartialEq))]
#[derive(PartialEq)]
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))]
pub enum SignatureAlgorithm {
ES256 = ecdsa::PubKey::ES256_ALGORITHM as isize,
// This is the default for all numbers not covered above.
// Unknown types should be ignored, instead of returning errors.
Unknown = 0,
}
impl TryFrom<&cbor::Value> for SignatureAlgorithm {
type Error = Ctap2StatusCode;
fn try_from(cbor_value: &cbor::Value) -> Result<Self, Ctap2StatusCode> {
match read_integer(cbor_value)? {
ecdsa::PubKey::ES256_ALGORITHM => Ok(SignatureAlgorithm::ES256),
_ => Ok(SignatureAlgorithm::Unknown),
}
}
}
// https://www.w3.org/TR/webauthn/#public-key-credential-source
#[derive(Clone)]
#[cfg_attr(test, derive(PartialEq))]
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))]
@@ -638,6 +696,7 @@ mod test {
use self::Ctap2StatusCode::CTAP2_ERR_CBOR_UNEXPECTED_TYPE;
use super::*;
use alloc::collections::BTreeMap;
use crypto::rng256::{Rng256, ThreadRng256};
#[test]
fn test_read_unsigned() {
@@ -949,6 +1008,26 @@ mod test {
assert_eq!(credential_type, Ok(expected_credential_type));
let created_cbor: cbor::Value = credential_type.unwrap().into();
assert_eq!(created_cbor, cbor_credential_type);
let cbor_unknown_type = cbor_text!("unknown-type");
let unknown_type = PublicKeyCredentialType::try_from(&cbor_unknown_type);
let expected_unknown_type = PublicKeyCredentialType::Unknown;
assert_eq!(unknown_type, Ok(expected_unknown_type));
}
#[test]
fn test_from_into_signature_algorithm() {
let cbor_signature_algorithm = cbor_int!(ecdsa::PubKey::ES256_ALGORITHM);
let signature_algorithm = SignatureAlgorithm::try_from(&cbor_signature_algorithm);
let expected_signature_algorithm = SignatureAlgorithm::ES256;
assert_eq!(signature_algorithm, Ok(expected_signature_algorithm));
let created_cbor: cbor::Value = cbor_int!(signature_algorithm.unwrap() as i64);
assert_eq!(created_cbor, cbor_signature_algorithm);
let cbor_unknown_algorithm = cbor_int!(-1);
let unknown_algorithm = SignatureAlgorithm::try_from(&cbor_unknown_algorithm);
let expected_unknown_algorithm = SignatureAlgorithm::Unknown;
assert_eq!(unknown_algorithm, Ok(expected_unknown_algorithm));
}
#[test]
@@ -965,6 +1044,23 @@ mod test {
assert_eq!(created_cbor, cbor_authenticator_transport);
}
#[test]
fn test_from_into_public_key_credential_parameter() {
let cbor_credential_parameter = cbor_map! {
"type" => "public-key",
"alg" => ecdsa::PubKey::ES256_ALGORITHM,
};
let credential_parameter =
PublicKeyCredentialParameter::try_from(&cbor_credential_parameter);
let expected_credential_parameter = PublicKeyCredentialParameter {
cred_type: PublicKeyCredentialType::PublicKey,
alg: SignatureAlgorithm::ES256,
};
assert_eq!(credential_parameter, Ok(expected_credential_parameter));
let created_cbor: cbor::Value = credential_parameter.unwrap().into();
assert_eq!(created_cbor, cbor_credential_parameter);
}
#[test]
fn test_from_into_public_key_credential_descriptor() {
let cbor_credential_descriptor = cbor_map! {
@@ -985,7 +1081,7 @@ mod test {
}
#[test]
fn test_from_extensions() {
fn test_from_into_extensions() {
let cbor_extensions = cbor_map! {
"the_answer" => 42,
};
@@ -995,6 +1091,53 @@ mod test {
.0
.insert("the_answer".to_string(), cbor_int!(42));
assert_eq!(extensions, Ok(expected_extensions));
let created_cbor: cbor::Value = extensions.unwrap().into();
assert_eq!(created_cbor, cbor_extensions);
}
#[test]
fn test_from_into_get_assertion_hmac_secret_output() {
let cbor_output = cbor_bytes![vec![0xC0; 32]];
let output = GetAssertionHmacSecretOutput::try_from(&cbor_output);
let expected_output = GetAssertionHmacSecretOutput(vec![0xC0; 32]);
assert_eq!(output, Ok(expected_output));
let created_cbor: cbor::Value = output.unwrap().into();
assert_eq!(created_cbor, cbor_output);
}
#[test]
fn test_hmac_secret_extension() {
let cbor_extensions = cbor_map! {
"hmac-secret" => true,
};
let extensions = Extensions::try_from(&cbor_extensions).unwrap();
assert!(extensions.has_make_credential_hmac_secret().unwrap());
let cbor_extensions = cbor_map! {
"hmac-secret" => false,
};
let extensions = Extensions::try_from(&cbor_extensions).unwrap();
assert!(!extensions.has_make_credential_hmac_secret().unwrap());
let mut rng = ThreadRng256 {};
let sk = crypto::ecdh::SecKey::gensk(&mut rng);
let pk = sk.genpk();
let cose_key = CoseKey::from(pk.clone());
let cbor_extensions = cbor_map! {
"hmac-secret" => cbor_map! {
1 => cbor::Value::Map(cose_key.0.clone()),
2 => vec![0x02; 32],
3 => vec![0x03; 32],
},
};
let extensions = Extensions::try_from(&cbor_extensions).unwrap();
let get_assertion_input = extensions.get_assertion_hmac_secret();
let expected_input = GetAssertionHmacSecretInput {
key_agreement: cose_key,
salt_enc: vec![0x02; 32],
salt_auth: vec![0x03; 32],
};
assert_eq!(get_assertion_input, Some(Ok(expected_input)));
}
#[test]
@@ -1046,8 +1189,6 @@ mod test {
#[test]
fn test_from_into_cose_key() {
use crypto::rng256::ThreadRng256;
let mut rng = ThreadRng256 {};
let sk = crypto::ecdh::SecKey::gensk(&mut rng);
let pk = sk.genpk();
@@ -1068,8 +1209,6 @@ mod test {
#[test]
fn test_credential_source_cbor_round_trip() {
use crypto::rng256::{Rng256, ThreadRng256};
let mut rng = ThreadRng256 {};
let credential = PublicKeyCredentialSource {
key_type: PublicKeyCredentialType::PublicKey,

View File

@@ -23,14 +23,18 @@ pub mod status_code;
mod storage;
mod timed_permission;
#[cfg(feature = "with_ctap2_1")]
use self::command::MAX_CREDENTIAL_COUNT_IN_LIST;
use self::command::{
AuthenticatorClientPinParameters, AuthenticatorGetAssertionParameters,
AuthenticatorMakeCredentialParameters, Command,
};
#[cfg(feature = "with_ctap2_1")]
use self::data_formats::AuthenticatorTransport;
use self::data_formats::{
ClientPinSubCommand, CoseKey, GetAssertionHmacSecretInput, PackedAttestationStatement,
PublicKeyCredentialDescriptor, PublicKeyCredentialSource, PublicKeyCredentialType,
PublicKeyCredentialUserEntity, SignatureAlgorithm,
PublicKeyCredentialDescriptor, PublicKeyCredentialParameter, PublicKeyCredentialSource,
PublicKeyCredentialType, PublicKeyCredentialUserEntity, SignatureAlgorithm,
};
use self::hid::ChannelID;
use self::key_material::{AAGUID, ATTESTATION_CERTIFICATE, ATTESTATION_PRIVATE_KEY};
@@ -99,6 +103,13 @@ pub const FIDO2_VERSION_STRING: &str = "FIDO_2_0";
#[cfg(feature = "with_ctap1")]
pub const U2F_VERSION_STRING: &str = "U2F_V2";
// We currently only support one algorithm for signatures: ES256.
// This algorithm is requested in MakeCredential and advertized in GetInfo.
pub const ES256_CRED_PARAM: PublicKeyCredentialParameter = PublicKeyCredentialParameter {
cred_type: PublicKeyCredentialType::PublicKey,
alg: SignatureAlgorithm::ES256,
};
fn check_pin_auth(hmac_key: &[u8], hmac_contents: &[u8], pin_auth: &[u8]) -> bool {
if pin_auth.len() != PIN_AUTH_LENGTH {
return false;
@@ -413,15 +424,7 @@ where
}
}
let has_es_256 = pub_key_cred_params
.iter()
.any(|(credential_type, algorithm)| {
// Even though there is only one type now, checking seems safer in
// case of extension so you can't forget to update here.
*credential_type == PublicKeyCredentialType::PublicKey
&& *algorithm == SignatureAlgorithm::ES256 as i64
});
if !has_es_256 {
if !pub_key_cred_params.contains(&ES256_CRED_PARAM) {
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM);
}
@@ -751,7 +754,7 @@ where
fn process_get_info(&self) -> Result<ResponseData, Ctap2StatusCode> {
let mut options_map = BTreeMap::new();
// TODO(kaczmarczyck) add FIDO 2.1 options
// TODO(kaczmarczyck) add authenticatorConfig and credProtect options
options_map.insert(String::from("rk"), true);
options_map.insert(String::from("up"), true);
options_map.insert(
@@ -772,6 +775,18 @@ where
pin_protocols: Some(vec![
CtapState::<R, CheckUserPresence>::PIN_PROTOCOL_VERSION,
]),
#[cfg(feature = "with_ctap2_1")]
max_credential_count_in_list: MAX_CREDENTIAL_COUNT_IN_LIST.map(|c| c as u64),
// You can use ENCRYPTED_CREDENTIAL_ID_SIZE here, but if your
// browser passes that value, it might be used to fingerprint.
#[cfg(feature = "with_ctap2_1")]
max_credential_id_length: None,
#[cfg(feature = "with_ctap2_1")]
transports: Some(vec![AuthenticatorTransport::Usb]),
#[cfg(feature = "with_ctap2_1")]
algorithms: Some(vec![ES256_CRED_PARAM]),
#[cfg(feature = "with_ctap2_1")]
firmware_version: None,
},
))
}
@@ -1093,6 +1108,9 @@ mod test {
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
let info_reponse = ctap_state.process_command(&[0x04], DUMMY_CHANNEL_ID);
#[cfg(feature = "with_ctap2_1")]
let mut expected_response = vec![0x00, 0xA8, 0x01];
#[cfg(not(feature = "with_ctap2_1"))]
let mut expected_response = vec![0x00, 0xA6, 0x01];
// The difference here is a longer array of supported versions.
#[cfg(not(feature = "with_ctap1"))]
@@ -1111,6 +1129,15 @@ mod test {
0x04, 0xA3, 0x62, 0x72, 0x6B, 0xF5, 0x62, 0x75, 0x70, 0xF5, 0x69, 0x63, 0x6C, 0x69,
0x65, 0x6E, 0x74, 0x50, 0x69, 0x6E, 0xF4, 0x05, 0x19, 0x04, 0x00, 0x06, 0x81, 0x01,
]);
#[cfg(feature = "with_ctap2_1")]
expected_response.extend(
[
0x09, 0x81, 0x63, 0x75, 0x73, 0x62, 0x0A, 0x81, 0xA2, 0x63, 0x61, 0x6C, 0x67, 0x26,
0x64, 0x74, 0x79, 0x70, 0x65, 0x6A, 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x2D, 0x6B,
0x65, 0x79,
]
.iter(),
);
assert_eq!(info_reponse, expected_response);
}
@@ -1128,10 +1155,7 @@ mod test {
user_display_name: None,
user_icon: None,
};
let pub_key_cred_params = vec![(
PublicKeyCredentialType::PublicKey,
SignatureAlgorithm::ES256 as i64,
)];
let pub_key_cred_params = vec![ES256_CRED_PARAM];
let options = MakeCredentialOptions {
rk: true,
uv: false,
@@ -1228,12 +1252,8 @@ mod test {
let user_immediately_present = |_| Ok(());
let mut ctap_state = CtapState::new(&mut rng, user_immediately_present);
let pub_key_cred_params = vec![(
PublicKeyCredentialType::PublicKey,
SignatureAlgorithm::ES256 as i64 + 1, // any different number works
)];
let mut make_credential_params = create_minimal_make_credential_parameters();
make_credential_params.pub_key_cred_params = pub_key_cred_params;
make_credential_params.pub_key_cred_params = vec![];
let make_credential_response =
ctap_state.process_make_credential(make_credential_params, DUMMY_CHANNEL_ID);

View File

@@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#[cfg(feature = "with_ctap2_1")]
use super::data_formats::{AuthenticatorTransport, PublicKeyCredentialParameter};
use super::data_formats::{
CoseKey, PackedAttestationStatement, PublicKeyCredentialDescriptor,
PublicKeyCredentialUserEntity,
@@ -102,16 +104,66 @@ impl From<AuthenticatorGetAssertionResponse> for cbor::Value {
#[cfg_attr(test, derive(PartialEq))]
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))]
pub struct AuthenticatorGetInfoResponse {
// TODO(kaczmarczyck) add fields from 2.1
// TODO(kaczmarczyck) add maxAuthenticatorConfigLength and defaultCredProtect
pub versions: Vec<String>,
pub extensions: Option<Vec<String>>,
pub aaguid: [u8; 16],
pub options: Option<BTreeMap<String, bool>>,
pub max_msg_size: Option<u64>,
pub pin_protocols: Option<Vec<u64>>,
#[cfg(feature = "with_ctap2_1")]
pub max_credential_count_in_list: Option<u64>,
#[cfg(feature = "with_ctap2_1")]
pub max_credential_id_length: Option<u64>,
#[cfg(feature = "with_ctap2_1")]
pub transports: Option<Vec<AuthenticatorTransport>>,
#[cfg(feature = "with_ctap2_1")]
pub algorithms: Option<Vec<PublicKeyCredentialParameter>>,
#[cfg(feature = "with_ctap2_1")]
pub firmware_version: Option<u64>,
}
impl From<AuthenticatorGetInfoResponse> for cbor::Value {
#[cfg(feature = "with_ctap2_1")]
fn from(get_info_response: AuthenticatorGetInfoResponse) -> Self {
let AuthenticatorGetInfoResponse {
versions,
extensions,
aaguid,
options,
max_msg_size,
pin_protocols,
max_credential_count_in_list,
max_credential_id_length,
transports,
algorithms,
firmware_version,
} = get_info_response;
let options_cbor: Option<cbor::Value> = options.map(|options| {
let option_map: BTreeMap<_, _> = options
.into_iter()
.map(|(key, value)| (cbor_text!(key), cbor_bool!(value)))
.collect();
cbor_map_btree!(option_map)
});
cbor_map_options! {
0x01 => cbor_array_vec!(versions),
0x02 => extensions.map(|vec| cbor_array_vec!(vec)),
0x03 => &aaguid,
0x04 => options_cbor,
0x05 => max_msg_size,
0x06 => pin_protocols.map(|vec| cbor_array_vec!(vec)),
0x07 => max_credential_count_in_list,
0x08 => max_credential_id_length,
0x09 => transports.map(|vec| cbor_array_vec!(vec)),
0x0A => algorithms.map(|vec| cbor_array_vec!(vec)),
0x0E => firmware_version,
}
}
#[cfg(not(feature = "with_ctap2_1"))]
fn from(get_info_response: AuthenticatorGetInfoResponse) -> Self {
let AuthenticatorGetInfoResponse {
versions,
@@ -131,12 +183,12 @@ impl From<AuthenticatorGetInfoResponse> for cbor::Value {
});
cbor_map_options! {
1 => cbor_array_vec!(versions),
2 => extensions.map(|vec| cbor_array_vec!(vec)),
3 => &aaguid,
4 => options_cbor,
5 => max_msg_size,
6 => pin_protocols.map(|vec| cbor_array_vec!(vec)),
0x01 => cbor_array_vec!(versions),
0x02 => extensions.map(|vec| cbor_array_vec!(vec)),
0x03 => &aaguid,
0x04 => options_cbor,
0x05 => max_msg_size,
0x06 => pin_protocols.map(|vec| cbor_array_vec!(vec)),
}
}
}
@@ -168,6 +220,8 @@ impl From<AuthenticatorClientPinResponse> for cbor::Value {
#[cfg(test)]
mod test {
use super::super::data_formats::PackedAttestationStatement;
#[cfg(feature = "with_ctap2_1")]
use super::super::ES256_CRED_PARAM;
use super::*;
#[test]
@@ -228,12 +282,58 @@ mod test {
options: None,
max_msg_size: None,
pin_protocols: None,
#[cfg(feature = "with_ctap2_1")]
max_credential_count_in_list: None,
#[cfg(feature = "with_ctap2_1")]
max_credential_id_length: None,
#[cfg(feature = "with_ctap2_1")]
transports: None,
#[cfg(feature = "with_ctap2_1")]
algorithms: None,
#[cfg(feature = "with_ctap2_1")]
firmware_version: None,
};
let response_cbor: Option<cbor::Value> =
ResponseData::AuthenticatorGetInfo(get_info_response).into();
let expected_cbor = cbor_map_options! {
1 => cbor_array_vec![vec!["FIDO_2_0"]],
3 => vec![0x00; 16],
0x01 => cbor_array_vec![vec!["FIDO_2_0"]],
0x03 => vec![0x00; 16],
};
assert_eq!(response_cbor, Some(expected_cbor));
}
#[test]
#[cfg(feature = "with_ctap2_1")]
fn test_get_info_optionals_into_cbor() {
let mut options_map = BTreeMap::new();
options_map.insert(String::from("rk"), true);
let get_info_response = AuthenticatorGetInfoResponse {
versions: vec!["FIDO_2_0".to_string()],
extensions: Some(vec!["extension".to_string()]),
aaguid: [0x00; 16],
options: Some(options_map),
max_msg_size: Some(1024),
pin_protocols: Some(vec![1]),
max_credential_count_in_list: Some(20),
max_credential_id_length: Some(256),
transports: Some(vec![AuthenticatorTransport::Usb]),
algorithms: Some(vec![ES256_CRED_PARAM]),
firmware_version: Some(0),
};
let response_cbor: Option<cbor::Value> =
ResponseData::AuthenticatorGetInfo(get_info_response).into();
let expected_cbor = cbor_map_options! {
0x01 => cbor_array_vec![vec!["FIDO_2_0"]],
0x02 => cbor_array_vec![vec!["extension"]],
0x03 => vec![0x00; 16],
0x04 => cbor_map! {"rk" => true},
0x05 => 1024,
0x06 => cbor_array_vec![vec![1]],
0x07 => 20,
0x08 => 256,
0x09 => cbor_array_vec![vec!["usb"]],
0x0A => cbor_array_vec![vec![ES256_CRED_PARAM]],
0x0E => 0,
};
assert_eq!(response_cbor, Some(expected_cbor));
}