Skip to content

Commit 0374fd8

Browse files
authored
implement jwa algorithms (#650)
Implement all JWA algorithms Close #621
1 parent 702a9a3 commit 0374fd8

File tree

5 files changed

+481
-9
lines changed

5 files changed

+481
-9
lines changed

rama-crypto/src/jose/constants.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
pub(crate) use der_encoding_tags::*;
2+
pub(crate) use rsa_algorithm_identifier::RSA_ALGORITHM_IDENTIFIER;
3+
mod der_encoding_tags {
4+
/// Identifier tag for a DER encoded integer.
5+
/// Defined in [ITU X.680](https://www.itu.int/ITU-T/studygroups/com17/languages/X.680-0207.pdf).
6+
pub(crate) const DER_TAG_INTEGER: u8 = 0x02;
7+
/// Identifier tag for a DER encoded bit string.
8+
/// Defined in [ITU X.680](https://www.itu.int/ITU-T/studygroups/com17/languages/X.680-0207.pdf).
9+
pub(crate) const DER_TAG_BIT_STRING: u8 = 0x03;
10+
/// Identifier tag for a DER encoded sequence.
11+
/// Defined in [ITU X.680](https://www.itu.int/ITU-T/studygroups/com17/languages/X.680-0207.pdf).
12+
pub(crate) const DER_TAG_SEQUENCE: u8 = 0x30;
13+
/// Maximum length of a DER encoded length in short form.
14+
/// Defined in [ITU X.690](https://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf).
15+
pub(crate) const DER_LENGTH_SHORT_FORM_MAX: usize = 127;
16+
/// Octet that indicates that no unused bits are present in a bit string.
17+
/// Defined in section 8.6 of [ITU X.690](https://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf).
18+
pub(crate) const BIT_STRING_NO_UNUSED_BITS: u8 = 0x00;
19+
}
20+
21+
/// DER encoded byte representation of RSA encryption algorithm identifier.
22+
///
23+
/// The identifier oid: `1.2.840.113549.1.1.1` defined in appendix C of
24+
/// [RFC 8017](https://datatracker.ietf.org/doc/rfc8017/)
25+
///
26+
/// Section 2.2.1 of the [RFC 3279](https://www.rfc-editor.org/rfc/rfc3279.html) specifies the tag needs to be
27+
/// NULL. The general structure is IDENTIFIER, PARAMETER, but for rsa here we don't
28+
/// have PARAMETER, so we use NULL instead.
29+
///
30+
mod rsa_algorithm_identifier {
31+
const SEQUENCE_TAG: u8 = 0x30;
32+
const LENGTH: u8 = 0x0d;
33+
const OBJECT_IDENTIFIER_TAG: u8 = 0x06;
34+
const LENGTH_OID: u8 = 0x09;
35+
const NULL_TAG: u8 = 0x05;
36+
const LENGTH_NULL: u8 = 0x00;
37+
38+
/// This entire thing has 3 layers, and the final goal is to
39+
/// get the der algorithm identifier for rsa encoding
40+
///
41+
/// 1. The identifier oid: 1.2.840.113549.1.1.1 defined in RFC 8017
42+
/// 2. RFC 3279 (Algorithms and Identifiers for the Internet X.509 PKI)
43+
/// specifies tag is needs to be NULL. The general structure is
44+
/// IDENTIFIER, PARAMETER, but for rsa here we dont have PARAMETER
45+
/// so NULL instead
46+
/// 3. We need to combine everything in DER encode
47+
/// - 3.1 (IDENTIFIER, PARAMETER) is a sequence, so SEQUENCE_TAG, LENGHT of what follows
48+
/// - 3.2 OBJECT_IDENTIFIER_TAG to specify OID, LENGTH of OID,and actual oid
49+
/// - 3.3 NULL_TAG and LENGTH_NULL to encode null value
50+
pub(crate) const RSA_ALGORITHM_IDENTIFIER: [u8; 15] = [
51+
SEQUENCE_TAG,
52+
LENGTH,
53+
OBJECT_IDENTIFIER_TAG,
54+
LENGTH_OID,
55+
// OID: 1.2.840.113549.1.1.1
56+
0x2a,
57+
0x86,
58+
0x48,
59+
0x86,
60+
0xf7,
61+
0x0d,
62+
0x01,
63+
0x01,
64+
0x01,
65+
NULL_TAG,
66+
LENGTH_NULL,
67+
];
68+
}
69+
70+
// Integer encoding constants
71+
pub(crate) const INTEGER_SIGN_BIT_MASK: u8 = 0x80;

rama-crypto/src/jose/jwa.rs

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
use std::ops::Deref;
22

3-
use aws_lc_rs::signature::{
4-
ECDSA_P256_SHA256_FIXED_SIGNING, ECDSA_P384_SHA384_FIXED_SIGNING,
5-
ECDSA_P521_SHA512_FIXED_SIGNING, EcdsaSigningAlgorithm, EcdsaVerificationAlgorithm,
3+
use aws_lc_rs::{
4+
hmac,
5+
hmac::{HMAC_SHA256, HMAC_SHA384, HMAC_SHA512},
6+
signature::{
7+
ECDSA_P256_SHA256_FIXED_SIGNING, ECDSA_P384_SHA384_FIXED_SIGNING,
8+
ECDSA_P521_SHA512_FIXED_SIGNING, EcdsaSigningAlgorithm, EcdsaVerificationAlgorithm,
9+
RSA_PKCS1_2048_8192_SHA256, RSA_PKCS1_2048_8192_SHA384, RSA_PKCS1_2048_8192_SHA512,
10+
RSA_PKCS1_SHA256, RSA_PKCS1_SHA384, RSA_PKCS1_SHA512, RSA_PSS_2048_8192_SHA256,
11+
RSA_PSS_2048_8192_SHA384, RSA_PSS_2048_8192_SHA512, RSA_PSS_SHA256, RSA_PSS_SHA384,
12+
RSA_PSS_SHA512, RsaEncoding, VerificationAlgorithm,
13+
},
614
};
715
use rama_core::error::OpaqueError;
816
use serde::{Deserialize, Serialize};
@@ -113,3 +121,58 @@ impl TryFrom<&'static EcdsaSigningAlgorithm> for JWA {
113121
}
114122
}
115123
}
124+
125+
impl TryFrom<JWA> for &'static hmac::Algorithm {
126+
type Error = OpaqueError;
127+
128+
fn try_from(value: JWA) -> Result<Self, Self::Error> {
129+
match value {
130+
JWA::HS256 => Ok(&HMAC_SHA256),
131+
JWA::HS384 => Ok(&HMAC_SHA384),
132+
JWA::HS512 => Ok(&HMAC_SHA512),
133+
_ => Err(OpaqueError::from_display(
134+
"Non-Hmac algorithm cannot be converted to hmac types",
135+
)),
136+
}
137+
}
138+
}
139+
140+
impl TryFrom<JWA> for &'static dyn RsaEncoding {
141+
type Error = OpaqueError;
142+
143+
fn try_from(value: JWA) -> Result<Self, Self::Error> {
144+
match value {
145+
JWA::RS256 => Ok(&RSA_PKCS1_SHA256),
146+
JWA::RS384 => Ok(&RSA_PKCS1_SHA384),
147+
JWA::RS512 => Ok(&RSA_PKCS1_SHA512),
148+
JWA::PS256 => Ok(&RSA_PSS_SHA256),
149+
JWA::PS384 => Ok(&RSA_PSS_SHA384),
150+
JWA::PS512 => Ok(&RSA_PSS_SHA512),
151+
_ => Err(OpaqueError::from_display(
152+
"Non-RSA algorithm cannot be converted to rsa types",
153+
)),
154+
}
155+
}
156+
}
157+
158+
impl TryFrom<JWA> for &'static dyn VerificationAlgorithm {
159+
type Error = OpaqueError;
160+
161+
fn try_from(value: JWA) -> Result<Self, Self::Error> {
162+
match value {
163+
JWA::RS256 => Ok(&RSA_PKCS1_2048_8192_SHA256),
164+
JWA::RS384 => Ok(&RSA_PKCS1_2048_8192_SHA384),
165+
JWA::RS512 => Ok(&RSA_PKCS1_2048_8192_SHA512),
166+
JWA::PS256 => Ok(&RSA_PSS_2048_8192_SHA256),
167+
JWA::PS384 => Ok(&RSA_PSS_2048_8192_SHA384),
168+
JWA::PS512 => Ok(&RSA_PSS_2048_8192_SHA512),
169+
JWA::ES256 | JWA::ES384 | JWA::ES512 => {
170+
let signing_algo: &'static EcdsaSigningAlgorithm = value.try_into()?;
171+
Ok(signing_algo.deref())
172+
}
173+
_ => Err(OpaqueError::from_display(
174+
"Verification algorithm is not supported",
175+
)),
176+
}
177+
}
178+
}

