diff --git a/Cargo.toml b/Cargo.toml index 707d79b..2995776 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,9 @@ bitflags = "1.2" derive_more = "0.99" jsonwebtoken = { version = "7.2", optional = true } num-bigint = { version = "0.2", optional = true } +p256 = { version = "0.3", optional = true } paste = "0.1" +rand = { version = "0.7", optional = true } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" syn = { version = "1.0", features = ["full"] } # required to parse const generics @@ -24,6 +26,7 @@ zeroize = { version = "1.1", features = ["zeroize_derive"] } [features] convert = ["num-bigint", "yasna"] +generate = ["p256", "rand"] [dev-dependencies] jsonwebtoken = "7.2" diff --git a/README.md b/README.md index 84cf456..24a5844 100644 --- a/README.md +++ b/README.md @@ -40,4 +40,6 @@ fn main() { * `convert` - enables `Key::{to_der, to_pem}`. This pulls in the [yasna](https://crates.io/crates/yasna) crate. +* `generate` - enables `Key::{generate_p256, generate_symmetric}`. + This pulls in the [p256](https://crates.io/crates/p256) and [rand](https://crates.io/crates/rand) crates. * `jsonwebtoken` - enables conversions to types in the [jsonwebtoken](https://crates.io/crates/jsonwebtoken) crate. diff --git a/src/lib.rs b/src/lib.rs index 04763d8..cf0adba 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -200,9 +200,12 @@ impl Key { pkcs8::write_private(oids, |writer: &mut DERWriterSeq| { writer.next().write_i8(1); // version writer.next().write_bytes(private_point.as_slice()); - writer.next().write_tagged(Tag::context(0), |writer| { - writer.write_oid(&prime256v1_oid) - }); + // The following tagged value is optional. OpenSSL produces it, + // but many tools, including jwt.io and `jsonwebtoken`, don't like it, + // so we don't include it. + // writer.next().write_tagged(Tag::context(0), |writer| { + // writer.write_oid(&prime256v1_oid) + // }); writer.next().write_tagged(Tag::context(1), write_public); }) } @@ -286,6 +289,45 @@ impl Key { writeln!(&mut pem, "-----END {} KEY-----", key_ty).unwrap(); Ok(pem) } + + /// Generates a new symmetric key with the specified number of bits. + /// Best used with one of the HS algorithms (e.g., HS256). + #[cfg(feature = "generate")] + pub fn generate_symmetric(num_bits: usize) -> Self { + use rand::RngCore; + let mut bytes = Vec::with_capacity(num_bits / 8); + rand::thread_rng().fill_bytes(&mut bytes); + Self::Symmetric { key: bytes.into() } + } + + /// Generates a new EC keypair using the prime256 curve. + /// Used with the ES256 algorithm. + #[cfg(feature = "generate")] + pub fn generate_p256() -> Self { + use p256::elliptic_curve::generic_array::GenericArray; + use rand::RngCore; + + let mut sk_bytes = GenericArray::default(); + rand::thread_rng().fill_bytes(&mut sk_bytes); + let sk = p256::SecretKey::new(sk_bytes); + let sk_scalar = p256::arithmetic::Scalar::from_secret(sk).unwrap(); + + let pk = p256::arithmetic::ProjectivePoint::generator() * &sk_scalar; + let pk_bytes = &pk + .to_affine() + .unwrap() + .to_uncompressed_pubkey() + .into_bytes()[1..]; + let (x_bytes, y_bytes) = pk_bytes.split_at(32); + + Self::EC { + curve: Curve::P256 { + d: Some(sk_scalar.to_bytes().into()), + x: ByteArray::try_from_slice(x_bytes).unwrap(), + y: ByteArray::try_from_slice(y_bytes).unwrap(), + }, + } + } } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/tests.rs b/src/tests.rs index dcc8e71..6e4b270 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -85,6 +85,32 @@ fn serialize_es256() { ); } +#[cfg(feature = "generate")] +#[test] +fn generate_p256() { + extern crate jsonwebtoken as jwt; + + #[derive(Serialize, Deserialize)] + struct TokenClaims {} + + let mut the_jwk = JsonWebKey::new(Key::generate_p256()); + the_jwk.set_algorithm(JsonWebAlgorithm::ES256).unwrap(); + + let encoding_key = jwt::EncodingKey::from_ec_der(&the_jwk.key.to_der().unwrap()); + let token = jwt::encode( + &jwt::Header::new(the_jwk.algorithm.unwrap().into()), + &TokenClaims {}, + &encoding_key, + ) + .unwrap(); + + let mut validation = jwt::Validation::new(the_jwk.algorithm.unwrap().into()); + validation.validate_exp = false; + let public_pem = the_jwk.key.to_public().unwrap().to_pem().unwrap(); + let decoding_key = jwt::DecodingKey::from_ec_pem(public_pem.as_bytes()).unwrap(); + jwt::decode::(&token, &decoding_key, &validation).unwrap(); +} + #[test] fn deserialize_hs256() { let jwk_str = r#"{ @@ -136,66 +162,65 @@ fn deserialize_rs256() { public: RsaPublic { e: PublicExponent, n: vec![ - 149, 122, 70, 152, 26, 8, 198, 62, 122, 78, 154, 109, 2, 150, 154, 115, - 165, 117, 247, 254, 214, 89, 186, 180, 107, 94, 26, 229, 65, 193, 6, 89, - 28, 231, 131, 112, 33, 70, 182, 95, 138, 125, 57, 226, 182, 179, 46, 171, - 45, 15, 245, 131, 80, 28, 75, 7, 236, 85, 76, 188, 48, 255, 145, 139 + 164, 44, 219, 113, 223, 100, 142, 248, 57, 173, 241, 135, 116, 67, 22, 157, + 122, 56, 247, 54, 193, 232, 82, 208, 250, 109, 1, 208, 27, 213, 167, 70, + 168, 141, 85, 152, 107, 76, 110, 140, 47, 153, 63, 182, 97, 196, 28, 143, + 199, 39, 54, 61, 172, 240, 20, 146, 98, 246, 43, 217, 254, 8, 17, 195 ] .into() }, private: Some(RsaPrivate { d: vec![ - 94, 141, 21, 0, 123, 95, 87, 127, 7, 192, 150, 192, 35, 165, 254, 22, 238, - 239, 187, 42, 16, 142, 123, 162, 74, 84, 33, 113, 40, 241, 159, 64, 89, - 124, 179, 255, 12, 172, 93, 39, 96, 13, 129, 49, 149, 19, 76, 118, 227, - 118, 7, 170, 21, 145, 28, 186, 27, 110, 96, 220, 157, 75, 140, 57 + 65, 218, 124, 107, 192, 223, 229, 57, 76, 105, 169, 104, 92, 10, 77, 23, + 253, 222, 187, 203, 11, 28, 213, 155, 93, 216, 59, 209, 238, 88, 85, 48, + 44, 209, 101, 31, 104, 135, 249, 115, 121, 253, 233, 26, 195, 12, 12, 230, + 48, 76, 32, 42, 114, 123, 3, 83, 73, 244, 217, 115, 207, 134, 116, 1 ] .into(), p: Some( vec![ - 252, 180, 162, 167, 154, 56, 121, 161, 159, 219, 155, 175, 195, 37, 42, - 246, 230, 209, 180, 167, 166, 172, 38, 168, 11, 27, 166, 162, 62, 183, - 2, 237 + 232, 4, 56, 200, 119, 159, 215, 182, 167, 254, 46, 75, 64, 241, 205, + 35, 28, 233, 31, 174, 113, 88, 228, 159, 254, 160, 129, 238, 175, 165, + 95, 35 ] .into() ), q: Some( vec![ - 151, 109, 34, 46, 152, 156, 17, 109, 238, 141, 173, 25, 131, 108, 79, - 232, 56, 217, 107, 206, 155, 15, 130, 16, 223, 1, 87, 9, 194, 130, 127, - 87 + 181, 37, 95, 165, 231, 194, 177, 253, 98, 90, 96, 44, 215, 54, 47, 197, + 209, 44, 82, 43, 244, 84, 193, 46, 64, 27, 91, 78, 40, 227, 252, 225 ] .into() ), dp: Some( vec![ - 250, 76, 172, 195, 23, 149, 18, 156, 140, 235, 7, 84, 219, 20, 104, - 110, 239, 135, 12, 201, 245, 227, 147, 210, 100, 86, 58, 1, 127, 222, - 227, 173 + 169, 89, 203, 136, 167, 168, 72, 111, 206, 151, 61, 123, 56, 96, 70, + 119, 134, 182, 178, 165, 69, 158, 184, 225, 255, 157, 112, 185, 164, 3, + 117, 57 ] .into() ), dq: Some( vec![ - 58, 163, 22, 19, 121, 33, 38, 86, 173, 131, 203, 62, 15, 248, 71, 81, - 35, 130, 126, 14, 185, 88, 222, 2, 238, 120, 52, 94, 33, 38, 43, 109 + 24, 191, 196, 115, 172, 88, 131, 108, 245, 21, 23, 242, 200, 108, 148, + 214, 88, 31, 208, 18, 69, 77, 151, 31, 52, 143, 8, 72, 131, 121, 178, + 193 ] .into() ), qi: Some( vec![ - 218, 108, 192, 105, 42, 251, 35, 80, 247, 188, 59, 78, 133, 181, 138, - 75, 223, 189, 16, 180, 71, 41, 176, 7, 207, 135, 97, 159, 128, 210, 8, - 26 + 105, 216, 80, 28, 127, 8, 25, 113, 95, 44, 67, 39, 103, 155, 127, 77, + 224, 169, 231, 56, 18, 193, 9, 45, 39, 105, 102, 202, 92, 84, 27, 67 ] .into() ) }) }, - algorithm: Some(JsonWebAlgorithm::RS256), + algorithm: None, key_id: None, key_ops: KeyOps::empty(), - key_use: None, + key_use: Some(KeyUse::Encryption), } ); } @@ -273,10 +298,9 @@ fn p256_private_to_pem() { assert_eq!( jwk.key.to_pem().unwrap(), "-----BEGIN PRIVATE KEY----- -MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgZoKQ9j4dhIBlMRVr -v+QG8P/T9sutv3/95eio9MtpgKigCgYIKoZIzj0DAQehRANCAARA4wea/3q1WUm/ -642qmucNIoiPkCItNcpGiZdidq/Q3U42GaB53LWrRBOjQqypl0HSST5zc2RF/JwZ -mXXtwGOJ +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgZoKQ9j4dhIBlMRVr +v+QG8P/T9sutv3/95eio9MtpgKihRANCAARA4wea/3q1WUm/642qmucNIoiPkCIt +NcpGiZdidq/Q3U42GaB53LWrRBOjQqypl0HSST5zc2RF/JwZmXXtwGOJ -----END PRIVATE KEY----- " );