Skip to content

Commit 9d6a7b3

Browse files
committed
feat(es512): add ES512 algorithm support with P-521 elliptic curve
1 parent 9934c7f commit 9d6a7b3

12 files changed

Lines changed: 238 additions & 12 deletions

File tree

Cargo.toml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,17 @@ signature = { version = "2.2.0", features = ["std"] }
3131
# For PEM decoding
3232
pem = { version = "3", optional = true }
3333
simple_asn1 = { version = "0.6", optional = true }
34+
pkcs8 = { version = "0.10", optional = true, features = ["alloc"] }
3435

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

3839
# "rust_crypto" feature
3940
ed25519-dalek = { version = "2.1.1", optional = true, features = ["pkcs8"] }
4041
hmac = { version = "0.12.1", optional = true }
41-
p256 = { version = "0.13.2", optional = true, features = ["ecdsa"] }
42-
p384 = { version = "0.13.0", optional = true, features = ["ecdsa"] }
42+
p256 = { version = "0.13.2", optional = true, features = ["ecdsa", "pkcs8"] }
43+
p384 = { version = "0.13.0", optional = true, features = ["ecdsa", "pkcs8"] }
44+
p521 = { version = "0.13.0", optional = true, features = ["ecdsa", "pkcs8"] }
4345
rand = { version = "0.8.5", optional = true, features = ["std"], default-features = false }
4446
rsa = { version = "0.9.6", optional = true }
4547
sha2 = { version = "0.10.7", optional = true, features = ["oid"] }
@@ -65,8 +67,8 @@ criterion = { version = "0.8", default-features = false }
6567

6668
[features]
6769
default = ["use_pem"]
68-
use_pem = ["pem", "simple_asn1"]
69-
rust_crypto = ["ed25519-dalek", "hmac", "p256", "p384", "rand", "rsa", "sha2"]
70+
use_pem = ["pem", "simple_asn1", "pkcs8"]
71+
rust_crypto = ["ed25519-dalek", "hmac", "p256", "p384", "p521", "rand", "rsa", "sha2", "pkcs8", "simple_asn1"]
7072
aws_lc_rs = ["aws-lc-rs"]
7173

7274
[[bench]]