rama-crypto/src/jose/jwk.rs

Lines changed: 178 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
use aws_lc_rs::{
22
digest::{Digest, SHA256, digest},
3+
encoding::{AsDer, Pkcs8V1Der},
34
pkcs8::Document,
45
rand::SystemRandom,
6+
rsa::KeySize,
57
signature::{
68
self, ECDSA_P256_SHA256_FIXED_SIGNING, EcdsaKeyPair, EcdsaSigningAlgorithm,
7-
EcdsaVerificationAlgorithm, KeyPair, Signature,
9+
EcdsaVerificationAlgorithm, KeyPair, RsaKeyPair, Signature,
810
},
911
};
1012
use base64::{Engine as _, prelude::BASE64_URL_SAFE_NO_PAD};
1113
use rama_core::error::{ErrorContext, OpaqueError};
1214
use serde::{Deserialize, Serialize, Serializer, ser::SerializeStruct};
1315

14-
use crate::jose::{JWA, Signer};
16+
use crate::jose::{JWA, Signer, jwk_utils::create_subject_public_key_info};
1517

1618
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
1719
/// [`JWK`] or JSON Web Key as defined in [`rfc7517`]
@@ -157,7 +159,21 @@ impl JWK {
157159
&self,
158160
) -> Result<signature::UnparsedPublicKey<Vec<u8>>, OpaqueError> {
159161
match &self.key_type {
160-
JWKType::RSA { .. } => Err(OpaqueError::from_display("currently not supported")),
162+
JWKType::RSA { n, e } => {
163+
let n_bytes = BASE64_URL_SAFE_NO_PAD
164+
.decode(n)
165+
.context("decode RSA modulus (n)")?;
166+
let e_bytes = BASE64_URL_SAFE_NO_PAD
167+
.decode(e)
168+
.context("decode RSA exponent (e)")?;
169+
170+
let rsa_public_key_sequence = create_subject_public_key_info(n_bytes, e_bytes);
171+
172+
Ok(signature::UnparsedPublicKey::new(
173+
self.alg.try_into()?,
174+
rsa_public_key_sequence,
175+
))
176+
}
161177
JWKType::OCT { .. } => Err(OpaqueError::from_display(
162178
"Symmetric key cannot be converted to public key",
163179
)),
@@ -181,6 +197,25 @@ impl JWK {
181197
}
182198
}
183199
}
200+
201+
/// Creates a new [`JWK`] from a given [`RSAKeyPair`]
202+
#[must_use]
203+
pub fn new_from_rsa_key_pair(rsa_key_pair: &RsaKeyPair, alg: JWA) -> Self {
204+
let n = rsa_key_pair.public_key().modulus();
205+
let e = rsa_key_pair.public_key().exponent();
206+
Self {
207+
alg,
208+
key_type: JWKType::RSA {
209+
n: BASE64_URL_SAFE_NO_PAD.encode(n.big_endian_without_leading_zero()),
210+
e: BASE64_URL_SAFE_NO_PAD.encode(e.big_endian_without_leading_zero()),
211+
},
212+
r#use: Some(JWKUse::Signature),
213+
key_ops: None,
214+
x5c: None,
215+
x5t: None,
216+
x5t_sha256: None,
217+
}
218+
}
184219
}
185220

