Skip to content

Commit

Permalink
add ecdsa verifier
Browse files Browse the repository at this point in the history
  • Loading branch information
Aconitin committed Apr 19, 2024
1 parent 30c9bf2 commit ad31b9a
Show file tree
Hide file tree
Showing 7 changed files with 457 additions and 0 deletions.
29 changes: 29 additions & 0 deletions identity_ecdsa_verifier/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[package]
name = "identity_ecdsa_verifier"
# TODO: description for consistency?
version = "0.1.0"
authors.workspace = true
edition.workspace = true
homepage.workspace = true
license.workspace = true
rust-version.workspace = true

[lints]
workspace = true

[dependencies]
identity_verification.workspace = true
k256 = { workspace = true, features = ["std", "ecdsa", "ecdsa-core"], optional = true }
p256 = { workspace = true, features = ["std", "ecdsa", "ecdsa-core"], optional = true }
signature.workspace = true

[dev-dependencies]
josekit.workspace = true
serde_json.workspace = true

[features]
default = ["es256", "es256k"]
# Enables the EcDSAJwsVerifier to verify JWS with alg = ES256.
es256 = ["dep:p256"]
# Enables the EcDSAJwsVerifier to verify JWS with alg = ES256K.
es256k = ["dep:k256"]
3 changes: 3 additions & 0 deletions identity_ecdsa_verifier/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# ECDSA Verifier

This crate implements a `JwsVerifier` capable of verifying EcDSA signatures with algorithms `ES256` and `ES256K`.
31 changes: 31 additions & 0 deletions identity_ecdsa_verifier/src/ecdsa_jws_verifier.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use identity_verification::jws::JwsAlgorithm;
use identity_verification::jws::JwsVerifier;
use identity_verification::jws::SignatureVerificationErrorKind;

/// An implementor of [`JwsVerifier`](identity_verification::jws::JwsVerifier)
/// that can handle a selection of EcDSA algorithms.
///
/// The following algorithms are supported, if the respective feature on the
/// crate is activated:
///
/// - [`JwsAlgorithm::ES256`](identity_verification::jws::JwsAlgorithm::ES256).
/// - [`JwsAlgorithm::ES256K`](identity_verification::jws::JwsAlgorithm::ES256K).
#[derive(Debug, Default)]
#[non_exhaustive]
pub struct EcDSAJwsVerifier {}

impl JwsVerifier for EcDSAJwsVerifier {
fn verify(
&self,
input: identity_verification::jws::VerificationInput,
public_key: &identity_verification::jwk::Jwk,
) -> Result<(), identity_verification::jws::SignatureVerificationError> {
match input.alg {
#[cfg(feature = "es256")]
JwsAlgorithm::ES256 => crate::Secp256R1Verifier::verify(&input, public_key),
#[cfg(feature = "es256k")]
JwsAlgorithm::ES256K => crate::Secp256K1Verifier::verify(&input, public_key),
_ => Err(SignatureVerificationErrorKind::UnsupportedAlg.into()),
}
}
}
26 changes: 26 additions & 0 deletions identity_ecdsa_verifier/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#![doc = include_str!("./../README.md")]
#![warn(
rust_2018_idioms,
unreachable_pub,
missing_docs,
rustdoc::missing_crate_level_docs,
rustdoc::broken_intra_doc_links,
rustdoc::private_intra_doc_links,
rustdoc::private_doc_tests,
clippy::missing_safety_doc
)]

mod ecdsa_jws_verifier;
#[cfg(feature = "es256k")]
mod secp256k1;
#[cfg(feature = "es256")]
mod secp256r1;

pub use ecdsa_jws_verifier::*;
#[cfg(feature = "es256k")]
pub use secp256k1::*;
#[cfg(feature = "es256")]
pub use secp256r1::*;

#[cfg(test)]
mod tests;
93 changes: 93 additions & 0 deletions identity_ecdsa_verifier/src/secp256k1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
use std::ops::Deref;

use identity_verification::jwk::JwkParamsEc;
use identity_verification::jws::SignatureVerificationError;
use identity_verification::jws::SignatureVerificationErrorKind;
use identity_verification::jwu::{self};
use k256::ecdsa::Signature;
use k256::ecdsa::VerifyingKey;
use k256::elliptic_curve::sec1::FromEncodedPoint;
use k256::elliptic_curve::subtle::CtOption;
use k256::EncodedPoint;
use k256::PublicKey;

/// A verifier that can handle the
/// [`JwsAlgorithm::ES256K`](identity_verification::jws::JwsAlgorithm::ES256K)
/// algorithm.
#[derive(Debug, Default)]
#[non_exhaustive]
pub struct Secp256K1Verifier {}