src/algorithms.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ impl AlgorithmFamily {
3030
Algorithm::PS384,
3131
Algorithm::PS512,
3232
],
33-
Self::Ec => &[Algorithm::ES256, Algorithm::ES384],
33+
Self::Ec => &[Algorithm::ES256, Algorithm::ES384, Algorithm::ES512],
3434
Self::Ed => &[Algorithm::EdDSA],
3535
}
3636
}
@@ -52,6 +52,8 @@ pub enum Algorithm {
5252
ES256,
5353
/// ECDSA using SHA-384
5454
ES384,
55+
/// ECDSA using SHA-512
56+
ES512,
5557

5658
/// RSASSA-PKCS1-v1_5 using SHA-256
5759
RS256,
@@ -80,6 +82,7 @@ impl FromStr for Algorithm {
8082
"HS512" => Ok(Algorithm::HS512),
8183
"ES256" => Ok(Algorithm::ES256),
8284
"ES384" => Ok(Algorithm::ES384),
85+
"ES512" => Ok(Algorithm::ES512),
8386
"RS256" => Ok(Algorithm::RS256),
8487
"RS384" => Ok(Algorithm::RS384),
8588
"PS256" => Ok(Algorithm::PS256),
@@ -102,7 +105,7 @@ impl Algorithm {
102105
| Algorithm::PS256
103106
| Algorithm::PS384
104107
| Algorithm::PS512 => AlgorithmFamily::Rsa,
105-
Algorithm::ES256 | Algorithm::ES384 => AlgorithmFamily::Ec,
108+
Algorithm::ES256 | Algorithm::ES384 | Algorithm::ES512 => AlgorithmFamily::Ec,
106109
Algorithm::EdDSA => AlgorithmFamily::Ed,
107110
}
108111
}

src/crypto/aws_lc/ecdsa.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ use crate::{Algorithm, DecodingKey, EncodingKey};
88
use aws_lc_rs::rand::SystemRandom;
99
use aws_lc_rs::signature::{
1010
ECDSA_P256_SHA256_FIXED, ECDSA_P256_SHA256_FIXED_SIGNING, ECDSA_P384_SHA384_FIXED,
11-
ECDSA_P384_SHA384_FIXED_SIGNING, EcdsaKeyPair, VerificationAlgorithm,
11+
ECDSA_P384_SHA384_FIXED_SIGNING, ECDSA_P521_SHA512_FIXED, ECDSA_P521_SHA512_FIXED_SIGNING,
12+
EcdsaKeyPair, VerificationAlgorithm,
1213
};
1314
use signature::{Error, Signer, Verifier};
1415

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

8283
define_ecdsa_signer!(Es384Signer, Algorithm::ES384, &ECDSA_P384_SHA384_FIXED_SIGNING);
8384
define_ecdsa_verifier!(Es384Verifier, Algorithm::ES384, ECDSA_P384_SHA384_FIXED);
85+
86+
define_ecdsa_signer!(Es512Signer, Algorithm::ES512, &ECDSA_P521_SHA512_FIXED_SIGNING);
87+
define_ecdsa_verifier!(Es512Verifier, Algorithm::ES512, ECDSA_P521_SHA512_FIXED);

src/crypto/aws_lc/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use aws_lc_rs::{
22
digest,
33
signature::{
44
self as aws_sig, ECDSA_P256_SHA256_FIXED_SIGNING, ECDSA_P384_SHA384_FIXED_SIGNING,
5-
EcdsaKeyPair, KeyPair,
5+
ECDSA_P521_SHA512_FIXED_SIGNING, EcdsaKeyPair, KeyPair,
66
},
77
};
88

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

@@ -64,6 +65,7 @@ fn new_signer(algorithm: &Algorithm, key: &EncodingKey) -> Result<Box<dyn JwtSig
6465
Algorithm::HS512 => Box::new(hmac::Hs512Signer::new(key)?) as Box<dyn JwtSigner>,
6566
Algorithm::ES256 => Box::new(ecdsa::Es256Signer::new(key)?) as Box<dyn JwtSigner>,
6667
Algorithm::ES384 => Box::new(ecdsa::Es384Signer::new(key)?) as Box<dyn JwtSigner>,
68+
Algorithm::ES512 => Box::new(ecdsa::Es512Signer::new(key)?) as Box<dyn JwtSigner>,
6769
Algorithm::RS256 => Box::new(rsa::Rsa256Signer::new(key)?) as Box<dyn JwtSigner>,
6870
Algorithm::RS384 => Box::new(rsa::Rsa384Signer::new(key)?) as Box<dyn JwtSigner>,
6971
Algorithm::RS512 => Box::new(rsa::Rsa512Signer::new(key)?) as Box<dyn JwtSigner>,
@@ -86,6 +88,7 @@ fn new_verifier(
8688
Algorithm::HS512 => Box::new(hmac::Hs512Verifier::new(key)?) as Box<dyn JwtVerifier>,
8789
Algorithm::ES256 => Box::new(ecdsa::Es256Verifier::new(key)?) as Box<dyn JwtVerifier>,
8890
Algorithm::ES384 => Box::new(ecdsa::Es384Verifier::new(key)?) as Box<dyn JwtVerifier>,
91+
Algorithm::ES512 => Box::new(ecdsa::Es512Verifier::new(key)?) as Box<dyn JwtVerifier>,
8992
Algorithm::RS256 => Box::new(rsa::Rsa256Verifier::new(key)?) as Box<dyn JwtVerifier>,
9093
Algorithm::RS384 => Box::new(rsa::Rsa384Verifier::new(key)?) as Box<dyn JwtVerifier>,
9194
Algorithm::RS512 => Box::new(rsa::Rsa512Verifier::new(key)?) as Box<dyn JwtVerifier>,

src/crypto/rust_crypto/ecdsa.rs

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
//! Implementations of the [`JwtSigner`] and [`JwtVerifier`] traits for the
22
//! ECDSA family of algorithms using RustCrypto
3-
43
use crate::algorithms::AlgorithmFamily;
54
use crate::crypto::{JwtSigner, JwtVerifier};
65
use crate::errors::{ErrorKind, Result, new_error};
@@ -11,7 +10,10 @@ use p256::ecdsa::{
1110
use p384::ecdsa::{
1211
Signature as Signature384, SigningKey as SigningKey384, VerifyingKey as VerifyingKey384,
1312
};
14-
use rsa::pkcs8::DecodePrivateKey;
13+
use p521::ecdsa::{
14+
Signature as Signature521, SigningKey as SigningKey521, VerifyingKey as VerifyingKey521,
15+
};
16+
use pkcs8::DecodePrivateKey;
1517
use signature::{Error, Signer, Verifier};
1618

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

8688
define_ecdsa_verifier!(Es256Verifier, Algorithm::ES256, VerifyingKey256, Signature256);
8789
define_ecdsa_verifier!(Es384Verifier, Algorithm::ES384, VerifyingKey384, Signature384);
90+
91+
// P521 (ES512) signer - uses sign() instead of sign_recoverable() since P521 doesn't support it
92+
pub struct Es512Signer(SigningKey521);
93+
94+
impl Es512Signer {
95+
pub(crate) fn new(encoding_key: &EncodingKey) -> Result<Self> {
96+
if encoding_key.family() != AlgorithmFamily::Ec {
97+
return Err(new_error(ErrorKind::InvalidKeyFormat));
98+
}
99+
100+
// Use pkcs8 to parse the PKCS8 wrapper and extract the ECPrivateKey DER
101+
use pkcs8::PrivateKeyInfo;
102+
let private_key_info = PrivateKeyInfo::try_from(encoding_key.inner())
103+
.map_err(|_| ErrorKind::InvalidKeyFormat)?;
104+
105+
// The private_key field contains the DER-encoded ECPrivateKey
106+
let ec_private_key_der = private_key_info.private_key;
107+
108+
// Use simple_asn1 to parse the ECPrivateKey structure
109+
use simple_asn1::ASN1Block;
110+
let asn1_blocks =
111+
simple_asn1::from_der(ec_private_key_der).map_err(|_| ErrorKind::InvalidKeyFormat)?;
112+
113+
// Find the OCTET STRING containing the 66-byte private key
114+
for block in asn1_blocks {
115+
if let ASN1Block::Sequence(_, entries) = block {
116+
// ECPrivateKey ::= SEQUENCE {
117+
// version INTEGER,
118+
// privateKey OCTET STRING, // This is what we need (index 1)
119+
// parameters [0] ECParameters OPTIONAL,
120+
// publicKey [1] BIT STRING OPTIONAL
121+
// }
122+
if entries.len() >= 2 {
123+
if let ASN1Block::OctetString(_, key_bytes) = &entries[1] {
124+
if key_bytes.len() == 66 {
125+
let mut field_bytes = p521::FieldBytes::default();
126+
field_bytes.copy_from_slice(key_bytes);
127+
return Ok(Self(
128+
SigningKey521::from_bytes(&field_bytes)
129+
.map_err(|_| ErrorKind::InvalidEcdsaKey)?,
130+
));
131+
}
132+
}
133+
}
134+
}
135+
}
136+
137+
Err(new_error(ErrorKind::InvalidKeyFormat))
138+
}
139+
}
140+
141+
impl Signer<Vec<u8>> for Es512Signer {
142+
fn try_sign(&self, msg: &[u8]) -> std::result::Result<Vec<u8>, Error> {
143+
let signature: Signature521 = self.0.sign(msg);
144+
Ok(signature.to_vec())
145+
}
146+
}
147+
148+
impl JwtSigner for Es512Signer {
149+
fn algorithm(&self) -> Algorithm {
150+
Algorithm::ES512
151+
}
152+
}
153+
154+
define_ecdsa_verifier!(Es512Verifier, Algorithm::ES512, VerifyingKey521, Signature521);

src/crypto/rust_crypto/mod.rs

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use ::rsa::{RsaPrivateKey, pkcs1::DecodeRsaPrivateKey, traits::PublicKeyParts};
2-
use p256::{ecdsa::SigningKey as P256SigningKey, pkcs8::DecodePrivateKey};
2+
use p256::ecdsa::SigningKey as P256SigningKey;
33
use p384::ecdsa::SigningKey as P384SigningKey;
4+
use p521::ecdsa::SigningKey as P521SigningKey;
5+
use pkcs8::DecodePrivateKey;
46
use sha2::{Digest, Sha256, Sha384, Sha512};
57

68
use crate::{
@@ -51,6 +53,46 @@ fn extract_ec_public_key_coordinates(
5153
_ => Err(ErrorKind::InvalidEcdsaKey.into()),
5254
}
5355
}
56+
Algorithm::ES512 => {
57+
// Use pkcs8 to parse the PKCS8 wrapper
58+
let private_key_info = pkcs8::PrivateKeyInfo::try_from(key_content)
59+
.map_err(|_| ErrorKind::InvalidEcdsaKey)?;
60+
61+
// The private_key field contains the DER-encoded ECPrivateKey
62+
let ec_private_key_der = private_key_info.private_key;
63+
64+
// Use simple_asn1 to parse the ECPrivateKey structure
65+
use simple_asn1::ASN1Block;
66+
let asn1_blocks = simple_asn1::from_der(ec_private_key_der)
67+
.map_err(|_| ErrorKind::InvalidEcdsaKey)?;
68+
69+
// Find the OCTET STRING containing the 66-byte private key
70+
for block in asn1_blocks {
71+
if let ASN1Block::Sequence(_, entries) = block {
72+
if entries.len() >= 2 {
73+
if let ASN1Block::OctetString(_, key_bytes) = &entries[1] {
74+
if key_bytes.len() == 66 {
75+
let mut field_bytes = p521::FieldBytes::default();
76+
field_bytes.copy_from_slice(key_bytes);
77+
let signing_key = P521SigningKey::from_bytes(&field_bytes)
78+
.map_err(|_| ErrorKind::InvalidEcdsaKey)?;
79+
let public_key = p521::ecdsa::VerifyingKey::from(&signing_key);
80+
let encoded = public_key.to_encoded_point(false);
81+
return match encoded.coordinates() {
82+
p521::elliptic_curve::sec1::Coordinates::Uncompressed {
83+
x,
84+
y,
85+
} => Ok((EllipticCurve::P521, x.to_vec(), y.to_vec())),
86+
_ => Err(ErrorKind::InvalidEcdsaKey.into()),
87+
};
88+
}
89+
}
90+
}
91+
}
92+
}
93+
94+
Err(ErrorKind::InvalidEcdsaKey.into())
95+
}
5496
_ => Err(ErrorKind::InvalidEcdsaKey.into()),
5597
}
5698
}
@@ -70,6 +112,7 @@ fn new_signer(algorithm: &Algorithm, key: &EncodingKey) -> Result<Box<dyn JwtSig
70112
Algorithm::HS512 => Box::new(hmac::Hs512Signer::new(key)?) as Box<dyn JwtSigner>,
71113
Algorithm::ES256 => Box::new(ecdsa::Es256Signer::new(key)?) as Box<dyn JwtSigner>,
72114
Algorithm::ES384 => Box::new(ecdsa::Es384Signer::new(key)?) as Box<dyn JwtSigner>,
115+
Algorithm::ES512 => Box::new(ecdsa::Es512Signer::new(key)?) as Box<dyn JwtSigner>,
73116
Algorithm::RS256 => Box::new(rsa::Rsa256Signer::new(key)?) as Box<dyn JwtSigner>,
74117
Algorithm::RS384 => Box::new(rsa::Rsa384Signer::new(key)?) as Box<dyn JwtSigner>,
75118
Algorithm::RS512 => Box::new(rsa::Rsa512Signer::new(key)?) as Box<dyn JwtSigner>,
@@ -92,6 +135,7 @@ fn new_verifier(
92135
Algorithm::HS512 => Box::new(hmac::Hs512Verifier::new(key)?) as Box<dyn JwtVerifier>,
93136
Algorithm::ES256 => Box::new(ecdsa::Es256Verifier::new(key)?) as Box<dyn JwtVerifier>,
94137
Algorithm::ES384 => Box::new(ecdsa::Es384Verifier::new(key)?) as Box<dyn JwtVerifier>,
138+
Algorithm::ES512 => Box::new(ecdsa::Es512Verifier::new(key)?) as Box<dyn JwtVerifier>,
95139
Algorithm::RS256 => Box::new(rsa::Rsa256Verifier::new(key)?) as Box<dyn JwtVerifier>,
96140
Algorithm::RS384 => Box::new(rsa::Rsa384Verifier::new(key)?) as Box<dyn JwtVerifier>,
97141
Algorithm::RS512 => Box::new(rsa::Rsa512Verifier::new(key)?) as Box<dyn JwtVerifier>,

src/jwk.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,8 @@ pub enum KeyAlgorithm {
164164
ES256,
165165
/// ECDSA using SHA-384
166166
ES384,
167+
/// ECDSA using SHA-512
168+
ES512,
167169

168170
/// RSASSA-PKCS1-v1_5 using SHA-256
169171
RS256,
@@ -207,6 +209,7 @@ impl FromStr for KeyAlgorithm {
207209
"HS512" => Ok(KeyAlgorithm::HS512),
208210
"ES256" => Ok(KeyAlgorithm::ES256),
209211
"ES384" => Ok(KeyAlgorithm::ES384),
212+
"ES512" => Ok(KeyAlgorithm::ES512),
210213
"RS256" => Ok(KeyAlgorithm::RS256),
211214
"RS384" => Ok(KeyAlgorithm::RS384),
212215
"PS256" => Ok(KeyAlgorithm::PS256),
@@ -444,6 +447,7 @@ impl Jwk {
444447
Algorithm::HS512 => KeyAlgorithm::HS512,
445448
Algorithm::ES256 => KeyAlgorithm::ES256,
446449
Algorithm::ES384 => KeyAlgorithm::ES384,
450+
Algorithm::ES512 => KeyAlgorithm::ES512,
447451
Algorithm::RS256 => KeyAlgorithm::RS256,
448452
Algorithm::RS384 => KeyAlgorithm::RS384,
449453
Algorithm::RS512 => KeyAlgorithm::RS512,
@@ -531,7 +535,7 @@ impl Jwk {
531535
}
532536
EllipticCurve::Ed25519 => {
533537
format!(
534-
r#"{{"crv":{},"kty":{},"x":"{}"}}"#,
538+
r#"{{crv:{},"kty":{},"x":"{}"}}"#,
535539
serde_json::to_string(&a.curve).unwrap(),
536540
serde_json::to_string(&a.key_type).unwrap(),
537541
a.x,
@@ -613,6 +617,7 @@ mod tests {
613617
assert_eq!(key_alg_result, KeyAlgorithm::UNKNOWN_ALGORITHM);
614618
}
615619

620+
#[cfg(any(feature = "rust_crypto", feature = "aws_lc_rs"))]
616621
#[test]
617622
#[wasm_bindgen_test]
618623
fn check_thumbprint() {

tests/ecdsa/mod.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,3 +191,73 @@ fn ec_jwk_from_key() {
191191
.unwrap()
192192
);
193193
}
194+
195+
// ES512 Tests
196+
#[cfg(feature = "use_pem")]
197+
#[test]
198+
#[wasm_bindgen_test]
199+
fn es512_round_trip_sign_verification_pem() {
200+
let privkey_pem = include_bytes!("private_es512_key.pem");
201+
let pubkey_pem = include_bytes!("public_es512_key.pem");
202+
203+
let encrypted =
204+
sign(b"hello world", &EncodingKey::from_ec_pem(privkey_pem).unwrap(), Algorithm::ES512)
205+
.unwrap();
206+
let is_valid = verify(
207+
&encrypted,
208+
b"hello world",
209+
&DecodingKey::from_ec_pem(pubkey_pem).unwrap(),
210+
Algorithm::ES512,
211+
)
212+
.unwrap();
213+
assert!(is_valid);
214+
}
215+
216+
#[cfg(feature = "use_pem")]
217+
#[test]
218+
#[wasm_bindgen_test]
219+
fn es512_round_trip_claim() {
220+
let privkey_pem = include_bytes!("private_es512_key.pem");
221+
let pubkey_pem = include_bytes!("public_es512_key.pem");
222+
let my_claims = Claims {
223+
sub: "es512@example.com".to_string(),
224+
company: "ACME".to_string(),
225+
exp: OffsetDateTime::now_utc().unix_timestamp() + 10000,
226+
};
227+
let token = encode(
228+
&Header::new(Algorithm::ES512),
229+
&my_claims,
230+
&EncodingKey::from_ec_pem(privkey_pem).unwrap(),
231+
)
232+
.unwrap();
233+
let token_data = decode::<Claims>(
234+
&token,
235+
&DecodingKey::from_ec_pem(pubkey_pem).unwrap(),
236+
&Validation::new(Algorithm::ES512),
237+
)
238+
.unwrap();
239+
assert_eq!(my_claims, token_data.claims);
240+
}
241+
242+
#[cfg(feature = "use_pem")]
243+
#[test]
244+
#[wasm_bindgen_test]
245+
fn es512_sign_and_verify() {
246+
let privkey_pem = include_bytes!("private_es512_key.pem");
247+
let pubkey_pem = include_bytes!("public_es512_key.pem");
248+
let message = b"test message for ES512";
249+
250+
// Sign the message
251+
let encrypted =
252+
sign(message, &EncodingKey::from_ec_pem(privkey_pem).unwrap(), Algorithm::ES512).unwrap();
253+
254+
// Verify the signature
255+
let is_valid = verify(
256+
&encrypted,
257+
message,
258+
&DecodingKey::from_ec_pem(pubkey_pem).unwrap(),
259+
Algorithm::ES512,
260+
)
261+
.unwrap();
262+
assert!(is_valid);
263+
}

0 commit comments

Comments
 (0)