186221
#[derive(Debug)]
@@ -257,7 +292,7 @@ impl EcdsaKey {
257292
}
258293

259294
#[derive(Serialize)]
260-
struct EcdsaKeySigningHeaders<'a> {
295+
struct SigningHeaders<'a> {
261296
alg: JWA,
262297
jwk: &'a JWK,
263298
}
@@ -272,7 +307,7 @@ impl Signer for EcdsaKey {
272307
_unprotected_headers: &mut super::jws::Headers,
273308
) -> Result<(), Self::Error> {
274309
let jwk = self.create_jwk();
275-
protected_headers.try_set_headers(EcdsaKeySigningHeaders {
310+
protected_headers.try_set_headers(SigningHeaders {
276311
alg: jwk.alg,
277312
jwk: &jwk,
278313
})?;
@@ -289,9 +324,91 @@ impl Signer for EcdsaKey {
289324
}
290325
}
291326

327+
pub struct RsaKey {
328+
rng: SystemRandom,
329+
alg: JWA,
330+
inner: RsaKeyPair,
331+
}
332+
333+
impl RsaKey {
334+
/// Create a new [`RsaKey`] from the given [`RsaKeyPair`]
335+
pub fn new(key_pair: RsaKeyPair, alg: JWA, rng: SystemRandom) -> Result<Self, OpaqueError> {
336+
Ok(Self {
337+
rng,
338+
alg,
339+
inner: key_pair,
340+
})
341+
}
342+
343+
/// Generate a new [`RsaKey`] from a newly generated [`RsaKeyPair`]
344+
pub fn generate(key_size: KeySize) -> Result<Self, OpaqueError> {
345+
let key_pair = RsaKeyPair::generate(key_size).context("error generating rsa key pair")?;
346+
347+
Self::new(key_pair, JWA::RS256, SystemRandom::new())
348+
}
349+
350+
/// Generate a new [`RsaKey`] from the given pkcs8 der
351+
pub fn from_pkcs8_der(
352+
pkcs8_der: &[u8],
353+
alg: JWA,
354+
rng: SystemRandom,
355+
) -> Result<Self, OpaqueError> {
356+
let key_pair = RsaKeyPair::from_pkcs8(pkcs8_der).context("create RSAKeyPair from pkcs8")?;
357+
358+
Self::new(key_pair, alg, rng)
359+
}
360+
361+
/// Create pkcs8 der for the current [`RsaKeyPair`]
362+
pub fn pkcs8_der(&self) -> Result<(JWA, Pkcs8V1Der<'static>), OpaqueError> {
363+
let doc = self
364+
.inner
365+
.as_der()
366+
.context("error creating pkcs8 der from rsa keypair")?;
367+
Ok((self.alg, doc))
368+
}
369+
370+
/// Create a [`JWK`] for this [`RsaKey`]
371+
#[must_use]
372+
pub fn create_jwk(&self) -> JWK {
373+
JWK::new_from_rsa_key_pair(&self.inner, self.alg)
374+
}
375+
376+
#[must_use]
377+
pub fn rng(&self) -> &SystemRandom {
378+
&self.rng
379+
}
380+
}
381+
382+
impl Signer for RsaKey {
383+
type Signature = Vec<u8>;
384+
type Error = OpaqueError;
385+
386+
fn set_headers(
387+
&self,
388+
protected_headers: &mut super::jws::Headers,
389+
_unprotected_headers: &mut super::jws::Headers,
390+
) -> Result<(), Self::Error> {
391+
let jwk = self.create_jwk();
392+
protected_headers.try_set_headers(SigningHeaders {
393+
alg: jwk.alg,
394+
jwk: &jwk,
395+
})?;
396+
Ok(())
397+
}
398+
399+
fn sign(&self, data: &str) -> Result<Self::Signature, Self::Error> {
400+
let mut sig = vec![0; self.inner.public_modulus_len()];
401+
self.inner
402+
.sign(self.alg.try_into()?, self.rng(), data.as_bytes(), &mut sig)
403+
.context("sign protected data")?;
404+
Ok(sig)
405+
}
406+
}
407+
292408
#[cfg(test)]
293409
mod tests {
294410
use super::*;
411+
use crate::jose::JWKType::RSA;
295412

296413
#[test]
297414
fn jwk_thumb_order_is_correct() {
@@ -328,4 +445,60 @@ mod tests {
328445

329446
assert_eq!(key.create_jwk(), recreated_key.create_jwk())
330447
}
448+
449+
#[test]
450+
fn test_n_and_e_are_base64_encoded() {
451+
let rsa_key_pair = RsaKey::generate(KeySize::Rsa4096).unwrap();
452+
let jwk = JWK::new_from_rsa_key_pair(&rsa_key_pair.inner, JWA::PS512);
453+
let JWKType::RSA { n, e } = jwk.key_type else {
454+
panic!("JWK type not RSA")
455+
};
456+
assert!(BASE64_URL_SAFE_NO_PAD.decode(n).is_ok());
457+
assert!(BASE64_URL_SAFE_NO_PAD.decode(e).is_ok());
458+
}
459+
460+
/// This example is taken from the [RFC 7517](https://datatracker.ietf.org/doc/html/rfc7517#appendix-A.1)
461+
/// Appendix A.1.
462+
#[test]
463+
fn test_unparsed_public_key() {
464+
let jwk_rsa = JWK {
465+
alg: JWA::RS256,
466+
key_type: RSA {
467+
n: "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK\
468+
7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl9\
469+
3lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHz\
470+
u6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksIN\
471+
HaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw"
472+
.to_owned(),
473+
e: "AQAB".to_owned(),
474+
},
475+
r#use: None,
476+
key_ops: None,
477+
x5c: None,
478+
x5t: None,
479+
x5t_sha256: None,
480+
};
481+
// This is the known byte sequence of the unparsed public key generated from the above JWK
482+
// using the python `cryptography` library.
483+
let expected_unparsed_bytes = [
484+
48, 130, 1, 34, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0, 3, 130, 1, 15,
485+
0, 48, 130, 1, 10, 2, 130, 1, 1, 0, 210, 252, 123, 106, 10, 30, 108, 103, 16, 74, 235,
486+
143, 136, 178, 87, 102, 155, 77, 246, 121, 221, 173, 9, 155, 92, 74, 108, 217, 168,
487+
128, 21, 181, 161, 51, 191, 11, 133, 108, 120, 113, 182, 223, 0, 11, 85, 79, 206, 179,
488+
194, 237, 81, 43, 182, 143, 20, 92, 110, 132, 52, 117, 47, 171, 82, 161, 207, 193, 36,
489+
64, 143, 121, 181, 138, 69, 120, 193, 100, 40, 133, 87, 137, 247, 162, 73, 227, 132,
490+
203, 45, 159, 174, 45, 103, 253, 150, 251, 146, 108, 25, 142, 7, 115, 153, 253, 200,
491+
21, 192, 175, 9, 125, 222, 90, 173, 239, 244, 77, 231, 14, 130, 127, 72, 120, 67, 36,
492+
57, 191, 238, 185, 96, 104, 208, 71, 79, 197, 13, 109, 144, 191, 58, 152, 223, 175, 16,
493+
64, 200, 156, 2, 214, 146, 171, 59, 60, 40, 150, 96, 157, 134, 253, 115, 183, 116, 206,
494+
7, 64, 100, 124, 238, 234, 163, 16, 189, 18, 249, 133, 168, 235, 159, 89, 253, 212, 38,
495+
206, 165, 178, 18, 15, 79, 42, 52, 188, 171, 118, 75, 126, 108, 84, 214, 132, 2, 56,
496+
188, 196, 5, 135, 165, 158, 102, 237, 31, 51, 137, 69, 119, 99, 92, 71, 10, 247, 92,
497+
249, 44, 32, 209, 218, 67, 225, 191, 196, 25, 226, 34, 166, 240, 208, 187, 53, 140, 94,
498+
56, 249, 203, 5, 10, 234, 254, 144, 72, 20, 241, 172, 26, 164, 156, 202, 158, 160, 202,
499+
131, 2, 3, 1, 0, 1,
500+
];
501+
let unparsed_key = jwk_rsa.unparsed_public_key().unwrap();
502+
assert_eq!(expected_unparsed_bytes, unparsed_key.as_ref());
503+
}
331504
}

0 commit comments

Comments
 (0)