impl Secp256K1Verifier {
/// Verify a JWS signature secured with the
/// [`JwsAlgorithm::ES256K`](identity_verification::jws::JwsAlgorithm::ES256K)
/// algorithm.
///
/// This function is useful when one is building a
/// [`JwsVerifier`](identity_verification::jws::JwsVerifier) that
/// handles the
/// [`JwsAlgorithm::ES256K`](identity_verification::jws::JwsAlgorithm::ES256K)
/// in the same manner as the [`Secp256K1Verifier`] hence extending its
/// capabilities.
///
/// # Warning
///
/// This function does not check whether `alg = ES256K` in the protected
/// header. Callers are expected to assert this prior to calling the
/// function.
pub fn verify(
input: &identity_verification::jws::VerificationInput,
public_key: &identity_verification::jwk::Jwk,
) -> Result<(), SignatureVerificationError> {
// Obtain a K256 public key.
let params: &JwkParamsEc = public_key
.try_ec_params()
.map_err(|_| SignatureVerificationErrorKind::UnsupportedKeyType)?;

// Concatenate x and y coordinates as required by
// EncodedPoint::from_untagged_bytes.
let public_key_bytes = jwu::decode_b64(&params.x)
.map_err(|err| {
SignatureVerificationError::new(SignatureVerificationErrorKind::KeyDecodingFailure)
.with_source(err)
})?
.into_iter()
.chain(jwu::decode_b64(&params.y).map_err(|err| {
SignatureVerificationError::new(SignatureVerificationErrorKind::KeyDecodingFailure)
.with_source(err)
})?)
.collect();

// The JWK contains the uncompressed x and y coordinates, so we can create the
// encoded point directly without prefixing an SEC1 tag.
let encoded_point: EncodedPoint = EncodedPoint::from_untagged_bytes(&public_key_bytes);
let public_key: PublicKey = {
let opt_public_key: CtOption<PublicKey> = PublicKey::from_encoded_point(&encoded_point);
if opt_public_key.is_none().into() {
return Err(SignatureVerificationError::new(
SignatureVerificationErrorKind::KeyDecodingFailure,
));
} else {
opt_public_key.unwrap()
// opt_public_key.expect("we should have asserted that the
// contained value is some")
}
};

let verifying_key: VerifyingKey = VerifyingKey::from(public_key);

let signature: Signature =
Signature::try_from(input.decoded_signature.deref()).map_err(|err| {
SignatureVerificationError::new(SignatureVerificationErrorKind::InvalidSignature)
.with_source(err)
})?;

match signature::Verifier::verify(&verifying_key, &input.signing_input, &signature) {
Ok(()) => Ok(()),
Err(err) => Err(SignatureVerificationError::new(
SignatureVerificationErrorKind::InvalidSignature,
)
.with_source(err)),
}
}
}
93 changes: 93 additions & 0 deletions identity_ecdsa_verifier/src/secp256r1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
use std::ops::Deref;

use identity_verification::jwk::JwkParamsEc;
use identity_verification::jws::SignatureVerificationError;
use identity_verification::jws::SignatureVerificationErrorKind;
use identity_verification::jwu::{self};
use p256::ecdsa::Signature;
use p256::ecdsa::VerifyingKey;
use p256::elliptic_curve::sec1::FromEncodedPoint;
use p256::elliptic_curve::subtle::CtOption;
use p256::EncodedPoint;
use p256::PublicKey;

/// A verifier that can handle the
/// [`JwsAlgorithm::ES256`](identity_verification::jws::JwsAlgorithm::ES256)
/// algorithm.
#[derive(Debug, Default)]
#[non_exhaustive]
pub struct Secp256R1Verifier {}

impl Secp256R1Verifier {
/// Verify a JWS signature secured with the
/// [`JwsAlgorithm::ES256`](identity_verification::jws::JwsAlgorithm::ES256)
/// algorithm.
///
/// This function is useful when one is building a
/// [`JwsVerifier`](identity_verification::jws::JwsVerifier) that
/// handles the
/// [`JwsAlgorithm::ES256`](identity_verification::jws::JwsAlgorithm::ES256)
/// in the same manner as the [`Secp256R1Verifier`] hence extending its
/// capabilities.
///
/// # Warning
///
/// This function does not check whether `alg = ES256` in the protected
/// header. Callers are expected to assert this prior to calling the
/// function.
pub fn verify(
input: &identity_verification::jws::VerificationInput,
public_key: &identity_verification::jwk::Jwk,
) -> Result<(), SignatureVerificationError> {
// Obtain a P256 public key.
let params: &JwkParamsEc = public_key
.try_ec_params()
.map_err(|_| SignatureVerificationErrorKind::UnsupportedKeyType)?;

// Concatenate x and y coordinates as required by
// EncodedPoint::from_untagged_bytes.
let public_key_bytes = jwu::decode_b64(&params.x)
.map_err(|err| {
SignatureVerificationError::new(SignatureVerificationErrorKind::KeyDecodingFailure)
.with_source(err)
})?
.into_iter()
.chain(jwu::decode_b64(&params.y).map_err(|err| {
SignatureVerificationError::new(SignatureVerificationErrorKind::KeyDecodingFailure)
.with_source(err)
})?)
.collect();

// The JWK contains the uncompressed x and y coordinates, so we can create the
// encoded point directly without prefixing an SEC1 tag.
let encoded_point: EncodedPoint = EncodedPoint::from_untagged_bytes(&public_key_bytes);
let public_key: PublicKey = {
let opt_public_key: CtOption<PublicKey> = PublicKey::from_encoded_point(&encoded_point);
if opt_public_key.is_none().into() {
return Err(SignatureVerificationError::new(
SignatureVerificationErrorKind::KeyDecodingFailure,
));
} else {
opt_public_key.unwrap()
// opt_public_key.expect("we should have asserted that the
// contained value is some")
}
};

let verifying_key: VerifyingKey = VerifyingKey::from(public_key);

let signature: Signature =
Signature::try_from(input.decoded_signature.deref()).map_err(|err| {
SignatureVerificationError::new(SignatureVerificationErrorKind::InvalidSignature)
.with_source(err)
})?;

match signature::Verifier::verify(&verifying_key, &input.signing_input, &signature) {
Ok(()) => Ok(()),
Err(err) => Err(SignatureVerificationError::new(
SignatureVerificationErrorKind::InvalidSignature,
)
.with_source(err)),
}
}
}
Loading

0 comments on commit ad31b9a

Please sign in to comment.