Skip to content
Open
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
10 changes: 6 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,17 @@ signature = { version = "2.2.0", features = ["std"] }
# For PEM decoding
pem = { version = "3", optional = true }
simple_asn1 = { version = "0.6", optional = true }
pkcs8 = { version = "0.10", optional = true, features = ["alloc"] }

# "aws_lc_rs" feature
aws-lc-rs = { version = "1.15.0", optional = true }

# "rust_crypto" feature
ed25519-dalek = { version = "2.1.1", optional = true, features = ["pkcs8"] }
hmac = { version = "0.12.1", optional = true }
p256 = { version = "0.13.2", optional = true, features = ["ecdsa"] }
p384 = { version = "0.13.0", optional = true, features = ["ecdsa"] }
p256 = { version = "0.13.2", optional = true, features = ["ecdsa", "pkcs8"] }
p384 = { version = "0.13.0", optional = true, features = ["ecdsa", "pkcs8"] }
p521 = { version = "0.13.0", optional = true, features = ["ecdsa", "pkcs8"] }
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a 0.14-rc8 published recently on crates.io, 0.13 is 2 years old. I think the new version might make the rust-crypto code easier?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0.14 is in heavy development for the last half year, based on rc history. Are you sure you want an rc dep in your crate?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p256 and p384 are the same story. I've tried to update them all to latest rc and tests were not failing, but I don't think this should be in scope for this particular PR.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Keats plz confirm if you want to update all p* crates (same maintainer) to rc version or we could skip it for now.

rand = { version = "0.8.5", optional = true, features = ["std"], default-features = false }
rsa = { version = "0.9.6", optional = true }
sha2 = { version = "0.10.7", optional = true, features = ["oid"] }
Expand All @@ -65,8 +67,8 @@ criterion = { version = "0.8", default-features = false }

[features]
default = ["use_pem"]
use_pem = ["pem", "simple_asn1"]
rust_crypto = ["ed25519-dalek", "hmac", "p256", "p384", "rand", "rsa", "sha2"]
use_pem = ["pem", "simple_asn1", "pkcs8"]
rust_crypto = ["ed25519-dalek", "hmac", "p256", "p384", "p521", "rand", "rsa", "sha2", "pkcs8", "simple_asn1"]
aws_lc_rs = ["aws-lc-rs"]

