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 alternate RustCrypto backend #58

Closed
wants to merge 1 commit into from
Closed
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
13 changes: 11 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@ all-features = true
maintenance = { status = "actively-developed" }

[features]
default = ["reqwest", "rustls-tls"]
default = ["reqwest", "rustls-tls", "ring"]
curl = ["oauth2/curl"]
reqwest = ["oauth2/reqwest"]
native-tls = ["oauth2/native-tls"]
rustls-tls = ["oauth2/rustls-tls"]
accept-rfc3339-timestamps = []
nightly = []
rustcrypto = ["rsa", "sha2", "hmac", "p256", "p384", "subtle", "dyn-clone"]

[dependencies]
base64 = "0.13"
Expand All @@ -33,7 +34,15 @@ itertools = "0.9"
log = "0.4"
oauth2 = { version = "4.1", default-features = false }
rand = "0.8"
ring = "0.16"
ring = { version = "0.16", optional = true }
# Using an old version as there are conflicts in the digest traits with p256 (even with 0.10)
hmac = { version = "0.11", optional = true }
rsa = { version = "0.5", features = ["alloc"], optional = true }
sha2 = { version = "0.9", default-features = false, optional = true }
p256 = { version = "0.9", optional = true }
p384 = { version = "0.9", optional = true }
subtle = { version = "2.4.1", optional = true }
dyn-clone = { version = "1.0", optional = true }
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
Expand Down
146 changes: 118 additions & 28 deletions src/core/crypto.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use num_bigint::{BigInt, Sign};
use ring::hmac;
use ring::rand::SecureRandom;
use ring::signature as ring_signature;
#[cfg(feature = "ring")]
use ring::{hmac, rand::SecureRandom, signature as ring_signature};

use crate::types::Base64UrlEncodedBytes;
use crate::{JsonWebKey, SignatureVerificationError, SigningError};
Expand All @@ -10,11 +9,13 @@ use super::{jwk::CoreJsonCurveType, CoreJsonWebKey, CoreJsonWebKeyType};

use std::ops::Deref;

