Merge branch 'develop' into authenticator-config

This commit is contained in:
kaczmarczyck
2021-01-14 15:14:07 +01:00
committed by GitHub
7 changed files with 264 additions and 119 deletions

View File

@@ -17,6 +17,7 @@ You will need one the following supported boards:
* [Nordic nRF52840 Dongle](https://www.nordicsemi.com/Software-and-tools/Development-Kits/nRF52840-Dongle) * [Nordic nRF52840 Dongle](https://www.nordicsemi.com/Software-and-tools/Development-Kits/nRF52840-Dongle)
to have a more practical form factor. to have a more practical form factor.
* [Makerdiary nRF52840-MDK USB dongle](https://wiki.makerdiary.com/nrf52840-mdk/). * [Makerdiary nRF52840-MDK USB dongle](https://wiki.makerdiary.com/nrf52840-mdk/).
* [Feitian OpenSK dongle](https://feitiantech.github.io/OpenSK_USB/).
In the case of the Nordic USB dongle, you may also need the following extra In the case of the Nordic USB dongle, you may also need the following extra
hardware: hardware:

View File

@@ -62,8 +62,10 @@ impl SecKey {
// - https://www.secg.org/sec1-v2.pdf // - https://www.secg.org/sec1-v2.pdf
} }
// DH key agreement method defined in the FIDO2 specification, Section 5.5.4. "Getting /// Creates a shared key using the Diffie Hellman key agreement.
// sharedSecret from Authenticator" ///
/// The key agreement is defined in the FIDO2 specification,
/// Section 6.5.5.4. "Obtaining the Shared Secret"
pub fn exchange_x_sha256(&self, other: &PubKey) -> [u8; 32] { pub fn exchange_x_sha256(&self, other: &PubKey) -> [u8; 32] {
let p = self.exchange_raw(other); let p = self.exchange_raw(other);
let mut x: [u8; 32] = [Default::default(); 32]; let mut x: [u8; 32] = [Default::default(); 32];
@@ -83,11 +85,13 @@ impl PubKey {
self.p.to_bytes_uncompressed(bytes); self.p.to_bytes_uncompressed(bytes);
} }
/// Creates a new PubKey from its coordinates on the elliptic curve.
pub fn from_coordinates(x: &[u8; NBYTES], y: &[u8; NBYTES]) -> Option<PubKey> { pub fn from_coordinates(x: &[u8; NBYTES], y: &[u8; NBYTES]) -> Option<PubKey> {
PointP256::new_checked_vartime(Int256::from_bin(x), Int256::from_bin(y)) PointP256::new_checked_vartime(Int256::from_bin(x), Int256::from_bin(y))
.map(|p| PubKey { p }) .map(|p| PubKey { p })
} }
/// Writes the coordinates into the passed in arrays.
pub fn to_coordinates(&self, x: &mut [u8; NBYTES], y: &mut [u8; NBYTES]) { pub fn to_coordinates(&self, x: &mut [u8; NBYTES], y: &mut [u8; NBYTES]) {
self.p.getx().to_int().to_bin(x); self.p.getx().to_int().to_bin(x);
self.p.gety().to_int().to_bin(y); self.p.gety().to_int().to_bin(y);

View File

@@ -21,12 +21,15 @@ use super::rng256::Rng256;
use super::{Hash256, HashBlockSize64Bytes}; use super::{Hash256, HashBlockSize64Bytes};
use alloc::vec; use alloc::vec;
use alloc::vec::Vec; use alloc::vec::Vec;
#[cfg(test)]
use arrayref::array_mut_ref;
#[cfg(feature = "std")] #[cfg(feature = "std")]
use arrayref::array_ref; use arrayref::array_ref;
use arrayref::{array_mut_ref, mut_array_refs}; use arrayref::mut_array_refs;
use cbor::{cbor_bytes, cbor_map_options};
use core::marker::PhantomData; use core::marker::PhantomData;
pub const NBYTES: usize = int256::NBYTES;
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
#[cfg_attr(feature = "derive_debug", derive(Debug))] #[cfg_attr(feature = "derive_debug", derive(Debug))]
pub struct SecKey { pub struct SecKey {
@@ -38,6 +41,7 @@ pub struct Signature {
s: NonZeroExponentP256, s: NonZeroExponentP256,
} }
#[cfg_attr(feature = "derive_debug", derive(Clone))]
pub struct PubKey { pub struct PubKey {
p: PointP256, p: PointP256,
} }
@@ -58,10 +62,11 @@ impl SecKey {
} }
} }
// ECDSA signature based on a RNG to generate a suitable randomization parameter. /// Creates an ECDSA signature based on a RNG.
// Under the hood, rejection sampling is used to make sure that the randomization parameter is ///
// uniformly distributed. /// Under the hood, rejection sampling is used to make sure that the
// The provided RNG must be cryptographically secure; otherwise this method is insecure. /// randomization parameter is uniformly distributed. The provided RNG must
/// be cryptographically secure; otherwise this method is insecure.
pub fn sign_rng<H, R>(&self, msg: &[u8], rng: &mut R) -> Signature pub fn sign_rng<H, R>(&self, msg: &[u8], rng: &mut R) -> Signature
where where
H: Hash256, H: Hash256,
@@ -77,8 +82,7 @@ impl SecKey {
} }
} }
// Deterministic ECDSA signature based on RFC 6979 to generate a suitable randomization /// Creates a deterministic ECDSA signature based on RFC 6979.
// parameter.
pub fn sign_rfc6979<H>(&self, msg: &[u8]) -> Signature pub fn sign_rfc6979<H>(&self, msg: &[u8]) -> Signature
where where
H: Hash256 + HashBlockSize64Bytes, H: Hash256 + HashBlockSize64Bytes,
@@ -101,8 +105,10 @@ impl SecKey {
} }
} }
// Try signing a curve element given a randomization parameter k. If no signature can be /// Try signing a curve element given a randomization parameter k.
// obtained from this k, None is returned and the caller should try again with another value. ///
/// If no signature can be obtained from this k, None is returned and the
/// caller should try again with another value.
fn try_sign(&self, k: &NonZeroExponentP256, msg: &ExponentP256) -> Option<Signature> { fn try_sign(&self, k: &NonZeroExponentP256, msg: &ExponentP256) -> Option<Signature> {
let r = ExponentP256::modn(PointP256::base_point_mul(k.as_exponent()).getx().to_int()); let r = ExponentP256::modn(PointP256::base_point_mul(k.as_exponent()).getx().to_int());
// The branching here is fine because all this reveals is that k generated an unsuitable r. // The branching here is fine because all this reveals is that k generated an unsuitable r.
@@ -214,7 +220,6 @@ impl Signature {
} }
impl PubKey { impl PubKey {
pub const ES256_ALGORITHM: i64 = -7;
#[cfg(feature = "with_ctap1")] #[cfg(feature = "with_ctap1")]
const UNCOMPRESSED_LENGTH: usize = 1 + 2 * int256::NBYTES; const UNCOMPRESSED_LENGTH: usize = 1 + 2 * int256::NBYTES;
@@ -242,35 +247,10 @@ impl PubKey {
representation representation
} }
// Encodes the key according to CBOR Object Signing and Encryption, defined in RFC 8152. /// Writes the coordinates into the passed in arrays.
pub fn to_cose_key(&self) -> Option<Vec<u8>> { pub fn to_coordinates(&self, x: &mut [u8; NBYTES], y: &mut [u8; NBYTES]) {
const EC2_KEY_TYPE: i64 = 2; self.p.getx().to_int().to_bin(x);
const P_256_CURVE: i64 = 1; self.p.gety().to_int().to_bin(y);
let mut x_bytes = vec![0; int256::NBYTES];
self.p
.getx()
.to_int()
.to_bin(array_mut_ref![x_bytes.as_mut_slice(), 0, int256::NBYTES]);
let x_byte_cbor: cbor::Value = cbor_bytes!(x_bytes);
let mut y_bytes = vec![0; int256::NBYTES];
self.p
.gety()
.to_int()
.to_bin(array_mut_ref![y_bytes.as_mut_slice(), 0, int256::NBYTES]);
let y_byte_cbor: cbor::Value = cbor_bytes!(y_bytes);
let cbor_value = cbor_map_options! {
1 => EC2_KEY_TYPE,
3 => PubKey::ES256_ALGORITHM,
-1 => P_256_CURVE,
-2 => x_byte_cbor,
-3 => y_byte_cbor,
};
let mut encoded_key = Vec::new();
if cbor::write(cbor_value, &mut encoded_key) {
Some(encoded_key)
} else {
None
}
} }
#[cfg(feature = "std")] #[cfg(feature = "std")]

View File

@@ -320,7 +320,7 @@ impl TryFrom<cbor::Value> for AuthenticatorClientPinParameters {
let pin_protocol = extract_unsigned(ok_or_missing(pin_protocol)?)?; let pin_protocol = extract_unsigned(ok_or_missing(pin_protocol)?)?;
let sub_command = ClientPinSubCommand::try_from(ok_or_missing(sub_command)?)?; let sub_command = ClientPinSubCommand::try_from(ok_or_missing(sub_command)?)?;
let key_agreement = key_agreement.map(extract_map).transpose()?.map(CoseKey); let key_agreement = key_agreement.map(CoseKey::try_from).transpose()?;
let pin_auth = pin_auth.map(extract_byte_string).transpose()?; let pin_auth = pin_auth.map(extract_byte_string).transpose()?;
let new_pin_enc = new_pin_enc.map(extract_byte_string).transpose()?; let new_pin_enc = new_pin_enc.map(extract_byte_string).transpose()?;
let pin_hash_enc = pin_hash_enc.map(extract_byte_string).transpose()?; let pin_hash_enc = pin_hash_enc.map(extract_byte_string).transpose()?;
@@ -449,8 +449,8 @@ mod test {
}; };
use super::super::ES256_CRED_PARAM; use super::super::ES256_CRED_PARAM;
use super::*; use super::*;
use alloc::collections::BTreeMap;
use cbor::{cbor_array, cbor_map}; use cbor::{cbor_array, cbor_map};
use crypto::rng256::ThreadRng256;
#[test] #[test]
fn test_from_cbor_make_credential_parameters() { fn test_from_cbor_make_credential_parameters() {
@@ -560,10 +560,15 @@ mod test {
#[test] #[test]
fn test_from_cbor_client_pin_parameters() { fn test_from_cbor_client_pin_parameters() {
let mut rng = ThreadRng256 {};
let sk = crypto::ecdh::SecKey::gensk(&mut rng);
let pk = sk.genpk();
let cose_key = CoseKey::from(pk);
let cbor_value = cbor_map! { let cbor_value = cbor_map! {
1 => 1, 1 => 1,
2 => ClientPinSubCommand::GetPinRetries, 2 => ClientPinSubCommand::GetPinRetries,
3 => cbor_map!{}, 3 => cbor::Value::from(cose_key.clone()),
4 => vec! [0xBB], 4 => vec! [0xBB],
5 => vec! [0xCC], 5 => vec! [0xCC],
6 => vec! [0xDD], 6 => vec! [0xDD],
@@ -576,7 +581,7 @@ mod test {
let expected_pin_protocol_parameters = AuthenticatorClientPinParameters { let expected_pin_protocol_parameters = AuthenticatorClientPinParameters {
pin_protocol: 1, pin_protocol: 1,
sub_command: ClientPinSubCommand::GetPinRetries, sub_command: ClientPinSubCommand::GetPinRetries,
key_agreement: Some(CoseKey(BTreeMap::new())), key_agreement: Some(cose_key),
pin_auth: Some(vec![0xBB]), pin_auth: Some(vec![0xBB]),
new_pin_enc: Some(vec![0xCC]), new_pin_enc: Some(vec![0xCC]),
pin_hash_enc: Some(vec![0xDD]), pin_hash_enc: Some(vec![0xDD]),

View File

@@ -17,12 +17,15 @@ use alloc::collections::BTreeMap;
use alloc::string::String; use alloc::string::String;
use alloc::vec::Vec; use alloc::vec::Vec;
use arrayref::array_ref; use arrayref::array_ref;
use cbor::{cbor_array_vec, cbor_bytes_lit, cbor_map_options, destructure_cbor_map}; use cbor::{cbor_array_vec, cbor_map, cbor_map_options, destructure_cbor_map};
use core::convert::TryFrom; use core::convert::TryFrom;
use crypto::{ecdh, ecdsa}; use crypto::{ecdh, ecdsa};
#[cfg(test)] #[cfg(test)]
use enum_iterator::IntoEnumIterator; use enum_iterator::IntoEnumIterator;
// Used as the identifier for ECDSA in assertion signatures and COSE.
const ES256_ALGORITHM: i64 = -7;
// https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialrpentity // https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialrpentity
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))] #[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
pub struct PublicKeyCredentialRpEntity { pub struct PublicKeyCredentialRpEntity {
@@ -326,17 +329,17 @@ impl TryFrom<cbor::Value> for GetAssertionHmacSecretInput {
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> { fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
destructure_cbor_map! { destructure_cbor_map! {
let { let {
1 => cose_key, 1 => key_agreement,
2 => salt_enc, 2 => salt_enc,
3 => salt_auth, 3 => salt_auth,
} = extract_map(cbor_value)?; } = extract_map(cbor_value)?;
} }
let cose_key = extract_map(ok_or_missing(cose_key)?)?; let key_agreement = CoseKey::try_from(ok_or_missing(key_agreement)?)?;
let salt_enc = extract_byte_string(ok_or_missing(salt_enc)?)?; let salt_enc = extract_byte_string(ok_or_missing(salt_enc)?)?;
let salt_auth = extract_byte_string(ok_or_missing(salt_auth)?)?; let salt_auth = extract_byte_string(ok_or_missing(salt_auth)?)?;
Ok(Self { Ok(Self {
key_agreement: CoseKey(cose_key), key_agreement,
salt_enc, salt_enc,
salt_auth, salt_auth,
}) })
@@ -436,7 +439,7 @@ impl From<PackedAttestationStatement> for cbor::Value {
#[derive(PartialEq)] #[derive(PartialEq)]
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))] #[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug))]
pub enum SignatureAlgorithm { pub enum SignatureAlgorithm {
ES256 = ecdsa::PubKey::ES256_ALGORITHM as isize, ES256 = ES256_ALGORITHM as isize,
// This is the default for all numbers not covered above. // This is the default for all numbers not covered above.
// Unknown types should be ignored, instead of returning errors. // Unknown types should be ignored, instead of returning errors.
Unknown = 0, Unknown = 0,
@@ -453,7 +456,7 @@ impl TryFrom<cbor::Value> for SignatureAlgorithm {
fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> { fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
match extract_integer(cbor_value)? { match extract_integer(cbor_value)? {
ecdsa::PubKey::ES256_ALGORITHM => Ok(SignatureAlgorithm::ES256), ES256_ALGORITHM => Ok(SignatureAlgorithm::ES256),
_ => Ok(SignatureAlgorithm::Unknown), _ => Ok(SignatureAlgorithm::Unknown),
} }
} }
@@ -618,72 +621,42 @@ impl PublicKeyCredentialSource {
} }
} }
// TODO(kaczmarczyck) we could decide to split this data type up // The COSE key is used for both ECDH and ECDSA public keys for transmission.
// It depends on the algorithm though, I think.
// So before creating a mess, this is my workaround.
#[derive(Clone)] #[derive(Clone)]
#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))] #[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
pub struct CoseKey(pub BTreeMap<cbor::KeyType, cbor::Value>); pub struct CoseKey {
x_bytes: [u8; ecdh::NBYTES],
// This is the algorithm specifier that is supposed to be used in a COSE key y_bytes: [u8; ecdh::NBYTES],
// map. The CTAP specification says -25 which represents ECDH-ES + HKDF-256 algorithm: i64,
// here: https://www.iana.org/assignments/cose/cose.xhtml#algorithms
// In fact, this is just used for compatibility with older specification versions.
const ECDH_ALGORITHM: i64 = -25;
// This is the identifier used by OpenSSH. To be compatible, we accept both.
const ES256_ALGORITHM: i64 = -7;
const EC2_KEY_TYPE: i64 = 2;
const P_256_CURVE: i64 = 1;
impl From<ecdh::PubKey> for CoseKey {
fn from(pk: ecdh::PubKey) -> Self {
let mut x_bytes = [0; ecdh::NBYTES];
let mut y_bytes = [0; ecdh::NBYTES];
pk.to_coordinates(&mut x_bytes, &mut y_bytes);
let x_byte_cbor: cbor::Value = cbor_bytes_lit!(&x_bytes);
let y_byte_cbor: cbor::Value = cbor_bytes_lit!(&y_bytes);
// TODO(kaczmarczyck) do not write optional parameters, spec is unclear
let cose_cbor_value = cbor_map_options! {
1 => EC2_KEY_TYPE,
3 => ECDH_ALGORITHM,
-1 => P_256_CURVE,
-2 => x_byte_cbor,
-3 => y_byte_cbor,
};
if let cbor::Value::Map(cose_map) = cose_cbor_value {
CoseKey(cose_map)
} else {
unreachable!();
}
}
} }
impl TryFrom<CoseKey> for ecdh::PubKey { impl CoseKey {
// This is the algorithm specifier for ECDH.
// CTAP requests -25 which represents ECDH-ES + HKDF-256 here:
// https://www.iana.org/assignments/cose/cose.xhtml#algorithms
const ECDH_ALGORITHM: i64 = -25;
// The parameter behind map key 1.
const EC2_KEY_TYPE: i64 = 2;
// The parameter behind map key -1.
const P_256_CURVE: i64 = 1;
}
// This conversion accepts both ECDH and ECDSA.
impl TryFrom<cbor::Value> for CoseKey {
type Error = Ctap2StatusCode; type Error = Ctap2StatusCode;
fn try_from(cose_key: CoseKey) -> Result<Self, Ctap2StatusCode> { fn try_from(cbor_value: cbor::Value) -> Result<Self, Ctap2StatusCode> {
destructure_cbor_map! { destructure_cbor_map! {
let { let {
// This is sorted correctly, negative encoding is bigger.
1 => key_type, 1 => key_type,
3 => algorithm, 3 => algorithm,
-1 => curve, -1 => curve,
-2 => x_bytes, -2 => x_bytes,
-3 => y_bytes, -3 => y_bytes,
} = cose_key.0; } = extract_map(cbor_value)?;
} }
let key_type = extract_integer(ok_or_missing(key_type)?)?;
if key_type != EC2_KEY_TYPE {
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM);
}
let algorithm = extract_integer(ok_or_missing(algorithm)?)?;
if algorithm != ECDH_ALGORITHM && algorithm != ES256_ALGORITHM {
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM);
}
let curve = extract_integer(ok_or_missing(curve)?)?;
if curve != P_256_CURVE {
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM);
}
let x_bytes = extract_byte_string(ok_or_missing(x_bytes)?)?; let x_bytes = extract_byte_string(ok_or_missing(x_bytes)?)?;
if x_bytes.len() != ecdh::NBYTES { if x_bytes.len() != ecdh::NBYTES {
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER); return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
@@ -692,10 +665,89 @@ impl TryFrom<CoseKey> for ecdh::PubKey {
if y_bytes.len() != ecdh::NBYTES { if y_bytes.len() != ecdh::NBYTES {
return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER); return Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER);
} }
let curve = extract_integer(ok_or_missing(curve)?)?;
if curve != CoseKey::P_256_CURVE {
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM);
}
let key_type = extract_integer(ok_or_missing(key_type)?)?;
if key_type != CoseKey::EC2_KEY_TYPE {
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM);
}
let algorithm = extract_integer(ok_or_missing(algorithm)?)?;
if algorithm != CoseKey::ECDH_ALGORITHM && algorithm != ES256_ALGORITHM {
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM);
}
let x_array_ref = array_ref![x_bytes.as_slice(), 0, ecdh::NBYTES]; Ok(CoseKey {
let y_array_ref = array_ref![y_bytes.as_slice(), 0, ecdh::NBYTES]; x_bytes: *array_ref![x_bytes.as_slice(), 0, ecdh::NBYTES],
ecdh::PubKey::from_coordinates(x_array_ref, y_array_ref) y_bytes: *array_ref![y_bytes.as_slice(), 0, ecdh::NBYTES],
algorithm,
})
}
}
impl From<CoseKey> for cbor::Value {
fn from(cose_key: CoseKey) -> Self {
let CoseKey {
x_bytes,
y_bytes,
algorithm,
} = cose_key;
cbor_map! {
1 => CoseKey::EC2_KEY_TYPE,
3 => algorithm,
-1 => CoseKey::P_256_CURVE,
-2 => x_bytes,
-3 => y_bytes,
}
}
}
impl From<ecdh::PubKey> for CoseKey {
fn from(pk: ecdh::PubKey) -> Self {
let mut x_bytes = [0; ecdh::NBYTES];
let mut y_bytes = [0; ecdh::NBYTES];
pk.to_coordinates(&mut x_bytes, &mut y_bytes);
CoseKey {
x_bytes,
y_bytes,
algorithm: CoseKey::ECDH_ALGORITHM,
}
}
}
impl From<ecdsa::PubKey> for CoseKey {
fn from(pk: ecdsa::PubKey) -> Self {
let mut x_bytes = [0; ecdh::NBYTES];
let mut y_bytes = [0; ecdh::NBYTES];
pk.to_coordinates(&mut x_bytes, &mut y_bytes);
CoseKey {
x_bytes,
y_bytes,
algorithm: ES256_ALGORITHM,
}
}
}
impl TryFrom<CoseKey> for ecdh::PubKey {
type Error = Ctap2StatusCode;
fn try_from(cose_key: CoseKey) -> Result<Self, Ctap2StatusCode> {
let CoseKey {
x_bytes,
y_bytes,
algorithm,
} = cose_key;
// Since algorithm can be used for different COSE key types, we check
// whether the current type is correct for ECDH. For an OpenSSH bugfix,
// the algorithm ES256_ALGORITHM is allowed here too.
// https://github.com/google/OpenSK/issues/90
if algorithm != CoseKey::ECDH_ALGORITHM && algorithm != ES256_ALGORITHM {
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM);
}
ecdh::PubKey::from_coordinates(&x_bytes, &y_bytes)
.ok_or(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER) .ok_or(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
} }
} }
@@ -909,8 +961,8 @@ mod test {
use super::*; use super::*;
use alloc::collections::BTreeMap; use alloc::collections::BTreeMap;
use cbor::{ use cbor::{
cbor_array, cbor_bool, cbor_bytes, cbor_false, cbor_int, cbor_map, cbor_null, cbor_text, cbor_array, cbor_bool, cbor_bytes, cbor_bytes_lit, cbor_false, cbor_int, cbor_null,
cbor_unsigned, cbor_text, cbor_unsigned,
}; };
use crypto::rng256::{Rng256, ThreadRng256}; use crypto::rng256::{Rng256, ThreadRng256};
@@ -1233,7 +1285,7 @@ mod test {
#[test] #[test]
fn test_from_into_signature_algorithm() { fn test_from_into_signature_algorithm() {
let cbor_signature_algorithm: cbor::Value = cbor_int!(ecdsa::PubKey::ES256_ALGORITHM); let cbor_signature_algorithm: cbor::Value = cbor_int!(ES256_ALGORITHM);
let signature_algorithm = SignatureAlgorithm::try_from(cbor_signature_algorithm.clone()); let signature_algorithm = SignatureAlgorithm::try_from(cbor_signature_algorithm.clone());
let expected_signature_algorithm = SignatureAlgorithm::ES256; let expected_signature_algorithm = SignatureAlgorithm::ES256;
assert_eq!(signature_algorithm, Ok(expected_signature_algorithm)); assert_eq!(signature_algorithm, Ok(expected_signature_algorithm));
@@ -1307,7 +1359,7 @@ mod test {
fn test_from_into_public_key_credential_parameter() { fn test_from_into_public_key_credential_parameter() {
let cbor_credential_parameter = cbor_map! { let cbor_credential_parameter = cbor_map! {
"type" => "public-key", "type" => "public-key",
"alg" => ecdsa::PubKey::ES256_ALGORITHM, "alg" => ES256_ALGORITHM,
}; };
let credential_parameter = let credential_parameter =
PublicKeyCredentialParameter::try_from(cbor_credential_parameter.clone()); PublicKeyCredentialParameter::try_from(cbor_credential_parameter.clone());
@@ -1363,7 +1415,7 @@ mod test {
let cose_key = CoseKey::from(pk); let cose_key = CoseKey::from(pk);
let cbor_extensions = cbor_map! { let cbor_extensions = cbor_map! {
"hmac-secret" => cbor_map! { "hmac-secret" => cbor_map! {
1 => cbor::Value::Map(cose_key.0.clone()), 1 => cbor::Value::from(cose_key.clone()),
2 => vec![0x02; 32], 2 => vec![0x02; 32],
3 => vec![0x03; 16], 3 => vec![0x03; 16],
}, },
@@ -1428,7 +1480,103 @@ mod test {
} }
#[test] #[test]
fn test_from_into_cose_key() { fn test_from_into_cose_key_cbor() {
for algorithm in &[CoseKey::ECDH_ALGORITHM, ES256_ALGORITHM] {
let cbor_value = cbor_map! {
1 => CoseKey::EC2_KEY_TYPE,
3 => algorithm,
-1 => CoseKey::P_256_CURVE,
-2 => [0u8; 32],
-3 => [0u8; 32],
};
let cose_key = CoseKey::try_from(cbor_value.clone()).unwrap();
let created_cbor_value = cbor::Value::from(cose_key);
assert_eq!(created_cbor_value, cbor_value);
}
}
#[test]
fn test_cose_key_unknown_algorithm() {
let cbor_value = cbor_map! {
1 => CoseKey::EC2_KEY_TYPE,
// unknown algorithm
3 => 0,
-1 => CoseKey::P_256_CURVE,
-2 => [0u8; 32],
-3 => [0u8; 32],
};
assert_eq!(
CoseKey::try_from(cbor_value),
Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM)
);
}
#[test]
fn test_cose_key_unknown_type() {
let cbor_value = cbor_map! {
// unknown type
1 => 0,
3 => CoseKey::ECDH_ALGORITHM,
-1 => CoseKey::P_256_CURVE,
-2 => [0u8; 32],
-3 => [0u8; 32],
};
assert_eq!(
CoseKey::try_from(cbor_value),
Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM)
);
}
#[test]
fn test_cose_key_unknown_curve() {
let cbor_value = cbor_map! {
1 => CoseKey::EC2_KEY_TYPE,
3 => CoseKey::ECDH_ALGORITHM,
// unknown curve
-1 => 0,
-2 => [0u8; 32],
-3 => [0u8; 32],
};
assert_eq!(
CoseKey::try_from(cbor_value),
Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM)
);
}
#[test]
fn test_cose_key_wrong_length_x() {
let cbor_value = cbor_map! {
1 => CoseKey::EC2_KEY_TYPE,
3 => CoseKey::ECDH_ALGORITHM,
-1 => CoseKey::P_256_CURVE,
// wrong length
-2 => [0u8; 31],
-3 => [0u8; 32],
};
assert_eq!(
CoseKey::try_from(cbor_value),
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
);
}
#[test]
fn test_cose_key_wrong_length_y() {
let cbor_value = cbor_map! {
1 => CoseKey::EC2_KEY_TYPE,
3 => CoseKey::ECDH_ALGORITHM,
-1 => CoseKey::P_256_CURVE,
-2 => [0u8; 32],
// wrong length
-3 => [0u8; 33],
};
assert_eq!(
CoseKey::try_from(cbor_value),
Err(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
);
}
#[test]
fn test_from_into_cose_key_ecdh() {
let mut rng = ThreadRng256 {}; let mut rng = ThreadRng256 {};
let sk = crypto::ecdh::SecKey::gensk(&mut rng); let sk = crypto::ecdh::SecKey::gensk(&mut rng);
let pk = sk.genpk(); let pk = sk.genpk();
@@ -1437,6 +1585,15 @@ mod test {
assert_eq!(created_pk, Ok(pk)); assert_eq!(created_pk, Ok(pk));
} }
#[test]
fn test_into_cose_key_ecdsa() {
let mut rng = ThreadRng256 {};
let sk = crypto::ecdsa::SecKey::gensk(&mut rng);
let pk = sk.genpk();
let cose_key = CoseKey::from(pk);
assert_eq!(cose_key.algorithm, ES256_ALGORITHM);
}
#[test] #[test]
fn test_from_into_client_pin_sub_command() { fn test_from_into_client_pin_sub_command() {
let cbor_sub_command: cbor::Value = cbor_int!(0x01); let cbor_sub_command: cbor::Value = cbor_int!(0x01);

View File

@@ -33,7 +33,7 @@ use self::command::{
}; };
use self::config_command::process_config; use self::config_command::process_config;
use self::data_formats::{ use self::data_formats::{
AuthenticatorTransport, CredentialProtectionPolicy, GetAssertionHmacSecretInput, AuthenticatorTransport, CoseKey, CredentialProtectionPolicy, GetAssertionHmacSecretInput,
PackedAttestationStatement, PublicKeyCredentialDescriptor, PublicKeyCredentialParameter, PackedAttestationStatement, PublicKeyCredentialDescriptor, PublicKeyCredentialParameter,
PublicKeyCredentialSource, PublicKeyCredentialType, PublicKeyCredentialUserEntity, PublicKeyCredentialSource, PublicKeyCredentialType, PublicKeyCredentialUserEntity,
SignatureAlgorithm, SignatureAlgorithm,
@@ -555,11 +555,9 @@ where
} }
auth_data.extend(vec![0x00, credential_id.len() as u8]); auth_data.extend(vec![0x00, credential_id.len() as u8]);
auth_data.extend(&credential_id); auth_data.extend(&credential_id);
let cose_key = match pk.to_cose_key() { if !cbor::write(cbor::Value::from(CoseKey::from(pk)), &mut auth_data) {
Some(cose_key) => cose_key, return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR);
None => return Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR), }
};
auth_data.extend(cose_key);
if has_extension_output { if has_extension_output {
let hmac_secret_output = if use_hmac_extension { Some(true) } else { None }; let hmac_secret_output = if use_hmac_extension { Some(true) } else { None };
let min_pin_length_output = if min_pin_length { let min_pin_length_output = if min_pin_length {

View File

@@ -195,7 +195,7 @@ impl From<AuthenticatorClientPinResponse> for cbor::Value {
} = client_pin_response; } = client_pin_response;
cbor_map_options! { cbor_map_options! {
1 => key_agreement.map(|cose_key| cbor_map_btree!(cose_key.0)), 1 => key_agreement.map(cbor::Value::from),
2 => pin_token, 2 => pin_token,
3 => retries, 3 => retries,
} }