Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add EDDSA support #408

Merged
merged 3 commits into from
Jan 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion fido-key-manager/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -579,7 +579,7 @@ async fn main() {
match &public_key.key {
COSEKeyType::EC_OKP(okp) => {
println!(" Octet key pair, curve {:?}", okp.curve);
println!(" X-coordinate: {}", hex::encode(okp.x));
println!(" X-coordinate: {}", hex::encode(&okp.x.0));
}
COSEKeyType::EC_EC2(ec) => {
println!(" Elliptic curve key, curve {:?}", ec.curve);
Expand Down
2 changes: 1 addition & 1 deletion webauthn-authenticator-rs/examples/authenticate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use webauthn_authenticator_rs::ui::{Cli, UiCallback};
use webauthn_authenticator_rs::AuthenticatorBackend;
use webauthn_rs_core::proto::RequestAuthenticationExtensions;
use webauthn_rs_core::WebauthnCore as Webauthn;
use webauthn_rs_proto::{AttestationConveyancePreference, COSEAlgorithm, UserVerificationPolicy};
use webauthn_rs_proto::{AttestationConveyancePreference, UserVerificationPolicy};

#[derive(Debug, clap::Parser)]
#[clap(about = "Register and authenticate test")]
Expand Down
10 changes: 5 additions & 5 deletions webauthn-rs-core/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -699,11 +699,13 @@ impl WebauthnCore {
// For us, we return the credential for the caller to persist.
// If trust failed, we have already returned an Err before this point.

// TODO: Associate the credentialId with the transport hints returned by calling
// Associate the credentialId with the transport hints returned by calling
// credential.response.getTransports(). This value SHOULD NOT be modified before or after
// storing it. It is RECOMMENDED to use this value to populate the transports of the
// allowCredentials option in future get() calls to help the client know how to find a
// suitable authenticator.
//
// Done as part of credential construction if the transports are valid/trusted.

Ok(credential)
}
Expand Down Expand Up @@ -3079,8 +3081,7 @@ mod tests {
false,
);
debug!("{:?}", result);
// Currently UNSUPPORTED as openssl doesn't have eddsa management utils that we need.
assert!(result.is_err());
assert!(result.is_ok());
}

#[test]
Expand Down Expand Up @@ -3123,8 +3124,7 @@ mod tests {
false,
);
debug!("{:?}", result);
// Currently UNSUPPORTED as openssl doesn't have eddsa management utils that we need.
assert!(result.is_err());
assert!(result.is_ok());
}

// ⚠️ Currently IGNORED as it appears that pixel 3a send INVALID attestation requests.
Expand Down
120 changes: 67 additions & 53 deletions webauthn-rs-core/src/crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ fn pkey_verify_signature(
.map_err(WebauthnError::OpenSSLError)?;
Ok(verifier)
}
COSEAlgorithm::EDDSA => {
sign::Verifier::new_without_digest(pkey).map_err(WebauthnError::OpenSSLError)
}
COSEAlgorithm::INSECURE_RS1 => {
error!("INSECURE SHA1 USAGE DETECTED");
Err(WebauthnError::CredentialInsecureCryptography)
Expand All @@ -48,11 +51,9 @@ fn pkey_verify_signature(
}
}?;

// ed25519/ed448 require oneshot mode.
verifier
.update(verification_data)
.map_err(WebauthnError::OpenSSLError)?;
verifier
.verify(signature)
.verify_oneshot(signature, verification_data)
.map_err(WebauthnError::OpenSSLError)
}

