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
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ 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"] }
p521 = { version = "0.13.0", optional = true, features = ["ecdsa"] }
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 @@ -66,7 +67,7 @@ criterion = { version = "0.4", default-features = false }
[features]
default = ["use_pem"]
use_pem = ["pem", "simple_asn1"]
rust_crypto = ["ed25519-dalek", "hmac", "p256", "p384", "rand", "rsa", "sha2"]
rust_crypto = ["ed25519-dalek", "hmac", "p256", "p384", "p521", "rand", "rsa", "sha2"]
aws_lc_rs = ["aws-lc-rs"]

[[bench]]
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ This library currently supports the following:
- PS512
- ES256
- ES384
- ES512
- EdDSA


Expand Down
7 changes: 5 additions & 2 deletions src/algorithms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,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 @@ -47,6 +47,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 @@ -75,6 +77,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 @@ -97,7 +100,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
137 changes: 137 additions & 0 deletions src/crypto/rust_crypto/ecdsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ use p256::ecdsa::{
use p384::ecdsa::{
Signature as Signature384, SigningKey as SigningKey384, VerifyingKey as VerifyingKey384,
};
use p521::ecdsa::{
Signature as Signature521, SigningKey as SigningKey521, VerifyingKey as VerifyingKey521,
};
use rsa::pkcs8::DecodePrivateKey;
use signature::{Error, Signer, Verifier};

Expand Down Expand Up @@ -85,3 +88,137 @@ 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 different API (no sign_recoverable, different PKCS8 extraction)
macro_rules! define_p521_signer {
($name:ident, $alg:expr) => {
pub struct $name(SigningKey521);

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

// Extract the raw 66-byte key from PKCS8 DER format
let pkcs8_der = encoding_key.inner();
let key_bytes = extract_p521_key_from_pkcs8(pkcs8_der)?;

// Verify correct length and convert to fixed-size array safely
if key_bytes.len() != 66 {
return Err(new_error(ErrorKind::InvalidEcdsaKey));
}

// Safe conversion using slice_as_array pattern
let mut key_array = [0u8; 66];
key_array.copy_from_slice(&key_bytes);

// Convert array to GenericArray reference using From trait
let field_bytes: &p521::FieldBytes = key_array.as_slice().try_into()
.map_err(|_| ErrorKind::InvalidEcdsaKey)?;

Ok(Self(
SigningKey521::from_bytes(field_bytes)
.map_err(|_| ErrorKind::InvalidEcdsaKey)?,
))
}
}

impl Signer<Vec<u8>> for $name {
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 $name {
fn algorithm(&self) -> Algorithm {
$alg
}
}
};
}

// P521 (ES512) verifier
macro_rules! define_p521_verifier {
($name:ident, $alg:expr) => {
pub struct $name(VerifyingKey521);

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

Ok(Self(
VerifyingKey521::from_sec1_bytes(decoding_key.as_bytes())
.map_err(|_| ErrorKind::InvalidEcdsaKey)?,
))
}
}

impl Verifier<Vec<u8>> for $name {
fn verify(&self, msg: &[u8], signature: &Vec<u8>) -> std::result::Result<(), Error> {
self.0
.verify(msg, &Signature521::from_slice(signature).map_err(Error::from_source)?)
.map_err(Error::from_source)?;
Ok(())
}
}

impl JwtVerifier for $name {
fn algorithm(&self) -> Algorithm {
$alg
}
}
};
}

define_p521_signer!(Es512Signer, Algorithm::ES512);
define_p521_verifier!(Es512Verifier, Algorithm::ES512);