[[bench]]
Expand Down
7 changes: 5 additions & 2 deletions src/algorithms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ impl AlgorithmFamily {
Algorithm::PS384,
Algorithm::PS512,
],
Self::Ec => &[Algorithm::ES256, Algorithm::ES384],
Self::Ec => &[Algorithm::ES256, Algorithm::ES384, Algorithm::ES512],
Self::Ed => &[Algorithm::EdDSA],
}
}
Expand All @@ -52,6 +52,8 @@ pub enum Algorithm {
ES256,
/// ECDSA using SHA-384
ES384,
/// ECDSA using SHA-512
ES512,

/// RSASSA-PKCS1-v1_5 using SHA-256
RS256,
Expand Down Expand Up @@ -80,6 +82,7 @@ impl FromStr for Algorithm {
"HS512" => Ok(Algorithm::HS512),
"ES256" => Ok(Algorithm::ES256),
"ES384" => Ok(Algorithm::ES384),
"ES512" => Ok(Algorithm::ES512),
"RS256" => Ok(Algorithm::RS256),
"RS384" => Ok(Algorithm::RS384),
"PS256" => Ok(Algorithm::PS256),
Expand All @@ -102,7 +105,7 @@ impl Algorithm {
| Algorithm::PS256
| Algorithm::PS384
| Algorithm::PS512 => AlgorithmFamily::Rsa,
Algorithm::ES256 | Algorithm::ES384 => AlgorithmFamily::Ec,
Algorithm::ES256 | Algorithm::ES384 | Algorithm::ES512 => AlgorithmFamily::Ec,
Algorithm::EdDSA => AlgorithmFamily::Ed,
}
}
Expand Down
6 changes: 5 additions & 1 deletion src/crypto/aws_lc/ecdsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ use crate::{Algorithm, DecodingKey, EncodingKey};
use aws_lc_rs::rand::SystemRandom;
use aws_lc_rs::signature::{
ECDSA_P256_SHA256_FIXED, ECDSA_P256_SHA256_FIXED_SIGNING, ECDSA_P384_SHA384_FIXED,
ECDSA_P384_SHA384_FIXED_SIGNING, EcdsaKeyPair, VerificationAlgorithm,
ECDSA_P384_SHA384_FIXED_SIGNING, ECDSA_P521_SHA512_FIXED, ECDSA_P521_SHA512_FIXED_SIGNING,
EcdsaKeyPair, VerificationAlgorithm,
};
use signature::{Error, Signer, Verifier};

Expand Down Expand Up @@ -81,3 +82,6 @@ define_ecdsa_verifier!(Es256Verifier, Algorithm::ES256, ECDSA_P256_SHA256_FIXED)

define_ecdsa_signer!(Es384Signer, Algorithm::ES384, &ECDSA_P384_SHA384_FIXED_SIGNING);
define_ecdsa_verifier!(Es384Verifier, Algorithm::ES384, ECDSA_P384_SHA384_FIXED);

define_ecdsa_signer!(Es512Signer, Algorithm::ES512, &ECDSA_P521_SHA512_FIXED_SIGNING);
define_ecdsa_verifier!(Es512Verifier, Algorithm::ES512, ECDSA_P521_SHA512_FIXED);
5 changes: 4 additions & 1 deletion src/crypto/aws_lc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use aws_lc_rs::{
digest,
signature::{
self as aws_sig, ECDSA_P256_SHA256_FIXED_SIGNING, ECDSA_P384_SHA384_FIXED_SIGNING,
EcdsaKeyPair, KeyPair,
ECDSA_P521_SHA512_FIXED_SIGNING, EcdsaKeyPair, KeyPair,
},
};

Expand Down Expand Up @@ -33,6 +33,7 @@ fn extract_ec_public_key_coordinates(
let (signing_alg, curve, pub_elem_bytes) = match alg {
Algorithm::ES256 => (&ECDSA_P256_SHA256_FIXED_SIGNING, EllipticCurve::P256, 32),
Algorithm::ES384 => (&ECDSA_P384_SHA384_FIXED_SIGNING, EllipticCurve::P384, 48),
Algorithm::ES512 => (&ECDSA_P521_SHA512_FIXED_SIGNING, EllipticCurve::P521, 66),
_ => return Err(ErrorKind::InvalidEcdsaKey.into()),
};

Expand Down Expand Up @@ -64,6 +65,7 @@ fn new_signer(algorithm: &Algorithm, key: &EncodingKey) -> Result<Box<dyn JwtSig
Algorithm::HS512 => Box::new(hmac::Hs512Signer::new(key)?) as Box<dyn JwtSigner>,
Algorithm::ES256 => Box::new(ecdsa::Es256Signer::new(key)?) as Box<dyn JwtSigner>,
Algorithm::ES384 => Box::new(ecdsa::Es384Signer::new(key)?) as Box<dyn JwtSigner>,
Algorithm::ES512 => Box::new(ecdsa::Es512Signer::new(key)?) as Box<dyn JwtSigner>,
Algorithm::RS256 => Box::new(rsa::Rsa256Signer::new(key)?) as Box<dyn JwtSigner>,
Algorithm::RS384 => Box::new(rsa::Rsa384Signer::new(key)?) as Box<dyn JwtSigner>,
Algorithm::RS512 => Box::new(rsa::Rsa512Signer::new(key)?) as Box<dyn JwtSigner>,
Expand All @@ -86,6 +88,7 @@ fn new_verifier(
Algorithm::HS512 => Box::new(hmac::Hs512Verifier::new(key)?) as Box<dyn JwtVerifier>,
Algorithm::ES256 => Box::new(ecdsa::Es256Verifier::new(key)?) as Box<dyn JwtVerifier>,
Algorithm::ES384 => Box::new(ecdsa::Es384Verifier::new(key)?) as Box<dyn JwtVerifier>,
Algorithm::ES512 => Box::new(ecdsa::Es512Verifier::new(key)?) as Box<dyn JwtVerifier>,
Algorithm::RS256 => Box::new(rsa::Rsa256Verifier::new(key)?) as Box<dyn JwtVerifier>,
Algorithm::RS384 => Box::new(rsa::Rsa384Verifier::new(key)?) as Box<dyn JwtVerifier>,
Algorithm::RS512 => Box::new(rsa::Rsa512Verifier::new(key)?) as Box<dyn JwtVerifier>,
Expand Down
71 changes: 69 additions & 2 deletions src/crypto/rust_crypto/ecdsa.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
//! Implementations of the [`JwtSigner`] and [`JwtVerifier`] traits for the
//! ECDSA family of algorithms using RustCrypto

use crate::algorithms::AlgorithmFamily;
use crate::crypto::{JwtSigner, JwtVerifier};
use crate::errors::{ErrorKind, Result, new_error};
Expand All @@ -11,7 +10,10 @@ use p256::ecdsa::{
use p384::ecdsa::{
Signature as Signature384, SigningKey as SigningKey384, VerifyingKey as VerifyingKey384,
};
use rsa::pkcs8::DecodePrivateKey;
use p521::ecdsa::{
Signature as Signature521, SigningKey as SigningKey521, VerifyingKey as VerifyingKey521,
};
use pkcs8::DecodePrivateKey;
use signature::{Error, Signer, Verifier};

macro_rules! define_ecdsa_signer {
Expand Down Expand Up @@ -85,3 +87,68 @@ define_ecdsa_signer!(Es384Signer, Algorithm::ES384, SigningKey384);

define_ecdsa_verifier!(Es256Verifier, Algorithm::ES256, VerifyingKey256, Signature256);
define_ecdsa_verifier!(Es384Verifier, Algorithm::ES384, VerifyingKey384, Signature384);

// P521 (ES512) signer - uses sign() instead of sign_recoverable() since P521 doesn't support it
pub struct Es512Signer(SigningKey521);

impl Es512Signer {
pub(crate) fn new(encoding_key: &EncodingKey) -> Result<Self> {
if encoding_key.family() != AlgorithmFamily::Ec {
return Err(new_error(ErrorKind::InvalidKeyFormat));
}

// Use pkcs8 to parse the PKCS8 wrapper and extract the ECPrivateKey DER
use pkcs8::PrivateKeyInfo;
let private_key_info = PrivateKeyInfo::try_from(encoding_key.inner())
.map_err(|_| ErrorKind::InvalidKeyFormat)?;

// The private_key field contains the DER-encoded ECPrivateKey
let ec_private_key_der = private_key_info.private_key;

// Use simple_asn1 to parse the ECPrivateKey structure
use simple_asn1::ASN1Block;
let asn1_blocks =
simple_asn1::from_der(ec_private_key_der).map_err(|_| ErrorKind::InvalidKeyFormat)?;

// Find the OCTET STRING containing the 66-byte private key
for block in asn1_blocks {
if let ASN1Block::Sequence(_, entries) = block {
// ECPrivateKey ::= SEQUENCE {
// version INTEGER,
// privateKey OCTET STRING, // This is what we need (index 1)
// parameters [0] ECParameters OPTIONAL,
// publicKey [1] BIT STRING OPTIONAL
// }
if entries.len() >= 2 {
if let ASN1Block::OctetString(_, key_bytes) = &entries[1] {
if key_bytes.len() == 66 {
let mut field_bytes = p521::FieldBytes::default();
field_bytes.copy_from_slice(key_bytes);
return Ok(Self(
SigningKey521::from_bytes(&field_bytes)
.map_err(|_| ErrorKind::InvalidEcdsaKey)?,
));
}
}
}
}
}

Err(new_error(ErrorKind::InvalidKeyFormat))
}
}

impl Signer<Vec<u8>> for Es512Signer {
fn try_sign(&self, msg: &[u8]) -> std::result::Result<Vec<u8>, Error> {
let signature: Signature521 = self.0.sign(msg);
Ok(signature.to_vec())
}
}

impl JwtSigner for Es512Signer {
fn algorithm(&self) -> Algorithm {
Algorithm::ES512
}
}

define_ecdsa_verifier!(Es512Verifier, Algorithm::ES512, VerifyingKey521, Signature521);
46 changes: 45 additions & 1 deletion src/crypto/rust_crypto/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use ::rsa::{RsaPrivateKey, pkcs1::DecodeRsaPrivateKey, traits::PublicKeyParts};
use p256::{ecdsa::SigningKey as P256SigningKey, pkcs8::DecodePrivateKey};
use p256::ecdsa::SigningKey as P256SigningKey;
use p384::ecdsa::SigningKey as P384SigningKey;
use p521::ecdsa::SigningKey as P521SigningKey;
use pkcs8::DecodePrivateKey;
use sha2::{Digest, Sha256, Sha384, Sha512};

use crate::{
Expand Down Expand Up @@ -51,6 +53,46 @@ fn extract_ec_public_key_coordinates(
_ => Err(ErrorKind::InvalidEcdsaKey.into()),
}
}
Algorithm::ES512 => {
// Use pkcs8 to parse the PKCS8 wrapper
let private_key_info = pkcs8::PrivateKeyInfo::try_from(key_content)
.map_err(|_| ErrorKind::InvalidEcdsaKey)?;

// The private_key field contains the DER-encoded ECPrivateKey
let ec_private_key_der = private_key_info.private_key;

// Use simple_asn1 to parse the ECPrivateKey structure
use simple_asn1::ASN1Block;
let asn1_blocks = simple_asn1::from_der(ec_private_key_der)
.map_err(|_| ErrorKind::InvalidEcdsaKey)?;

// Find the OCTET STRING containing the 66-byte private key
for block in asn1_blocks {
if let ASN1Block::Sequence(_, entries) = block {
if entries.len() >= 2 {
if let ASN1Block::OctetString(_, key_bytes) = &entries[1] {
if key_bytes.len() == 66 {
let mut field_bytes = p521::FieldBytes::default();
field_bytes.copy_from_slice(key_bytes);
let signing_key = P521SigningKey::from_bytes(&field_bytes)
.map_err(|_| ErrorKind::InvalidEcdsaKey)?;
let public_key = p521::ecdsa::VerifyingKey::from(&signing_key);
let encoded = public_key.to_encoded_point(false);
return match encoded.coordinates() {
p521::elliptic_curve::sec1::Coordinates::Uncompressed {
x,
y,
} => Ok((EllipticCurve::P521, x.to_vec(), y.to_vec())),
_ => Err(ErrorKind::InvalidEcdsaKey.into()),
};
}
}
}
}
}

Err(ErrorKind::InvalidEcdsaKey.into())
}
_ => Err(ErrorKind::InvalidEcdsaKey.into()),
}
}
Expand All @@ -70,6 +112,7 @@ fn new_signer(algorithm: &Algorithm, key: &EncodingKey) -> Result<Box<dyn JwtSig
Algorithm::HS512 => Box::new(hmac::Hs512Signer::new(key)?) as Box<dyn JwtSigner>,
Algorithm::ES256 => Box::new(ecdsa::Es256Signer::new(key)?) as Box<dyn JwtSigner>,
Algorithm::ES384 => Box::new(ecdsa::Es384Signer::new(key)?) as Box<dyn JwtSigner>,
Algorithm::ES512 => Box::new(ecdsa::Es512Signer::new(key)?) as Box<dyn JwtSigner>,
Algorithm::RS256 => Box::new(rsa::Rsa256Signer::new(key)?) as Box<dyn JwtSigner>,
Algorithm::RS384 => Box::new(rsa::Rsa384Signer::new(key)?) as Box<dyn JwtSigner>,
Algorithm::RS512 => Box::new(rsa::Rsa512Signer::new(key)?) as Box<dyn JwtSigner>,
Expand All @@ -92,6 +135,7 @@ fn new_verifier(
Algorithm::HS512 => Box::new(hmac::Hs512Verifier::new(key)?) as Box<dyn JwtVerifier>,
Algorithm::ES256 => Box::new(ecdsa::Es256Verifier::new(key)?) as Box<dyn JwtVerifier>,
Algorithm::ES384 => Box::new(ecdsa::Es384Verifier::new(key)?) as Box<dyn JwtVerifier>,
Algorithm::ES512 => Box::new(ecdsa::Es512Verifier::new(key)?) as Box<dyn JwtVerifier>,
Algorithm::RS256 => Box::new(rsa::Rsa256Verifier::new(key)?) as Box<dyn JwtVerifier>,
Algorithm::RS384 => Box::new(rsa::Rsa384Verifier::new(key)?) as Box<dyn JwtVerifier>,
Algorithm::RS512 => Box::new(rsa::Rsa512Verifier::new(key)?) as Box<dyn JwtVerifier>,
Expand Down
5 changes: 5 additions & 0 deletions src/jwk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,8 @@ pub enum KeyAlgorithm {
ES256,
/// ECDSA using SHA-384
ES384,
/// ECDSA using SHA-512
ES512,

/// RSASSA-PKCS1-v1_5 using SHA-256
RS256,
Expand Down Expand Up @@ -207,6 +209,7 @@ impl FromStr for KeyAlgorithm {
"HS512" => Ok(KeyAlgorithm::HS512),
"ES256" => Ok(KeyAlgorithm::ES256),
"ES384" => Ok(KeyAlgorithm::ES384),
"ES512" => Ok(KeyAlgorithm::ES512),
"RS256" => Ok(KeyAlgorithm::RS256),
"RS384" => Ok(KeyAlgorithm::RS384),
"PS256" => Ok(KeyAlgorithm::PS256),
Expand Down Expand Up @@ -444,6 +447,7 @@ impl Jwk {
Algorithm::HS512 => KeyAlgorithm::HS512,
Algorithm::ES256 => KeyAlgorithm::ES256,
Algorithm::ES384 => KeyAlgorithm::ES384,
Algorithm::ES512 => KeyAlgorithm::ES512,
Algorithm::RS256 => KeyAlgorithm::RS256,
Algorithm::RS384 => KeyAlgorithm::RS384,
Algorithm::RS512 => KeyAlgorithm::RS512,
Expand Down Expand Up @@ -613,6 +617,7 @@ mod tests {
assert_eq!(key_alg_result, KeyAlgorithm::UNKNOWN_ALGORITHM);
}

#[cfg(any(feature = "rust_crypto", feature = "aws_lc_rs"))]
#[test]
#[wasm_bindgen_test]
fn check_thumbprint() {
Expand Down
70 changes: 70 additions & 0 deletions tests/ecdsa/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,3 +191,73 @@ fn ec_jwk_from_key() {
.unwrap()
);
}

// ES512 Tests
#[cfg(feature = "use_pem")]
#[test]
#[wasm_bindgen_test]
fn es512_round_trip_sign_verification_pem() {
let privkey_pem = include_bytes!("private_es512_key.pem");
let pubkey_pem = include_bytes!("public_es512_key.pem");

let encrypted =
sign(b"hello world", &EncodingKey::from_ec_pem(privkey_pem).unwrap(), Algorithm::ES512)
.unwrap();
let is_valid = verify(
&encrypted,
b"hello world",
&DecodingKey::from_ec_pem(pubkey_pem).unwrap(),
Algorithm::ES512,
)
.unwrap();
assert!(is_valid);
}

#[cfg(feature = "use_pem")]
#[test]
#[wasm_bindgen_test]
fn es512_round_trip_claim() {
let privkey_pem = include_bytes!("private_es512_key.pem");
let pubkey_pem = include_bytes!("public_es512_key.pem");
let my_claims = Claims {
sub: "es512@example.com".to_string(),
company: "ACME".to_string(),
exp: OffsetDateTime::now_utc().unix_timestamp() + 10000,
};
let token = encode(
&Header::new(Algorithm::ES512),
&my_claims,
&EncodingKey::from_ec_pem(privkey_pem).unwrap(),
)
.unwrap();
let token_data = decode::<Claims>(
&token,
&DecodingKey::from_ec_pem(pubkey_pem).unwrap(),
&Validation::new(Algorithm::ES512),
)
.unwrap();
assert_eq!(my_claims, token_data.claims);
}

#[cfg(feature = "use_pem")]
#[test]
#[wasm_bindgen_test]
fn es512_sign_and_verify() {
let privkey_pem = include_bytes!("private_es512_key.pem");
let pubkey_pem = include_bytes!("public_es512_key.pem");
let message = b"test message for ES512";

// Sign the message
let encrypted =
sign(message, &EncodingKey::from_ec_pem(privkey_pem).unwrap(), Algorithm::ES512).unwrap();

// Verify the signature
let is_valid = verify(
&encrypted,
message,
&DecodingKey::from_ec_pem(pubkey_pem).unwrap(),
Algorithm::ES512,
)
.unwrap();
assert!(is_valid);
}
8 changes: 8 additions & 0 deletions tests/ecdsa/private_es512_key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-----BEGIN PRIVATE KEY-----
MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIASpHRYZx6l+CIFdI2
9MO1GGnfy4eyWXApZLmQUm9nbZCX2MDY6VB63umkLii3h+ng899S2GNqpWpqK4oc
TOwlL16hgYkDgYYABABoIJ4A1xiM93QfTORva8sVTWyrqNFC8VaTA9wNbHTV+6U/
SyG1IiQ/wjdmHNzZmXMNah/ICrJGcvrJkN8Ol3tEFgD346qAuxWQp5OF4Fvadluo
uN/z8IPoeGtWIcTeU2xiJMBohyAKBR4j7yCKVVrQ7FFZ6di4LikqgloUeaMeGLop
OA==
-----END PRIVATE KEY-----
Loading