Add support for ed25519 keys (#335)

This commit is contained in:
Egor Duda
2022-05-12 22:56:33 +03:00
parent f95ae1f5ab
commit 983bc5c5b2
6 changed files with 325 additions and 47 deletions

View File

@@ -27,7 +27,8 @@ use sk_cbor as cbor;
use sk_cbor::{cbor_array_vec, cbor_map, cbor_map_options, destructure_cbor_map};
// Used as the identifier for ECDSA in assertion signatures and COSE.
const ES256_ALGORITHM: i64 = -7;
pub const ES256_ALGORITHM: i64 = -7;
pub const EDDSA_ALGORITHM: i64 = -8;
// https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialrpentity
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -503,6 +504,8 @@ impl From<PackedAttestationStatement> for cbor::Value {
#[cfg_attr(feature = "fuzz", derive(Arbitrary))]
pub enum SignatureAlgorithm {
ES256 = ES256_ALGORITHM as isize,
#[cfg(feature = "with_ed25519")]
EDDSA = EDDSA_ALGORITHM as isize,
// This is the default for all numbers not covered above.
// Unknown types should be ignored, instead of returning errors.
Unknown = 0,
@@ -518,6 +521,8 @@ impl From<i64> for SignatureAlgorithm {
fn from(int: i64) -> Self {
match int {
ES256_ALGORITHM => SignatureAlgorithm::ES256,
#[cfg(feature = "with_ed25519")]
EDDSA_ALGORITHM => SignatureAlgorithm::EDDSA,
_ => SignatureAlgorithm::Unknown,
}
}
@@ -720,6 +725,8 @@ pub struct CoseKey {
x_bytes: [u8; ecdh::NBYTES],
y_bytes: [u8; ecdh::NBYTES],
algorithm: i64,
key_type: i64,
curve: i64,
}
impl CoseKey {
@@ -729,8 +736,12 @@ impl CoseKey {
const ECDH_ALGORITHM: i64 = -25;
// The parameter behind map key 1.
const EC2_KEY_TYPE: i64 = 2;
#[cfg(feature = "with_ed25519")]
const OKP_KEY_TYPE: i64 = 1;
// The parameter behind map key -1.
const P_256_CURVE: i64 = 1;
#[cfg(feature = "with_ed25519")]
const ED25519_CURVE: i64 = 6;
}
// This conversion accepts both ECDH and ECDSA.
@@ -776,6 +787,8 @@ impl TryFrom<cbor::Value> for CoseKey {
x_bytes: *array_ref![x_bytes.as_slice(), 0, ecdh::NBYTES],
y_bytes: *array_ref![y_bytes.as_slice(), 0, ecdh::NBYTES],
algorithm,
key_type,
curve,
})
}
}
@@ -786,12 +799,14 @@ impl From<CoseKey> for cbor::Value {
x_bytes,
y_bytes,
algorithm,
key_type,
curve,
} = cose_key;
cbor_map! {
1 => CoseKey::EC2_KEY_TYPE,
1 => key_type,
3 => algorithm,
-1 => CoseKey::P_256_CURVE,
-1 => curve,
-2 => x_bytes,
-3 => y_bytes,
}
@@ -807,6 +822,8 @@ impl From<ecdh::PubKey> for CoseKey {
x_bytes,
y_bytes,
algorithm: CoseKey::ECDH_ALGORITHM,
key_type: CoseKey::EC2_KEY_TYPE,
curve: CoseKey::P_256_CURVE,
}
}
}
@@ -820,6 +837,21 @@ impl From<ecdsa::PubKey> for CoseKey {
x_bytes,
y_bytes,
algorithm: ES256_ALGORITHM,
key_type: CoseKey::EC2_KEY_TYPE,
curve: CoseKey::P_256_CURVE,
}
}
}
#[cfg(feature = "with_ed25519")]
impl From<ed25519_dalek::PublicKey> for CoseKey {
fn from(pk: ed25519_dalek::PublicKey) -> Self {
CoseKey {
x_bytes: pk.to_bytes(),
y_bytes: [0u8; 32],
key_type: CoseKey::OKP_KEY_TYPE,
curve: CoseKey::ED25519_CURVE,
algorithm: EDDSA_ALGORITHM,
}
}
}
@@ -832,6 +864,8 @@ impl TryFrom<CoseKey> for ecdh::PubKey {
x_bytes,
y_bytes,
algorithm,
key_type,
curve,
} = cose_key;
// Since algorithm can be used for different COSE key types, we check
@@ -841,6 +875,9 @@ impl TryFrom<CoseKey> for ecdh::PubKey {
if algorithm != CoseKey::ECDH_ALGORITHM && algorithm != ES256_ALGORITHM {
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM);
}
if key_type != CoseKey::EC2_KEY_TYPE || curve != CoseKey::P_256_CURVE {
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM);
}
ecdh::PubKey::from_coordinates(&x_bytes, &y_bytes)
.ok_or(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
}
@@ -854,9 +891,11 @@ impl TryFrom<CoseKey> for ecdsa::PubKey {
x_bytes,
y_bytes,
algorithm,
key_type,
curve,
} = cose_key;
if algorithm != ES256_ALGORITHM {
if algorithm != ES256_ALGORITHM || key_type != CoseKey::EC2_KEY_TYPE || curve != CoseKey::P_256_CURVE {
return Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM);
}
ecdsa::PubKey::from_coordinates(&x_bytes, &y_bytes)
@@ -904,7 +943,11 @@ impl TryFrom<CoseSignature> for ecdsa::Signature {
match cose_signature.algorithm {
SignatureAlgorithm::ES256 => ecdsa::Signature::from_bytes(&cose_signature.bytes)
.ok_or(Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER),
SignatureAlgorithm::Unknown => Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM),
#[cfg(feature = "with_ed25519")]
SignatureAlgorithm::EDDSA =>
Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM),
SignatureAlgorithm::Unknown =>
Err(Ctap2StatusCode::CTAP2_ERR_UNSUPPORTED_ALGORITHM),
}
}
}
@@ -1564,6 +1607,13 @@ mod test {
let signature_algorithm = SignatureAlgorithm::from(alg_int);
assert_eq!(signature_algorithm, SignatureAlgorithm::ES256);
#[cfg(feature = "with_ed25519")]
{
let alg_int = SignatureAlgorithm::EDDSA as i64;
let signature_algorithm = SignatureAlgorithm::from(alg_int);
assert_eq!(signature_algorithm, SignatureAlgorithm::EDDSA);
}
let unknown_alg_int = -1;
let unknown_algorithm = SignatureAlgorithm::from(unknown_alg_int);
assert_eq!(unknown_algorithm, SignatureAlgorithm::Unknown);
@@ -1578,6 +1628,16 @@ mod test {
let created_cbor: cbor::Value = signature_algorithm.unwrap().into();
assert_eq!(created_cbor, cbor_signature_algorithm);
#[cfg(feature = "with_ed25519")]
{
let cbor_signature_algorithm: cbor::Value = cbor_int!(EDDSA_ALGORITHM);
let signature_algorithm = SignatureAlgorithm::try_from(cbor_signature_algorithm.clone());
let expected_signature_algorithm = SignatureAlgorithm::EDDSA;
assert_eq!(signature_algorithm, Ok(expected_signature_algorithm));
let created_cbor: cbor::Value = signature_algorithm.unwrap().into();
assert_eq!(created_cbor, cbor_signature_algorithm);
}
let cbor_unknown_algorithm: cbor::Value = cbor_int!(-1);
let unknown_algorithm = SignatureAlgorithm::try_from(cbor_unknown_algorithm);
let expected_unknown_algorithm = SignatureAlgorithm::Unknown;
@@ -1641,23 +1701,33 @@ mod test {
}
}
#[test]
fn test_from_into_public_key_credential_parameter() {
fn test_from_into_public_key_credential_parameter(alg_int: i64, signature_algorithm: SignatureAlgorithm) {
let cbor_credential_parameter = cbor_map! {
"alg" => ES256_ALGORITHM,
"alg" => alg_int,
"type" => "public-key",
};
let credential_parameter =
PublicKeyCredentialParameter::try_from(cbor_credential_parameter.clone());
let expected_credential_parameter = PublicKeyCredentialParameter {
cred_type: PublicKeyCredentialType::PublicKey,
alg: SignatureAlgorithm::ES256,
alg: signature_algorithm,
};
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_ecdsa_public_key_credential_parameter() {
test_from_into_public_key_credential_parameter(ES256_ALGORITHM, SignatureAlgorithm::ES256);
}
#[test]
#[cfg(feature = "with_ed25519")]
fn test_from_into_ed25519_public_key_credential_parameter() {
test_from_into_public_key_credential_parameter(EDDSA_ALGORITHM, SignatureAlgorithm::EDDSA);
}
#[test]
fn test_from_into_public_key_credential_descriptor() {
let cbor_credential_descriptor = cbor_map! {