Expand Down Expand Up @@ -366,13 +367,13 @@ impl TryFrom<&serde_cbor_2::Value> for COSEKey {
// return it
Ok(cose_key)
} else if key_type == (COSEKeyTypeId::EC_OKP as i128) && (type_ == COSEAlgorithm::EDDSA) {
debug!(?d, "WebauthnError::COSEKeyInvalidType - EC_OKP");
// https://datatracker.ietf.org/doc/html/rfc8152#section-13.2

let curve_type_value = m
.get(&serde_cbor_2::Value::Integer(-1))
.ok_or(WebauthnError::COSEKeyInvalidCBORValue)?;
let curve_type = cbor_try_i128!(curve_type_value)?;
let curve = cbor_try_i128!(curve_type_value).and_then(EDDSACurve::try_from)?;

/*
// Some keys may return this as a string rather than int.
// The only key so far is the solokey and it's ed25519 support
Expand All @@ -395,26 +396,22 @@ impl TryFrom<&serde_cbor_2::Value> for COSEKey {
.ok_or(WebauthnError::COSEKeyInvalidCBORValue)?;
let x = cbor_try_bytes!(x_value)?;

if x.len() != 32 {
if x.len() != curve.coordinate_size() {
return Err(WebauthnError::COSEKeyEDDSAXInvalid);
}

let mut x_temp = [0; 32];
x_temp.copy_from_slice(x);

let cose_key = COSEKey {
type_,
key: COSEKeyType::EC_OKP(COSEOKPKey {
curve: EDDSACurve::try_from(curve_type)?,
x: x_temp,
curve,
x: x.to_vec().into(),
}),
};

// The rfc additionally states:
// " Applications MUST check that the curve and the key type are
// consistent and reject a key if they are not."
// this means feeding the values to openssl to validate them for us!

cose_key.validate()?;
// return it
Ok(cose_key)
Expand Down Expand Up @@ -508,43 +505,7 @@ impl COSEKey {
}

pub(crate) fn validate(&self) -> Result<(), WebauthnError> {
match &self.key {
COSEKeyType::EC_EC2(ec2k) => {
// Get the curve type
let curve = ec2k.curve.to_openssl_nid();
let ec_group =
ec::EcGroup::from_curve_name(curve).map_err(WebauthnError::OpenSSLError)?;

let xbn =
bn::BigNum::from_slice(ec2k.x.as_ref()).map_err(WebauthnError::OpenSSLError)?;
let ybn =
bn::BigNum::from_slice(ec2k.y.as_ref()).map_err(WebauthnError::OpenSSLError)?;

let ec_key = ec::EcKey::from_public_key_affine_coordinates(&ec_group, &xbn, &ybn)
.map_err(WebauthnError::OpenSSLError)?;

ec_key.check_key().map_err(WebauthnError::OpenSSLError)
}
COSEKeyType::RSA(rsak) => {
let nbn =
bn::BigNum::from_slice(rsak.n.as_ref()).map_err(WebauthnError::OpenSSLError)?;
let ebn = bn::BigNum::from_slice(&rsak.e).map_err(WebauthnError::OpenSSLError)?;

let _rsa_key = rsa::Rsa::from_public_components(nbn, ebn)
.map_err(WebauthnError::OpenSSLError)?;
/*
// Only applies to keys with private components!
rsa_key
.check_key()
.map_err(WebauthnError::OpenSSLError)
*/
Ok(())
}
COSEKeyType::EC_OKP(_edk) => {
warn!("ED25519 or ED448 keys are not currently supported");
Err(WebauthnError::COSEKeyEDUnsupported)
}
}
self.get_openssl_pkey().map(|_| ())
}

/// Retrieve the public key of this COSEKey as an OpenSSL structure
Expand Down Expand Up @@ -582,9 +543,14 @@ impl COSEKey {
let p = pkey::PKey::from_rsa(rsa_key).map_err(WebauthnError::OpenSSLError)?;
Ok(p)
}
_ => {
debug!("get_openssl_pkey");
Err(WebauthnError::COSEKeyInvalidType)
COSEKeyType::EC_OKP(edk) => {
let id = match &edk.curve {
EDDSACurve::ED25519 => pkey::Id::ED25519,
EDDSACurve::ED448 => pkey::Id::ED448,
};

pkey::PKey::public_key_from_raw_bytes(&edk.x.0, id)
.map_err(WebauthnError::OpenSSLError)
}
}
}
Expand Down Expand Up @@ -731,4 +697,52 @@ mod tests {
_ => panic!("Key should be parsed EC2 key"),
}
}