pub fn sign_hmac(key: &[u8], hmac_alg: hmac::Algorithm, msg: &[u8]) -> hmac::Tag {
#[cfg(feature = "ring")]
pub fn sign_hmac(key: &[u8], hmac_alg: hmac::Algorithm, msg: &[u8]) -> Vec<u8> {
let signing_key = hmac::Key::new(hmac_alg, key);
hmac::sign(&signing_key, msg)
hmac::sign(&signing_key, msg).as_ref().into()
}

#[cfg(feature = "ring")]
pub fn verify_hmac(
key: &CoreJsonWebKey,
hmac_alg: hmac::Algorithm,
Expand All @@ -29,6 +30,7 @@ pub fn verify_hmac(
.map_err(|_| SignatureVerificationError::CryptoError("bad HMAC".to_string()))
}

#[cfg(feature = "ring")]
pub fn sign_rsa(
key: &ring_signature::RsaKeyPair,
padding_alg: &'static dyn ring_signature::RsaEncoding,
Expand Down Expand Up @@ -89,6 +91,7 @@ fn ec_public_key(
}
}

#[cfg(feature = "ring")]
pub fn verify_rsa_signature(
key: &CoreJsonWebKey,
params: &ring_signature::RsaParameters,
Expand All @@ -111,13 +114,38 @@ pub fn verify_rsa_signature(
.verify(params, msg, signature)
.map_err(|_| SignatureVerificationError::CryptoError("bad signature".to_string()))
}
#[cfg(feature = "rustcrypto")]
pub fn verify_rsa_signature(
key: &CoreJsonWebKey,
padding: rsa::PaddingScheme,
msg: &[u8],
signature: &[u8],
) -> Result<(), SignatureVerificationError> {
use rsa::PublicKey;

let (n, e) = rsa_public_key(key).map_err(SignatureVerificationError::InvalidKey)?;
// let's n and e as a big integers to prevent issues with leading zeros
// according to https://datatracker.ietf.org/doc/html/rfc7518#section-6.3.1.1
// `n` is alwasy unsigned (hence has sign plus)

let n_bigint = rsa::BigUint::from_bytes_be(n.deref());
let e_bigint = rsa::BigUint::from_bytes_be(e.deref());
let public_key = rsa::RsaPublicKey::new(n_bigint, e_bigint)
.map_err(|e| SignatureVerificationError::InvalidKey(format!("{}", e)))?;

public_key
.verify(padding, msg, signature)
.map_err(|_| SignatureVerificationError::CryptoError("bad signature".to_string()))
}

/// According to RFC5480, Section-2.2 implementations of Elliptic Curve Cryptography MUST support the uncompressed form.
/// The first octet of the octet string indicates whether the uncompressed or compressed form is used. For the uncompressed
/// form, the first octet has to be 0x04.
/// According to https://briansmith.org/rustdoc/ring/signature/index.html#ecdsa__fixed-details-fixed-length-pkcs11-style-ecdsa-signatures,
/// to recover the X and Y coordinates from an octet string, the Octet-String-To-Elliptic-Curve-Point Conversion
/// is used (Section 2.3.4 of https://www.secg.org/sec1-v2.pdf).

#[cfg(feature = "ring")]
pub fn verify_ec_signature(
key: &CoreJsonWebKey,
params: &'static ring_signature::EcdsaVerificationAlgorithm,
Expand All @@ -138,12 +166,56 @@ pub fn verify_ec_signature(
.verify(msg, signature)
.map_err(|_| SignatureVerificationError::CryptoError("EC Signature was wrong".to_string()))
}
#[cfg(feature = "rustcrypto")]
pub fn verify_ec_signature(
key: &CoreJsonWebKey,
msg: &[u8],
signature: &[u8],
) -> Result<(), SignatureVerificationError> {
use p256::ecdsa::signature::{Signature, Verifier};

let (x, y, crv) = ec_public_key(&key).map_err(SignatureVerificationError::InvalidKey)?;
if *crv == CoreJsonCurveType::P521 {
return Err(SignatureVerificationError::UnsupportedAlg(
"P521".to_string(),
));
}
let mut pk = vec![0x04];
pk.extend(x.deref());
pk.extend(y.deref());
let public_key = match *crv {
CoreJsonCurveType::P256 => p256::ecdsa::VerifyingKey::from_sec1_bytes(&pk),
CoreJsonCurveType::P384 => {
// p384::ecdsa::VerifyingKey::from_sec1_bytes(pk)
return Err(SignatureVerificationError::UnsupportedAlg(
"P384".to_string(),
));
}
CoreJsonCurveType::P521 => {
return Err(SignatureVerificationError::UnsupportedAlg(
"P521".to_string(),
));
}
}
.map_err(|e| SignatureVerificationError::InvalidKey(format!("{}", e)))?;
public_key
.verify(
msg,
&p256::ecdsa::Signature::from_bytes(signature).map_err(|_| {
SignatureVerificationError::CryptoError("Invalid signature".to_string())
})?,
)
.map_err(|_| SignatureVerificationError::CryptoError("EC Signature was wrong".to_string()))
}

#[cfg(test)]
mod tests {
use super::*;
use std::ops::Deref;

#[cfg(feature = "rustcrypto")]
use sha2::Digest;

use crate::{
core::{crypto::rsa_public_key, CoreJsonWebKey},
SignatureVerificationError,
Expand All @@ -166,32 +238,50 @@ mod tests {
}
)).unwrap();

// Old way of verifying the jwt, take the modulus directly form the JWK
let (n, e) = rsa_public_key(&key)
.map_err(SignatureVerificationError::InvalidKey)
.unwrap();

let public_key = ring_signature::RsaPublicKeyComponents {
n: n.deref(),
e: e.deref(),
};
// This fails, since ring expects the keys to have no leading zeros
assert! {
public_key
.verify(
#[cfg(feature = "ring")]
{
// Old way of verifying the jwt, take the modulus directly form the JWK
let (n, e) = rsa_public_key(&key)
.map_err(SignatureVerificationError::InvalidKey)
.unwrap();

let public_key = ring_signature::RsaPublicKeyComponents {
n: n.deref(),
e: e.deref(),
};
// This fails, since ring expects the keys to have no leading zeros
assert! {
public_key
.verify(
&ring_signature::RSA_PKCS1_2048_8192_SHA256,
msg.as_bytes(),
&signature,
).is_err()
};
// This should succeed as the function uses big-integers to actually harmonize parsing
assert! {
verify_rsa_signature(
&key,
&ring_signature::RSA_PKCS1_2048_8192_SHA256,
msg.as_bytes(),
&signature,
).is_err()
};
// This should succeed as the function uses big-integers to actually harmonize parsing
assert! {
verify_rsa_signature(
&key,
&ring_signature::RSA_PKCS1_2048_8192_SHA256,
msg.as_bytes(),
&signature,
).is_ok()
).is_ok()
}
}

#[cfg(feature = "rustcrypto")]
{
let mut hasher = sha2::Sha256::new();
hasher.update(msg);
let hash = hasher.finalize().to_vec();
assert! {
verify_rsa_signature(
&key,
rsa::PaddingScheme::new_pkcs1v15_sign(Some(rsa::Hash::SHA2_256)),
&hash,
&signature,
).is_ok()
}
}
}
}
Loading