/// Extract the 66-byte P-521 private key from PKCS8 DER format
///
/// P-521 keys in PKCS8 format have a different structure than the standard P256/P384:
/// PKCS8 ::= SEQUENCE {
/// version INTEGER,
/// algorithm AlgorithmIdentifier,
/// PrivateKey OCTET STRING
/// }
/// The PrivateKey octet string contains a DER-encoded ECPrivateKey SEQUENCE:
/// ECPrivateKey ::= SEQUENCE {
/// version INTEGER,
/// privateKey OCTET STRING (66 bytes for P-521)
/// }
fn extract_p521_key_from_pkcs8(pkcs8_der: &[u8]) -> Result<Vec<u8>> {
let asn1_blocks = simple_asn1::from_der(pkcs8_der)
.map_err(|_| ErrorKind::InvalidKeyFormat)?;

for block in asn1_blocks {
if let simple_asn1::ASN1Block::Sequence(_, entries) = block {
// The third element (index 2) should be the privateKey OCTET STRING
if entries.len() >= 3 {
if let simple_asn1::ASN1Block::OctetString(_, value) = &entries[2] {
// The value is DER-encoded and contains a SEQUENCE with the actual key
if let Ok(inner_blocks) = simple_asn1::from_der(value) {
for inner_block in inner_blocks {
if let simple_asn1::ASN1Block::Sequence(_, inner_entries) = inner_block {
// Look for the OCTET STRING within this sequence
for inner_entry in inner_entries {
if let simple_asn1::ASN1Block::OctetString(_, key_value) = inner_entry {
// This should be our 66-byte key
if key_value.len() == 66 {
return Ok(key_value.to_vec());
}
}
}
}
}
}
}
}
}
}

Err(new_error(ErrorKind::InvalidKeyFormat))
}
5 changes: 3 additions & 2 deletions src/decoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use crate::validation::{Validation, validate};
// Crypto
#[cfg(feature = "aws_lc_rs")]
use crate::crypto::aws_lc::{
ecdsa::{Es256Verifier, Es384Verifier},
ecdsa::{Es256Verifier, Es384Verifier, Es512Verifier},
eddsa::EdDSAVerifier,
hmac::{Hs256Verifier, Hs384Verifier, Hs512Verifier},
rsa::{
Expand All @@ -26,7 +26,7 @@ use crate::crypto::aws_lc::{
};
#[cfg(feature = "rust_crypto")]
use crate::crypto::rust_crypto::{
ecdsa::{Es256Verifier, Es384Verifier},
ecdsa::{Es256Verifier, Es384Verifier, Es512Verifier},
eddsa::EdDSAVerifier,
hmac::{Hs256Verifier, Hs384Verifier, Hs512Verifier},
rsa::{
Expand Down Expand Up @@ -326,6 +326,7 @@ pub fn jwt_verifier_factory(
Algorithm::HS512 => Box::new(Hs512Verifier::new(key)?) as Box<dyn JwtVerifier>,
Algorithm::ES256 => Box::new(Es256Verifier::new(key)?) as Box<dyn JwtVerifier>,
Algorithm::ES384 => Box::new(Es384Verifier::new(key)?) as Box<dyn JwtVerifier>,
Algorithm::ES512 => Box::new(Es512Verifier::new(key)?) as Box<dyn JwtVerifier>,
Algorithm::RS256 => Box::new(Rsa256Verifier::new(key)?) as Box<dyn JwtVerifier>,
Algorithm::RS384 => Box::new(Rsa384Verifier::new(key)?) as Box<dyn JwtVerifier>,
Algorithm::RS512 => Box::new(Rsa512Verifier::new(key)?) as Box<dyn JwtVerifier>,
Expand Down
5 changes: 3 additions & 2 deletions src/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use crate::serialization::{b64_encode, b64_encode_part};
// Crypto
#[cfg(feature = "aws_lc_rs")]
use crate::crypto::aws_lc::{
ecdsa::{Es256Signer, Es384Signer},
ecdsa::{Es256Signer, Es384Signer, Es512Signer},
eddsa::EdDSASigner,
hmac::{Hs256Signer, Hs384Signer, Hs512Signer},
rsa::{
Expand All @@ -26,7 +26,7 @@ use crate::crypto::aws_lc::{
};
#[cfg(feature = "rust_crypto")]
use crate::crypto::rust_crypto::{
ecdsa::{Es256Signer, Es384Signer},
ecdsa::{Es256Signer, Es384Signer, Es512Signer},
eddsa::EdDSASigner,
hmac::{Hs256Signer, Hs384Signer, Hs512Signer},
rsa::{
Expand Down Expand Up @@ -202,6 +202,7 @@ pub(crate) fn jwt_signer_factory(
Algorithm::HS512 => Box::new(Hs512Signer::new(key)?) as Box<dyn JwtSigner>,
Algorithm::ES256 => Box::new(Es256Signer::new(key)?) as Box<dyn JwtSigner>,
Algorithm::ES384 => Box::new(Es384Signer::new(key)?) as Box<dyn JwtSigner>,
Algorithm::ES512 => Box::new(Es512Signer::new(key)?) as Box<dyn JwtSigner>,
Algorithm::RS256 => Box::new(Rsa256Signer::new(key)?) as Box<dyn JwtSigner>,
Algorithm::RS384 => Box::new(Rsa384Signer::new(key)?) as Box<dyn JwtSigner>,
Algorithm::RS512 => Box::new(Rsa512Signer::new(key)?) as Box<dyn JwtSigner>,
Expand Down
4 changes: 4 additions & 0 deletions src/jwk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,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 @@ -219,6 +221,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 @@ -553,6 +556,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
69 changes: 69 additions & 0 deletions tests/ecdsa/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,3 +191,72 @@ fn ec_jwk_from_key() {
.unwrap()
);
}

// ES512 Tests
#[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-----
8 changes: 8 additions & 0 deletions tests/ecdsa/private_es512_key.pk8
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-----BEGIN PRIVATE KEY-----
MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIASBiTacIylSnlw2mi
We9ggHXP2wQgQVjWd4GkbPpK54c1hn3j4qHxNroYE3O5A1JkIHCBvMxAmDZexpZP
W0+vG1KhgYkDgYYABACPR/NfSO17emjwePAo/R95JsUGT1ensaDsIE+K86LaqF30
Ji/sg0eW+OOkQG4tVplFzVIDBftPA/gLzUdMslr2OABPthB0tgMnDU99O8+w0n5m
WbbZ9rs2T1WW6nkGHPH1aJ/4hNuz8HgZ8Tyg66k2ugwH+i9HDSMPK5gbxOF4K1x0
yA==
-----END PRIVATE KEY-----
6 changes: 6 additions & 0 deletions tests/ecdsa/public_es512_key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-----BEGIN PUBLIC KEY-----
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAaCCeANcYjPd0H0zkb2vLFU1sq6jR
QvFWkwPcDWx01fulP0shtSIkP8I3Zhzc2ZlzDWofyAqyRnL6yZDfDpd7RBYA9+Oq
gLsVkKeTheBb2nZbqLjf8/CD6HhrViHE3lNsYiTAaIcgCgUeI+8gilVa0OxRWenY
uC4pKoJaFHmjHhi6KTg=
-----END PUBLIC KEY-----
6 changes: 6 additions & 0 deletions tests/ecdsa/public_es512_key.pk8
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-----BEGIN PUBLIC KEY-----
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAj0fzX0jte3po8HjwKP0feSbFBk9X
p7Gg7CBPivOi2qhd9CYv7INHlvjjpEBuLVaZRc1SAwX7TwP4C81HTLJa9jgAT7YQ
dLYDJw1PfTvPsNJ+Zlm22fa7Nk9Vlup5Bhzx9Wif+ITbs/B4GfE8oOupNroMB/ov
Rw0jDyuYG8TheCtcdMg=
-----END PUBLIC KEY-----