#[test]
fn cbor_ed25519() {
let hex_data = hex!(
"
A4 // Map - 4 elements
01 01 // 1: 1, ; kty: OKP key type
03 27 // 3: -8, ; alg: EDDSA signature algorithm
20 06 // -1: 6, ; crv: Ed25519 curve
21 58 20 43565027f918beb00257d112b903d15b93f5cbc7562dfc8458fbefd714546e3c // -2: x, ; Y-coordinate");
let val: Value = serde_cbor_2::from_slice(&hex_data).unwrap();
let key = COSEKey::try_from(&val).unwrap();
assert_eq!(key.type_, COSEAlgorithm::EDDSA);
match key.key {
COSEKeyType::EC_OKP(pkey) => {
assert_eq!(
pkey.x.as_ref(),
hex!("43565027f918beb00257d112b903d15b93f5cbc7562dfc8458fbefd714546e3c")
);
assert_eq!(pkey.curve, EDDSACurve::ED25519);
}
_ => panic!("Key should be parsed OKP key"),
}
}

#[test]
fn cbor_ed448() {
let hex_data = hex!(
"
A4 // Map - 4 elements
01 01 // 1: 1, ; kty: OKP key type
03 27 // 3: -8, ; alg: EDDSA signature algorithm
20 07 // -1: 7, ; crv: Ed448 curve
21 58 39 0c04658f79c3fd86c4b3d676057b76353126e9b905a7e204c07846c1a2ab3791b02fc5e9c6930345ea7bf8524b944220d4bd711c010c9b2a80 // -2: x, ; Y-coordinate");
let val: Value = serde_cbor_2::from_slice(&hex_data).unwrap();
let key = COSEKey::try_from(&val).unwrap();
assert_eq!(key.type_, COSEAlgorithm::EDDSA);
match key.key {
COSEKeyType::EC_OKP(pkey) => {
assert_eq!(
pkey.x.as_ref(),
hex!("0c04658f79c3fd86c4b3d676057b76353126e9b905a7e204c07846c1a2ab3791b02fc5e9c6930345ea7bf8524b944220d4bd711c010c9b2a80")
);
assert_eq!(pkey.curve, EDDSACurve::ED448);
}
_ => panic!("Key should be parsed OKP key"),
}
}
}
12 changes: 11 additions & 1 deletion webauthn-rs-core/src/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,16 @@ pub enum EDDSACurve {
ED448 = 7,
}

impl EDDSACurve {
/// Returns the size in bytes of the coordinate for the specified curve
pub(crate) fn coordinate_size(&self) -> usize {
match self {
Self::ED25519 => 32,
Self::ED448 => 57,
}
}
}

/// An ECDSACurve identifier. You probably will never need to alter
/// or use this value, as it is set inside the Credential for you.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
Expand Down Expand Up @@ -163,7 +173,7 @@ pub struct COSEOKPKey {
/// The curve that this key references.
pub curve: EDDSACurve,
/// The key's public X coordinate.
pub x: [u8; 32],
pub x: Base64UrlSafeData,
}

/// A COSE RSA PublicKey. This is a provided credential from a registered
Expand Down
2 changes: 0 additions & 2 deletions webauthn-rs-proto/src/attest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,6 @@ impl From<web_sys::PublicKeyCredential> for RegisterPublicKeyCredential {
fn from(data: web_sys::PublicKeyCredential) -> RegisterPublicKeyCredential {
use js_sys::Uint8Array;

// is_user_verifying_platform_authenticator_available

// AuthenticatorAttestationResponse has getTransports but web_sys isn't exposing it?
let transports = None;

Expand Down
2 changes: 2 additions & 0 deletions webauthn-rs-proto/src/cose.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ impl COSEAlgorithm {
COSEAlgorithm::RS256,
// COSEAlgorithm::RS384,
// COSEAlgorithm::RS512
// -- Testing required
// COSEAlgorithm::EDDSA,
]
}

Expand Down
Loading