Merge pull request #253 from kaczmarczyck/cose-api
COSE conversion for ECDH and ECDSA
This commit is contained in:
@@ -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);
|
||||||
|
|||||||
@@ -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")]
|
||||||
|
|||||||
@@ -317,7 +317,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()?;
|
||||||
@@ -423,8 +423,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() {
|
||||||
@@ -534,10 +534,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],
|
||||||
@@ -552,7 +557,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]),
|
||||||
|
|||||||
@@ -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 {
|
||||||
@@ -322,17 +325,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,
|
||||||
})
|
})
|
||||||
@@ -432,7 +435,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,
|
||||||
@@ -449,7 +452,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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -614,72 +617,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);
|
||||||
@@ -688,10 +661,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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -805,8 +857,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};
|
||||||
|
|
||||||
@@ -1129,7 +1181,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));
|
||||||
@@ -1203,7 +1255,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());
|
||||||
@@ -1257,7 +1309,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],
|
||||||
},
|
},
|
||||||
@@ -1322,7 +1374,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();
|
||||||
@@ -1331,6 +1479,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);
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ use self::command::{
|
|||||||
MAX_CREDENTIAL_COUNT_IN_LIST,
|
MAX_CREDENTIAL_COUNT_IN_LIST,
|
||||||
};
|
};
|
||||||
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,
|
||||||
@@ -534,11 +534,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 extensions_output = cbor_map_options! {
|
let extensions_output = cbor_map_options! {
|
||||||
|
|||||||
@@ -192,7 +192,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,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user