diff --git a/Cargo.toml b/Cargo.toml index 808636f..06b5f55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ resolver = "2" [workspace.package] edition = "2021" -version = "0.0.21" +version = "0.0.22" authors = ["Jun Kurihara"] homepage = "https://github.com/junkurihara/httpsig-rs" repository = "https://github.com/junkurihara/httpsig-rs" diff --git a/README.md b/README.md index 92f94e2..cb32b3b 100644 --- a/README.md +++ b/README.md @@ -16,13 +16,11 @@ This crates provides a basic library [httpsig](./httpsig) and [its extension](./ - [x] HMAC using SHA-256 - [x] Ed25519 - [x] ECDSA-P256 using SHA-256 -- [ ] ECDSA-P384 using SHA-384 +- [x] ECDSA-P384 using SHA-384 +- [x] RSASSA-PSS using SHA-512 +- [x] RSASSA-PKCS1-v1_5 using SHA-256 -~~- [ ] RSASSA-PSS using SHA-512~~ - -~~- [ ] RSASSA-PKCS1-v1_5 using SHA-256~~ - -At this point, we have no plan to support RSA signature due to [the problem related to the non-constant time operation](https://github.com/RustCrypto/RSA/issues/19), i.e., [Mervin Attack](https://people.redhat.com/~hkario/marvin/). +At this point, **RSA signature is non-default** due to [the problem related to the non-constant time operation](https://github.com/RustCrypto/RSA/issues/19), i.e., [Marvin Attack](https://people.redhat.com/~hkario/marvin/). If you want to use RSA signature, please enable the `rsa-signature` feature flag in your `Cargo.toml`. ## Usage of Extension for `hyper` (`httpsig-hyper`) @@ -48,8 +46,11 @@ async fn signer(&mut req: Request) -> HttpSigResult<()> { .unwrap(); let mut signature_params = HttpSignatureParams::try_new(&covered_components).unwrap(); + // specify algorithm name since we cannot always infer it from key info + let alg = AlgorithmName::Ed25519; + // set signing/verifying key information, alg and keyid - let secret_key = SecretKey::from_pem(SECRET_KEY_STRING).unwrap(); + let secret_key = SecretKey::from_pem(&alg, SECRET_KEY_STRING).unwrap(); signature_params.set_key_info(&secret_key); req @@ -59,7 +60,11 @@ async fn signer(&mut req: Request) -> HttpSigResult<()> { /// Validation function that verifies a request with a signature async fn verifier(req: &Request) -> HttpSigResult { - let public_key = PublicKey::from_pem(PUBLIC_KEY_STRING).unwrap(); + // specify algorithm name since we cannot always infer it from key info + let alg = AlgorithmName::Ed25519; // directly use Ed25519 algorithm + // or else infer it from the request. Find your public key from IndexMap with alg and key_id pairs + // let alg_key_id_map = req.get_alg_key_ids().unwrap(); + let public_key = PublicKey::from_pem(&alg, PUBLIC_KEY_STRING).unwrap(); let key_id = public_key.key_id(); // verify signature with checking key_id @@ -105,8 +110,11 @@ async fn signer(&mut res: Response, corresponding_req: &Request) -> Htt .unwrap(); let mut signature_params = HttpSignatureParams::try_new(&covered_components).unwrap(); + // specify algorithm name since we cannot always infer it from key info + let alg = AlgorithmName::Ed25519; + // set signing/verifying key information, alg and keyid - let secret_key = SecretKey::from_pem(SECRET_KEY_STRING).unwrap(); + let secret_key = SecretKey::from_pem(&alg, SECRET_KEY_STRING).unwrap(); signature_params.set_key_info(&secret_key); req @@ -116,7 +124,11 @@ async fn signer(&mut res: Response, corresponding_req: &Request) -> Htt /// Validation function that verifies a response with a signature from response itself and sent request async fn verifier(res: &Response, sent_req: &Request) -> HttpSigResult { - let public_key = PublicKey::from_pem(PUBLIC_KEY_STRING).unwrap(); + // specify algorithm name since we cannot always infer it from key info + let alg = AlgorithmName::Ed25519; // directly use Ed25519 algorithm + // or else infer it from the response. Find your public key from IndexMap with alg and key_id pairs + // let alg_key_id_map = res.get_alg_key_ids().unwrap(); + let public_key = PublicKey::from_pem(&alg, PUBLIC_KEY_STRING).unwrap(); let key_id = public_key.key_id(); // verify signature with checking key_id diff --git a/httpsig-hyper/Cargo.toml b/httpsig-hyper/Cargo.toml index e58f8a1..de941b9 100644 --- a/httpsig-hyper/Cargo.toml +++ b/httpsig-hyper/Cargo.toml @@ -15,10 +15,11 @@ rust-version.workspace = true [features] default = ["blocking"] blocking = ["futures/executor"] +rsa-signature = ["httpsig/rsa-signature"] [dependencies] -httpsig = { path = "../httpsig", version = "0.0.21" } +httpsig = { path = "../httpsig", version = "0.0.22" } thiserror = { version = "2.0.18" } tracing = { version = "0.1.44" } diff --git a/httpsig-hyper/README.md b/httpsig-hyper/README.md index 2db5ec6..076e0a8 100644 --- a/httpsig-hyper/README.md +++ b/httpsig-hyper/README.md @@ -27,7 +27,8 @@ If you need to verify the body of a given message when `content-digest` is cover ```rust // first verifies the signature according to `signature-input` header -let public_key = PublicKey::from_pem(EDDSA_PUBLIC_KEY).unwrap(); +let alg = AlgorithmName::Ed25519; +let public_key = PublicKey::from_pem(&alg, EDDSA_PUBLIC_KEY).unwrap(); let signature_verification = req.verify_message_signature(&public_key, None).await; assert!(verification_res.is_ok()); diff --git a/httpsig-hyper/examples/hyper-request.rs b/httpsig-hyper/examples/hyper-request.rs index ec0ef49..d9243f3 100644 --- a/httpsig-hyper/examples/hyper-request.rs +++ b/httpsig-hyper/examples/hyper-request.rs @@ -43,7 +43,7 @@ async fn sender_ed25519(req: &mut Request) { let mut signature_params = HttpSignatureParams::try_new(&covered_components).unwrap(); // set signing/verifying key information, alg and keyid with ed25519 - let secret_key = SecretKey::from_pem(EDDSA_SECRET_KEY).unwrap(); + let secret_key = SecretKey::from_pem(&AlgorithmName::Ed25519, EDDSA_SECRET_KEY).unwrap(); signature_params.set_key_info(&secret_key); // set signature with custom signature name @@ -65,7 +65,7 @@ async fn sender_hs256(req: &mut Request) { let mut signature_params = HttpSignatureParams::try_new(&covered_components).unwrap(); // set signing/verifying key information, alg and keyid and random noce with hmac-sha256 - let shared_key = SharedKey::from_base64(HMACSHA256_SECRET_KEY).unwrap(); + let shared_key = SharedKey::from_base64(&AlgorithmName::HmacSha256, HMACSHA256_SECRET_KEY).unwrap(); signature_params.set_key_info(&shared_key); signature_params.set_random_nonce(); @@ -81,7 +81,7 @@ where B: http_body::Body + Send + Sync, { println!("Verifying ED25519 signature"); - let public_key = PublicKey::from_pem(EDDSA_PUBLIC_KEY).unwrap(); + let public_key = PublicKey::from_pem(&AlgorithmName::Ed25519, EDDSA_PUBLIC_KEY).unwrap(); let key_id = public_key.key_id(); // verify signature with checking key_id @@ -94,7 +94,7 @@ where B: http_body::Body + Send + Sync, { println!("Verifying HMAC-SHA256 signature"); - let shared_key = SharedKey::from_base64(HMACSHA256_SECRET_KEY).unwrap(); + let shared_key = SharedKey::from_base64(&AlgorithmName::HmacSha256, HMACSHA256_SECRET_KEY).unwrap(); let key_id = VerifyingKey::key_id(&shared_key); // verify signature with checking key_id diff --git a/httpsig-hyper/examples/hyper-response.rs b/httpsig-hyper/examples/hyper-response.rs index 9b12524..e096d29 100644 --- a/httpsig-hyper/examples/hyper-response.rs +++ b/httpsig-hyper/examples/hyper-response.rs @@ -55,7 +55,7 @@ async fn sender_ed25519(res: &mut Response, received_req: &Request, received_req: &Request bool; /// Extract all key ids for signature bases contained in the request headers - fn get_key_ids(&self) -> Result, Self::Error>; + fn get_alg_key_ids(&self) -> Result, Option)>, Self::Error>; /// Extract all signature params used to generate signature bases contained in the request headers fn get_signature_params(&self) -> Result, Self::Error>; @@ -243,9 +243,9 @@ where } /// Extract all signature bases contained in the request headers - fn get_key_ids(&self) -> HyperSigResult> { + fn get_alg_key_ids(&self) -> HyperSigResult, Option)>> { let req_or_res = RequestOrResponse::Request(self); - get_key_ids_inner(&req_or_res) + get_alg_key_ids_inner(&req_or_res) } /// Extract all signature params used to generate signature bases contained in the request headers @@ -358,9 +358,9 @@ where } /// Extract all key ids for signature bases contained in the response headers - fn get_key_ids(&self) -> Result, Self::Error> { + fn get_alg_key_ids(&self) -> Result, Option)>, Self::Error> { let req_or_res = RequestOrResponse::Response(self); - get_key_ids_inner(&req_or_res) + get_alg_key_ids_inner(&req_or_res) } /// Extract all signature params used to generate signature bases contained in the response headers @@ -594,11 +594,25 @@ fn has_message_signature_inner(headers: &HeaderMap) -> bool { } /// get key ids inner function -fn get_key_ids_inner(req_or_res: &RequestOrResponse) -> HyperSigResult> { +fn get_alg_key_ids_inner( + req_or_res: &RequestOrResponse, +) -> HyperSigResult, Option)>> { let signature_headers_map = extract_signature_headers_with_name(req_or_res)?; let res = signature_headers_map .iter() - .filter_map(|(name, headers)| headers.signature_params().keyid.clone().map(|key_id| (name.clone(), key_id))) + .map(|(name, headers)| { + // Unknown or unsupported algorithm strings are mapped to None + let alg = headers + .signature_params() + .alg + .clone() + .map(|a| AlgorithmName::from_str(&a)) + .transpose() + .ok() + .flatten(); + let key_id = headers.signature_params().keyid.clone(); + (name.clone(), (alg, key_id)) + }) .collect(); Ok(res) } @@ -904,7 +918,7 @@ mod tests { *, }; use http_body_util::Full; - use httpsig::prelude::{PublicKey, SecretKey, SharedKey}; + use httpsig::prelude::{AlgorithmName, PublicKey, SecretKey, SharedKey}; type BoxBody = http_body_util::combinators::BoxBody; @@ -1053,7 +1067,7 @@ MCowBQYDK2VwAyEA1ixMQcxO46PLlgQfYS46ivFd+n0CcDHSKUnuhm3i1O0= #[tokio::test] async fn test_set_verify_message_signature_req() { let mut req = build_request().await; - let secret_key = SecretKey::from_pem(EDDSA_SECRET_KEY).unwrap(); + let secret_key = SecretKey::from_pem(&AlgorithmName::Ed25519, EDDSA_SECRET_KEY).unwrap(); let mut signature_params = HttpSignatureParams::try_new(&build_covered_components_req()).unwrap(); signature_params.set_key_info(&secret_key); @@ -1062,7 +1076,7 @@ MCowBQYDK2VwAyEA1ixMQcxO46PLlgQfYS46ivFd+n0CcDHSKUnuhm3i1O0= assert!(signature_input.starts_with(r##"sig=("@method" "date" "content-type" "content-digest")"##)); // let signature = req.headers().get("signature").unwrap().to_str().unwrap(); - let public_key = PublicKey::from_pem(EDDSA_PUBLIC_KEY).unwrap(); + let public_key = PublicKey::from_pem(&AlgorithmName::Ed25519, EDDSA_PUBLIC_KEY).unwrap(); let verification_res = req.verify_message_signature(&public_key, None).await; assert!(verification_res.is_ok()); } @@ -1072,7 +1086,7 @@ MCowBQYDK2VwAyEA1ixMQcxO46PLlgQfYS46ivFd+n0CcDHSKUnuhm3i1O0= let req = build_request().await; let mut res = build_response().await; - let secret_key = SecretKey::from_pem(EDDSA_SECRET_KEY).unwrap(); + let secret_key = SecretKey::from_pem(&AlgorithmName::Ed25519, EDDSA_SECRET_KEY).unwrap(); let mut signature_params = HttpSignatureParams::try_new(&build_covered_components_res()).unwrap(); signature_params.set_key_info(&secret_key); @@ -1090,7 +1104,7 @@ MCowBQYDK2VwAyEA1ixMQcxO46PLlgQfYS46ivFd+n0CcDHSKUnuhm3i1O0= assert!(signature_input.starts_with(r##"sig=("@status" "@method";req "date" "content-type" "content-digest";req)"##)); // let signature = req.headers().get("signature").unwrap().to_str().unwrap(); - let public_key = PublicKey::from_pem(EDDSA_PUBLIC_KEY).unwrap(); + let public_key = PublicKey::from_pem(&AlgorithmName::Ed25519, EDDSA_PUBLIC_KEY).unwrap(); let verification_res = res.verify_message_signature(&public_key, None, Some(&req)).await; assert!(verification_res.is_ok()); } @@ -1098,7 +1112,7 @@ MCowBQYDK2VwAyEA1ixMQcxO46PLlgQfYS46ivFd+n0CcDHSKUnuhm3i1O0= #[tokio::test] async fn test_expired_signature() { let mut req = build_request().await; - let secret_key = SecretKey::from_pem(EDDSA_SECRET_KEY).unwrap(); + let secret_key = SecretKey::from_pem(&AlgorithmName::Ed25519, EDDSA_SECRET_KEY).unwrap(); let mut signature_params = HttpSignatureParams::try_new(&build_covered_components_req()).unwrap(); signature_params.set_key_info(&secret_key); let created = signature_params.created.unwrap(); @@ -1107,7 +1121,7 @@ MCowBQYDK2VwAyEA1ixMQcxO46PLlgQfYS46ivFd+n0CcDHSKUnuhm3i1O0= req.set_message_signature(&signature_params, &secret_key, None).await.unwrap(); - let public_key = PublicKey::from_pem(EDDSA_PUBLIC_KEY).unwrap(); + let public_key = PublicKey::from_pem(&AlgorithmName::Ed25519, EDDSA_PUBLIC_KEY).unwrap(); let verification_res = req.verify_message_signature(&public_key, None).await; assert!(verification_res.is_err()); } @@ -1115,7 +1129,7 @@ MCowBQYDK2VwAyEA1ixMQcxO46PLlgQfYS46ivFd+n0CcDHSKUnuhm3i1O0= #[tokio::test] async fn test_set_verify_with_signature_name() { let mut req = build_request().await; - let secret_key = SecretKey::from_pem(EDDSA_SECRET_KEY).unwrap(); + let secret_key = SecretKey::from_pem(&AlgorithmName::Ed25519, EDDSA_SECRET_KEY).unwrap(); let mut signature_params = HttpSignatureParams::try_new(&build_covered_components_req()).unwrap(); signature_params.set_key_info(&secret_key); @@ -1129,7 +1143,7 @@ MCowBQYDK2VwAyEA1ixMQcxO46PLlgQfYS46ivFd+n0CcDHSKUnuhm3i1O0= assert_eq!(signature_headers_map.len(), 1); assert_eq!(signature_headers_map[0].signature_name(), "custom_sig_name"); - let public_key = PublicKey::from_pem(EDDSA_PUBLIC_KEY).unwrap(); + let public_key = PublicKey::from_pem(&AlgorithmName::Ed25519, EDDSA_PUBLIC_KEY).unwrap(); let verification_res = req.verify_message_signature(&public_key, None).await; assert!(verification_res.is_ok()); } @@ -1137,13 +1151,13 @@ MCowBQYDK2VwAyEA1ixMQcxO46PLlgQfYS46ivFd+n0CcDHSKUnuhm3i1O0= #[tokio::test] async fn test_set_verify_with_key_id() { let mut req = build_request().await; - let secret_key = SecretKey::from_pem(EDDSA_SECRET_KEY).unwrap(); + let secret_key = SecretKey::from_pem(&AlgorithmName::Ed25519, EDDSA_SECRET_KEY).unwrap(); let mut signature_params = HttpSignatureParams::try_new(&build_covered_components_req()).unwrap(); signature_params.set_key_info(&secret_key); req.set_message_signature(&signature_params, &secret_key, None).await.unwrap(); - let public_key = PublicKey::from_pem(EDDSA_PUBLIC_KEY).unwrap(); + let public_key = PublicKey::from_pem(&AlgorithmName::Ed25519, EDDSA_PUBLIC_KEY).unwrap(); let key_id = public_key.key_id(); let verification_res = req.verify_message_signature(&public_key, Some(&key_id)).await; assert!(verification_res.is_ok()); @@ -1158,7 +1172,7 @@ MCowBQYDK2VwAyEA1ixMQcxO46PLlgQfYS46ivFd+n0CcDHSKUnuhm3i1O0= #[tokio::test] async fn test_set_verify_with_key_id_hmac_sha256() { let mut req = build_request().await; - let secret_key = SharedKey::from_base64(HMACSHA256_SECRET_KEY).unwrap(); + let secret_key = SharedKey::from_base64(&AlgorithmName::HmacSha256, HMACSHA256_SECRET_KEY).unwrap(); let mut signature_params = HttpSignatureParams::try_new(&build_covered_components_req()).unwrap(); signature_params.set_key_info(&secret_key); // Random nonce is highly recommended for HMAC @@ -1166,28 +1180,34 @@ MCowBQYDK2VwAyEA1ixMQcxO46PLlgQfYS46ivFd+n0CcDHSKUnuhm3i1O0= req.set_message_signature(&signature_params, &secret_key, None).await.unwrap(); - let key_id = VerifyingKey::key_id(&secret_key); - let verification_res = req.verify_message_signature(&secret_key, Some(&key_id)).await; + let org_key_id = VerifyingKey::key_id(&secret_key); + let (alg, key_id) = req.get_alg_key_ids().unwrap().into_iter().next().unwrap().1; + let alg = alg.unwrap(); + let key_id = key_id.unwrap(); + assert_eq!(org_key_id, key_id); + let verification_key = SharedKey::from_base64(&alg, HMACSHA256_SECRET_KEY).unwrap(); + let verification_res = req.verify_message_signature(&verification_key, Some(&key_id)).await; assert!(verification_res.is_ok()); - let verification_res = req.verify_message_signature(&secret_key, Some("NotFoundKeyId")).await; + let verification_res = req.verify_message_signature(&verification_key, Some("NotFoundKeyId")).await; assert!(verification_res.is_err()); } #[tokio::test] - async fn test_get_key_ids() { + async fn test_get_alg_key_ids() { let mut req = build_request().await; - let secret_key = SecretKey::from_pem(EDDSA_SECRET_KEY).unwrap(); + let secret_key = SecretKey::from_pem(&AlgorithmName::Ed25519, EDDSA_SECRET_KEY).unwrap(); let mut signature_params = HttpSignatureParams::try_new(&build_covered_components_req()).unwrap(); signature_params.set_key_info(&secret_key); req.set_message_signature(&signature_params, &secret_key, None).await.unwrap(); - let key_ids = req.get_key_ids().unwrap(); + let key_ids = req.get_alg_key_ids().unwrap(); assert_eq!(key_ids.len(), 1); - assert_eq!(key_ids[0], "gjrE7ACMxgzYfFHgabgf4kLTg1eKIdsJ94AiFTFj1is="); + assert_eq!(key_ids[0].0.as_ref().unwrap(), &AlgorithmName::Ed25519); + assert_eq!(key_ids[0].1.as_ref().unwrap(), "gjrE7ACMxgzYfFHgabgf4kLTg1eKIdsJ94AiFTFj1is="); } - const P256_SECERT_KEY: &str = r##"-----BEGIN PRIVATE KEY----- + const P256_SECRET_KEY: &str = r##"-----BEGIN PRIVATE KEY----- MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgv7zxW56ojrWwmSo1 4uOdbVhUfj9Jd+5aZIB9u8gtWnihRANCAARGYsMe0CT6pIypwRvoJlLNs4+cTh2K L7fUNb5i6WbKxkpAoO+6T3pMBG5Yw7+8NuGTvvtrZAXduA2giPxQ8zCf @@ -1202,11 +1222,11 @@ ii+31DW+YulmysZKQKDvuk96TARuWMO/vDbhk777a2QF3bgNoIj8UPMwnw== async fn test_set_verify_multiple_signatures() { let mut req = build_request().await; - let secret_key_eddsa = SecretKey::from_pem(EDDSA_SECRET_KEY).unwrap(); + let secret_key_eddsa = SecretKey::from_pem(&AlgorithmName::Ed25519, EDDSA_SECRET_KEY).unwrap(); let mut signature_params_eddsa = HttpSignatureParams::try_new(&build_covered_components_req()).unwrap(); signature_params_eddsa.set_key_info(&secret_key_eddsa); - let secret_key_p256 = SecretKey::from_pem(P256_SECERT_KEY).unwrap(); + let secret_key_p256 = SecretKey::from_pem(&AlgorithmName::EcdsaP256Sha256, P256_SECRET_KEY).unwrap(); let mut signature_params_hmac = HttpSignatureParams::try_new(&build_covered_components_req()).unwrap(); signature_params_hmac.set_key_info(&secret_key_p256); @@ -1217,8 +1237,8 @@ ii+31DW+YulmysZKQKDvuk96TARuWMO/vDbhk777a2QF3bgNoIj8UPMwnw== req.set_message_signatures(params_key_name).await.unwrap(); - let public_key_eddsa = PublicKey::from_pem(EDDSA_PUBLIC_KEY).unwrap(); - let public_key_p256 = PublicKey::from_pem(P256_PUBLIC_KEY).unwrap(); + let public_key_eddsa = PublicKey::from_pem(&AlgorithmName::Ed25519, EDDSA_PUBLIC_KEY).unwrap(); + let public_key_p256 = PublicKey::from_pem(&AlgorithmName::EcdsaP256Sha256, P256_PUBLIC_KEY).unwrap(); let key_id_eddsa = public_key_eddsa.key_id(); let key_id_p256 = public_key_p256.key_id(); @@ -1239,13 +1259,13 @@ ii+31DW+YulmysZKQKDvuk96TARuWMO/vDbhk777a2QF3bgNoIj8UPMwnw== #[test] fn test_blocking_set_verify_message_signature_req() { let mut req = futures::executor::block_on(build_request()); - let secret_key = SecretKey::from_pem(EDDSA_SECRET_KEY).unwrap(); + let secret_key = SecretKey::from_pem(&AlgorithmName::Ed25519, EDDSA_SECRET_KEY).unwrap(); let mut signature_params = HttpSignatureParams::try_new(&build_covered_components_req()).unwrap(); signature_params.set_key_info(&secret_key); req.set_message_signature_sync(&signature_params, &secret_key, None).unwrap(); - let public_key = PublicKey::from_pem(EDDSA_PUBLIC_KEY).unwrap(); + let public_key = PublicKey::from_pem(&AlgorithmName::Ed25519, EDDSA_PUBLIC_KEY).unwrap(); let verification_res = req.verify_message_signature_sync(&public_key, None); assert!(verification_res.is_ok()); } @@ -1255,14 +1275,14 @@ ii+31DW+YulmysZKQKDvuk96TARuWMO/vDbhk777a2QF3bgNoIj8UPMwnw== fn test_blocking_set_verify_message_signature_res() { let req = futures::executor::block_on(build_request()); let mut res = futures::executor::block_on(build_response()); - let secret_key = SecretKey::from_pem(EDDSA_SECRET_KEY).unwrap(); + let secret_key = SecretKey::from_pem(&AlgorithmName::Ed25519, EDDSA_SECRET_KEY).unwrap(); let mut signature_params = HttpSignatureParams::try_new(&build_covered_components_res()).unwrap(); signature_params.set_key_info(&secret_key); res .set_message_signature_sync(&signature_params, &secret_key, None, Some(&req)) .unwrap(); - let public_key = PublicKey::from_pem(EDDSA_PUBLIC_KEY).unwrap(); + let public_key = PublicKey::from_pem(&AlgorithmName::Ed25519, EDDSA_PUBLIC_KEY).unwrap(); let verification_res = res.verify_message_signature_sync(&public_key, None, Some(&req)); assert!(verification_res.is_ok()); } diff --git a/httpsig-hyper/src/lib.rs b/httpsig-hyper/src/lib.rs index b040c21..691a392 100644 --- a/httpsig-hyper/src/lib.rs +++ b/httpsig-hyper/src/lib.rs @@ -122,7 +122,7 @@ MCowBQYDK2VwAyEA1ixMQcxO46PLlgQfYS46ivFd+n0CcDHSKUnuhm3i1O0= let mut req = build_request().await; - let secret_key = SecretKey::from_pem(EDDSA_SECRET_KEY).unwrap(); + let secret_key = SecretKey::from_pem(&AlgorithmName::Ed25519, EDDSA_SECRET_KEY).unwrap(); let covered_components = COVERED_COMPONENTS_REQ .iter() @@ -145,7 +145,9 @@ MCowBQYDK2VwAyEA1ixMQcxO46PLlgQfYS46ivFd+n0CcDHSKUnuhm3i1O0= assert!(signature.starts_with(r##"custom_sig_name=:"##)); // verify without checking key_id - let public_key = PublicKey::from_pem(EDDSA_PUBLIC_KEY).unwrap(); + // get algorithm from signature params + let (alg, _key_id) = req.get_alg_key_ids().unwrap().into_iter().next().unwrap().1; + let public_key = PublicKey::from_pem(&alg.unwrap(), EDDSA_PUBLIC_KEY).unwrap(); let verification_res = req.verify_message_signature(&public_key, None).await; assert!(verification_res.is_ok()); @@ -165,7 +167,7 @@ MCowBQYDK2VwAyEA1ixMQcxO46PLlgQfYS46ivFd+n0CcDHSKUnuhm3i1O0= let req = build_request().await; let mut res = build_response().await; - let secret_key = SecretKey::from_pem(EDDSA_SECRET_KEY).unwrap(); + let secret_key = SecretKey::from_pem(&AlgorithmName::Ed25519, EDDSA_SECRET_KEY).unwrap(); let covered_components = COVERED_COMPONENTS_RES .iter() @@ -187,8 +189,10 @@ MCowBQYDK2VwAyEA1ixMQcxO46PLlgQfYS46ivFd+n0CcDHSKUnuhm3i1O0= assert!(signature_input.starts_with(r##"custom_sig_name=("##)); assert!(signature.starts_with(r##"custom_sig_name=:"##)); - // verify without checking key_id, request must be provided if `req` field param is included - let public_key = PublicKey::from_pem(EDDSA_PUBLIC_KEY).unwrap(); + // verify without checking key_id, request must be provided if `req` field param is included in signature params + // get algorithm from signature params + let (alg, _key_id) = res.get_alg_key_ids().unwrap().into_iter().next().unwrap().1; + let public_key = PublicKey::from_pem(&alg.unwrap(), EDDSA_PUBLIC_KEY).unwrap(); let verification_res = res.verify_message_signature(&public_key, None, Some(&req)).await; assert!(verification_res.is_ok()); let verification_res = res @@ -213,7 +217,7 @@ MCowBQYDK2VwAyEA1ixMQcxO46PLlgQfYS46ivFd+n0CcDHSKUnuhm3i1O0= // show usage of set_message_signature_sync and verify_message_signature_sync let mut req = futures::executor::block_on(build_request()); - let secret_key = SecretKey::from_pem(EDDSA_SECRET_KEY).unwrap(); + let secret_key = SecretKey::from_pem(&AlgorithmName::Ed25519, EDDSA_SECRET_KEY).unwrap(); let covered_components = COVERED_COMPONENTS_REQ .iter() .map(|v| message_component::HttpMessageComponentId::try_from(*v)) @@ -224,7 +228,9 @@ MCowBQYDK2VwAyEA1ixMQcxO46PLlgQfYS46ivFd+n0CcDHSKUnuhm3i1O0= signature_params.set_key_info(&secret_key); // set signature req.set_message_signature_sync(&signature_params, &secret_key, None).unwrap(); - let public_key = PublicKey::from_pem(EDDSA_PUBLIC_KEY).unwrap(); + + let (alg, _key_id) = req.get_alg_key_ids().unwrap().into_iter().next().unwrap().1; + let public_key = PublicKey::from_pem(&alg.unwrap(), EDDSA_PUBLIC_KEY).unwrap(); let verification_res = req.verify_message_signature_sync(&public_key, None); assert!(verification_res.is_ok()); } @@ -235,7 +241,7 @@ MCowBQYDK2VwAyEA1ixMQcxO46PLlgQfYS46ivFd+n0CcDHSKUnuhm3i1O0= // show usage of set_message_signature_sync and verify_message_signature_sync let req = futures::executor::block_on(build_request()); let mut res = futures::executor::block_on(build_response()); - let secret_key = SecretKey::from_pem(EDDSA_SECRET_KEY).unwrap(); + let secret_key = SecretKey::from_pem(&AlgorithmName::Ed25519, EDDSA_SECRET_KEY).unwrap(); let covered_components = COVERED_COMPONENTS_RES .iter() .map(|v| message_component::HttpMessageComponentId::try_from(*v)) @@ -248,7 +254,9 @@ MCowBQYDK2VwAyEA1ixMQcxO46PLlgQfYS46ivFd+n0CcDHSKUnuhm3i1O0= res .set_message_signature_sync(&signature_params, &secret_key, None, Some(&req)) .unwrap(); - let public_key = PublicKey::from_pem(EDDSA_PUBLIC_KEY).unwrap(); + + let (alg, _key_id) = res.get_alg_key_ids().unwrap().into_iter().next().unwrap().1; + let public_key = PublicKey::from_pem(&alg.unwrap(), EDDSA_PUBLIC_KEY).unwrap(); let verification_res = res.verify_message_signature_sync(&public_key, None, Some(&req)); assert!(verification_res.is_ok()); } diff --git a/httpsig/Cargo.toml b/httpsig/Cargo.toml index e798575..b20d835 100644 --- a/httpsig/Cargo.toml +++ b/httpsig/Cargo.toml @@ -12,6 +12,10 @@ rust-version.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +default = [] +rsa-signature = ["rsa"] + [dependencies] thiserror = { version = "2.0.18" } tracing = { version = "0.1.44" } @@ -41,6 +45,12 @@ hmac = { version = "0.12.1" } sha2 = { version = "0.10.9", default-features = false } bytes = { version = "1.11.1" } +# rsa is optional +rsa = { version = "0.10.0-rc.15", default-features = false, optional = true, features = [ + "encoding", + "sha2", +] } + # encoding base64 = { version = "0.22.1" } diff --git a/httpsig/src/crypto/asymmetric.rs b/httpsig/src/crypto/asymmetric.rs index a1e998d..f2a6787 100644 --- a/httpsig/src/crypto/asymmetric.rs +++ b/httpsig/src/crypto/asymmetric.rs @@ -14,6 +14,14 @@ use pkcs8::{der::Decode, Document, PrivateKeyInfo}; use sha2::{Digest, Sha256, Sha384}; use spki::SubjectPublicKeyInfoRef; +#[cfg(feature = "rsa-signature")] +use rsa::{ + pkcs1::{DecodeRsaPrivateKey, DecodeRsaPublicKey, EncodeRsaPublicKey}, + pkcs1v15, pss, + signature::{Keypair, RandomizedSigner, SignatureEncoding, Verifier}, + RsaPrivateKey, RsaPublicKey, +}; + #[allow(non_upper_case_globals, dead_code)] /// Algorithm OIDs mod algorithm_oids { @@ -21,6 +29,9 @@ mod algorithm_oids { pub const EC: &str = "1.2.840.10045.2.1"; /// OID for `id-Ed25519`, if you're curious pub const Ed25519: &str = "1.3.101.112"; + #[cfg(feature = "rsa-signature")] + /// OID for `id-rsaEncryption`, if you're curious + pub const rsaEncryption: &str = "1.2.840.113549.1.1.1"; } #[allow(non_upper_case_globals, dead_code)] /// Params OIDs @@ -42,11 +53,16 @@ pub enum SecretKey { EcdsaP256Sha256(EcSecretKey), /// ed25519 Ed25519(Ed25519SecretKey), + #[cfg(feature = "rsa-signature")] + /// rsa-v1_5-sha256 + RsaV1_5Sha256(pkcs1v15::SigningKey), + #[cfg(feature = "rsa-signature")] + RsaPssSha512(pss::SigningKey), } impl SecretKey { /// from plain bytes - pub fn from_bytes(alg: AlgorithmName, bytes: &[u8]) -> HttpSigResult { + pub fn from_bytes(alg: &AlgorithmName, bytes: &[u8]) -> HttpSigResult { match alg { AlgorithmName::EcdsaP256Sha256 => { debug!("Read P256 private key"); @@ -65,15 +81,29 @@ impl SecretKey { let sk = ed25519_compact::KeyPair::from_seed(ed25519_compact::Seed::new(seed)).sk; Ok(Self::Ed25519(sk)) } + #[cfg(feature = "rsa-signature")] + AlgorithmName::RsaV1_5Sha256 => { + debug!("Read RSA private key"); + // read PrivateKeyInfo.private_key as RsaPrivateKey (RFC 3447), which is DER encoded RSAPrivateKey in PKCS#1 + let sk = RsaPrivateKey::from_pkcs1_der(bytes).map_err(|e| HttpSigError::ParsePrivateKeyError(e.to_string()))?; + Ok(Self::RsaV1_5Sha256(pkcs1v15::SigningKey::::new(sk))) + } + #[cfg(feature = "rsa-signature")] + AlgorithmName::RsaPssSha512 => { + debug!("Read RSA-PSS private key"); + // read PrivateKeyInfo.private_key as RsaPrivateKey (RFC 3447), which is DER encoded RSAPrivateKey in PKCS#1 + let sk = RsaPrivateKey::from_pkcs1_der(bytes).map_err(|e| HttpSigError::ParsePrivateKeyError(e.to_string()))?; + Ok(Self::RsaPssSha512(pss::SigningKey::::new(sk))) + } _ => Err(HttpSigError::ParsePrivateKeyError("Unsupported algorithm".to_string())), } } /// parse der /// Derive secret key from der bytes - pub fn from_der(der: &[u8]) -> HttpSigResult { + pub fn from_der(alg: &AlgorithmName, der: &[u8]) -> HttpSigResult { let pki = PrivateKeyInfo::from_der(der).map_err(|e| HttpSigError::ParsePrivateKeyError(e.to_string()))?; - let (algorithm_name, sk_bytes) = match pki.algorithm.oid.to_string().as_ref() { + let sk_bytes = match pki.algorithm.oid.to_string().as_ref() { // ec algorithm_oids::EC => { let param = pki @@ -85,26 +115,46 @@ impl SecretKey { params_oids::Secp384r1 => AlgorithmName::EcdsaP384Sha384, _ => return Err(HttpSigError::ParsePrivateKeyError("Unsupported curve".to_string())), }; + // assert algorithm + if algorithm_name != *alg { + return Err(HttpSigError::ParsePrivateKeyError("Algorithm mismatch".to_string())); + } let sk_bytes = sec1::EcPrivateKey::try_from(pki.private_key) .map_err(|e| HttpSigError::ParsePrivateKeyError(format!("Error decoding EcPrivateKey: {e}")))? .private_key; - (algorithm_name, sk_bytes) + sk_bytes } // ed25519 - algorithm_oids::Ed25519 => (AlgorithmName::Ed25519, &pki.private_key[2..]), + algorithm_oids::Ed25519 => { + // assert algorithm + if AlgorithmName::Ed25519 != *alg { + return Err(HttpSigError::ParsePrivateKeyError("Algorithm mismatch".to_string())); + } + &pki.private_key[2..] + } + // rsa + #[cfg(feature = "rsa-signature")] + algorithm_oids::rsaEncryption => { + // assert algorithm + match alg { + AlgorithmName::RsaV1_5Sha256 | AlgorithmName::RsaPssSha512 => {} + _ => return Err(HttpSigError::ParsePrivateKeyError("Algorithm mismatch".to_string())), + } + pki.private_key + } _ => return Err(HttpSigError::ParsePrivateKeyError("Unsupported algorithm".to_string())), }; - let sk = Self::from_bytes(algorithm_name, sk_bytes)?; + let sk = Self::from_bytes(alg, sk_bytes)?; Ok(sk) } /// Derive secret key from pem string - pub fn from_pem(pem: &str) -> HttpSigResult { + pub fn from_pem(alg: &AlgorithmName, pem: &str) -> HttpSigResult { let (tag, doc) = Document::from_pem(pem).map_err(|e| HttpSigError::ParsePrivateKeyError(e.to_string()))?; if tag != "PRIVATE KEY" { return Err(HttpSigError::ParsePrivateKeyError("Invalid tag".to_string())); }; - Self::from_der(doc.as_bytes()) + Self::from_der(alg, doc.as_bytes()) } /// Get public key from secret key @@ -113,6 +163,10 @@ impl SecretKey { Self::EcdsaP256Sha256(key) => PublicKey::EcdsaP256Sha256(key.public_key()), Self::EcdsaP384Sha384(key) => PublicKey::EcdsaP384Sha384(key.public_key()), Self::Ed25519(key) => PublicKey::Ed25519(key.public_key()), + #[cfg(feature = "rsa-signature")] + Self::RsaV1_5Sha256(key) => PublicKey::RsaV1_5Sha256(key.verifying_key()), + #[cfg(feature = "rsa-signature")] + Self::RsaPssSha512(key) => PublicKey::RsaPssSha512(key.verifying_key()), } } } @@ -142,6 +196,18 @@ impl super::SigningKey for SecretKey { let sig = sk.sign(data, Some(ed25519_compact::Noise::default())); Ok(sig.as_ref().to_vec()) } + #[cfg(feature = "rsa-signature")] + Self::RsaV1_5Sha256(sk) => { + debug!("Sign RsaV1_5Sha256"); + let sig = sk.sign_with_rng(&mut rand::rng(), data); + Ok(sig.to_vec()) + } + #[cfg(feature = "rsa-signature")] + Self::RsaPssSha512(sk) => { + debug!("Sign RsaPssSha512"); + let sig = sk.sign_with_rng(&mut rand::rng(), data); + Ok(sig.to_vec()) + } } } @@ -181,11 +247,17 @@ pub enum PublicKey { EcdsaP384Sha384(EcPublicKey), /// ed25519 Ed25519(Ed25519PublicKey), + #[cfg(feature = "rsa-signature")] + /// rsa-v1_5-sha256 + RsaV1_5Sha256(pkcs1v15::VerifyingKey), + #[cfg(feature = "rsa-signature")] + /// rsa-pss-sha512 + RsaPssSha512(pss::VerifyingKey), } impl PublicKey { /// from plain bytes - pub fn from_bytes(alg: AlgorithmName, bytes: &[u8]) -> HttpSigResult { + pub fn from_bytes(alg: &AlgorithmName, bytes: &[u8]) -> HttpSigResult { match alg { AlgorithmName::EcdsaP256Sha256 => { debug!("Read P256 public key"); @@ -202,13 +274,27 @@ impl PublicKey { let pk = ed25519_compact::PublicKey::from_slice(bytes).map_err(|e| HttpSigError::ParsePublicKeyError(e.to_string()))?; Ok(Self::Ed25519(pk)) } + #[cfg(feature = "rsa-signature")] + AlgorithmName::RsaV1_5Sha256 => { + debug!("Read RSA public key"); + // read RsaPublicKey in PKCS#1 DER format + let pk = RsaPublicKey::from_pkcs1_der(bytes).map_err(|e| HttpSigError::ParsePublicKeyError(e.to_string()))?; + Ok(Self::RsaV1_5Sha256(pkcs1v15::VerifyingKey::new(pk))) + } + #[cfg(feature = "rsa-signature")] + AlgorithmName::RsaPssSha512 => { + debug!("Read RSA-PSS public key"); + // read RsaPublicKey in PKCS#1 DER format + let pk = RsaPublicKey::from_pkcs1_der(bytes).map_err(|e| HttpSigError::ParsePublicKeyError(e.to_string()))?; + Ok(Self::RsaPssSha512(pss::VerifyingKey::new(pk))) + } _ => Err(HttpSigError::ParsePublicKeyError("Unsupported algorithm".to_string())), } } #[allow(dead_code)] /// Convert from pem string - pub fn from_pem(pem: &str) -> HttpSigResult { + pub fn from_pem(alg: &AlgorithmName, pem: &str) -> HttpSigResult { let (tag, doc) = Document::from_pem(pem).map_err(|e| HttpSigError::ParsePublicKeyError(e.to_string()))?; if tag != "PUBLIC KEY" { return Err(HttpSigError::ParsePublicKeyError("Invalid tag".to_string())); @@ -217,7 +303,7 @@ impl PublicKey { let spki_ref = SubjectPublicKeyInfoRef::from_der(doc.as_bytes()) .map_err(|e| HttpSigError::ParsePublicKeyError(format!("Error decoding SubjectPublicKeyInfo: {e}").to_string()))?; - let (algorithm_name, pk_bytes) = match spki_ref.algorithm.oid.to_string().as_ref() { + let pk_bytes = match spki_ref.algorithm.oid.to_string().as_ref() { // ec algorithm_oids::EC => { let param = spki_ref @@ -229,23 +315,41 @@ impl PublicKey { params_oids::Secp384r1 => AlgorithmName::EcdsaP384Sha384, _ => return Err(HttpSigError::ParsePublicKeyError("Unsupported curve".to_string())), }; - let pk_bytes = spki_ref + // assert algorithm + if algorithm_name != *alg { + return Err(HttpSigError::ParsePublicKeyError("Algorithm mismatch".to_string())); + } + spki_ref .subject_public_key .as_bytes() - .ok_or(HttpSigError::ParsePublicKeyError("Invalid public key".to_string()))?; - (algorithm_name, pk_bytes) + .ok_or(HttpSigError::ParsePublicKeyError("Invalid public key".to_string()))? } // ed25519 - algorithm_oids::Ed25519 => ( - AlgorithmName::Ed25519, + algorithm_oids::Ed25519 => { + // assert algorithm + if AlgorithmName::Ed25519 != *alg { + return Err(HttpSigError::ParsePublicKeyError("Algorithm mismatch".to_string())); + } + spki_ref + .subject_public_key + .as_bytes() + .ok_or(HttpSigError::ParsePublicKeyError("Invalid public key".to_string()))? + } + // rsa + #[cfg(feature = "rsa-signature")] + algorithm_oids::rsaEncryption => { + match alg { + AlgorithmName::RsaV1_5Sha256 | AlgorithmName::RsaPssSha512 => {} + _ => return Err(HttpSigError::ParsePublicKeyError("Algorithm mismatch".to_string())), + } spki_ref .subject_public_key .as_bytes() - .ok_or(HttpSigError::ParsePublicKeyError("Invalid public key".to_string()))?, - ), + .ok_or(HttpSigError::ParsePublicKeyError("Invalid public key".to_string()))? + } _ => return Err(HttpSigError::ParsePublicKeyError("Unsupported algorithm".to_string())), }; - Self::from_bytes(algorithm_name, pk_bytes) + Self::from_bytes(alg, pk_bytes) } } @@ -280,10 +384,27 @@ impl super::VerifyingKey for PublicKey { pk.verify(data, &sig) .map_err(|e| HttpSigError::InvalidSignature(e.to_string())) } + #[cfg(feature = "rsa-signature")] + Self::RsaV1_5Sha256(pk) => { + debug!("Verify RsaV1_5Sha256"); + let sig = pkcs1v15::Signature::try_from(signature).map_err(|e| HttpSigError::ParseSignatureError(e.to_string()))?; + pk.verify(data, &sig) + .map_err(|e| HttpSigError::InvalidSignature(e.to_string())) + } + #[cfg(feature = "rsa-signature")] + Self::RsaPssSha512(pk) => { + debug!("Verify RsaPssSha512"); + let sig = pss::Signature::try_from(signature).map_err(|e| HttpSigError::ParseSignatureError(e.to_string()))?; + pk.verify(data, &sig) + .map_err(|e| HttpSigError::InvalidSignature(e.to_string())) + } } } - /// Create key id + /// Create key id, created by SHA-256 hash of the public key bytes, then encoded in base64 + /// - For ECDSA keys, use the uncompressed SEC1 encoding of the public key point as the byte representation. + /// - For Ed25519 keys, use the raw 32-byte public key. + /// - For RSA keys, use the DER encoding of the RSAPublicKey structure in PKCS#1 format. fn key_id(&self) -> String { use base64::{engine::general_purpose, Engine as _}; @@ -291,6 +412,18 @@ impl super::VerifyingKey for PublicKey { Self::EcdsaP256Sha256(vk) => vk.to_encoded_point(true).as_bytes().to_vec(), Self::EcdsaP384Sha384(vk) => vk.to_encoded_point(true).as_bytes().to_vec(), Self::Ed25519(vk) => vk.as_ref().to_vec(), + #[cfg(feature = "rsa-signature")] + Self::RsaV1_5Sha256(vk) => vk + .as_ref() + .to_pkcs1_der() + .map(|der| der.as_bytes().to_vec()) + .unwrap_or(b"rsa-der-serialization-failed".to_vec()), + #[cfg(feature = "rsa-signature")] + Self::RsaPssSha512(vk) => vk + .as_ref() + .to_pkcs1_der() + .map(|der| der.as_bytes().to_vec()) + .unwrap_or(b"rsa-der-serialization-failed".to_vec()), }; let mut hasher = ::new(); hasher.update(&bytes); @@ -304,6 +437,10 @@ impl super::VerifyingKey for PublicKey { Self::EcdsaP256Sha256(_) => AlgorithmName::EcdsaP256Sha256, Self::EcdsaP384Sha384(_) => AlgorithmName::EcdsaP384Sha384, Self::Ed25519(_) => AlgorithmName::Ed25519, + #[cfg(feature = "rsa-signature")] + Self::RsaV1_5Sha256(_) => AlgorithmName::RsaV1_5Sha256, + #[cfg(feature = "rsa-signature")] + Self::RsaPssSha512(_) => AlgorithmName::RsaPssSha512, } } } @@ -315,7 +452,7 @@ mod tests { use super::*; use std::matches; - const P256_SECERT_KEY: &str = r##"-----BEGIN PRIVATE KEY----- + const P256_SECRET_KEY: &str = r##"-----BEGIN PRIVATE KEY----- MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgv7zxW56ojrWwmSo1 4uOdbVhUfj9Jd+5aZIB9u8gtWnihRANCAARGYsMe0CT6pIypwRvoJlLNs4+cTh2K L7fUNb5i6WbKxkpAoO+6T3pMBG5Yw7+8NuGTvvtrZAXduA2giPxQ8zCf @@ -326,7 +463,7 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERmLDHtAk+qSMqcEb6CZSzbOPnE4d ii+31DW+YulmysZKQKDvuk96TARuWMO/vDbhk777a2QF3bgNoIj8UPMwnw== -----END PUBLIC KEY----- "##; - const P384_SECERT_KEY: &str = r##"-----BEGIN PRIVATE KEY----- + const P384_SECRET_KEY: &str = r##"-----BEGIN PRIVATE KEY----- MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDCPYbeLLlIQKUzVyVGH MeuFp/9o2Lr+4GrI3bsbHuViMMceiuM+8xqzFCSm4Ltl5UyhZANiAARKg3yM+Ltx n4ZptF3hI6Q167crEtPRklCEsRTyWUqy+VrrnM5LU/+fqxVbyniBZHd4vmQVYtjF @@ -349,63 +486,137 @@ MCowBQYDK2VwAyEA1ixMQcxO46PLlgQfYS46ivFd+n0CcDHSKUnuhm3i1O0= -----END PUBLIC KEY----- "##; + #[cfg(feature = "rsa-signature")] + const RSA2048_SECRET_KEY: &str = r##"-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCrjtdIxemmmL9V +wfp7qqwytfRDZqQM6XNWcAi3x+j5dHFFIKKWQktJ7eCTRYQrBwjQs5sb0ieNUYwQ +vTIH53z9PqKl1bCIk/6Pago2JNBQAUP9DSs+zcYYC1TYwPM12mxIqz6tHVBabBuG +49OoqWGgU4J5YkXvjyNFPVK1+dePLalm4/jUJb4VpbppN5NQ9qaqRTB3vQPW9i3D +uy2hefxh7FGfKx9BwrtKcV4JmjN9IjPpdjZTex/8GF3eIiePkHIJS88w8lkC4F03 +06EuaMRs6KyWpj1aof+LvMG6iIRAigc0K//4tTwfKALRky4tW9JAYe3cFACDulu2 +VXKGatq1AgMBAAECggEAH/cOb9XIci0VwXHSLQag7RXv/Dr8qBc7UUiwpyWNaCVl +EX9CLAMQKiczZ91VAftejhxY8zcV/YPLODc4QjbEmB76iTGmodwJW0lju7DiS3Xg +6B5zB1Gp7kL2PSi+aDNZZ7TYicLjfOWVv21lu5BLy2aj8d/4rekapkUFyzhRDLEk +E9/mvztCrLjLXMS6SFXY/rjfwckBT/tACbmgHInzRcoyX75FYyGtOc3w1S1tXEM8 +7j/7EHZf+mNcHlpV5OMw+StVfl1Qwx8eJ9ZW1TmZEoysRe4zj/ej7+wTBSAC7AoA +UVB6G8hVU1NP+KD7Z9/6SvfJGvj8yR1HdBE5BZ54JQKBgQDfkpDEH4EH0pRSd867 +nrijAwnqd4xdP11aOwgrrppxavUWAmd3vmki8k4O3ghkz8MtNd+bTcZNKcl4ioS3 +boFA++wZQuzPBu6dbwlM9QX0VyzKAmGITrcnFrxCk3k8d6r9DzTVrzY8oK7nvo+1 +n9QYtlBs/SyJZl4McEOCV0fsowKBgQDEcO6KkwQPt//Qm6Qb1ImrQQ1kgvOu77iG +R5Gn/FkURJvy+I4g9ANRXmHFTcdMSIS3dzY5Zr4kwa0UYJ/ivQ5QYwzYISiW3kgj +jmoLhxfWOXaO+vGNBXoZb5JkKrT2pnLlbbeiHaur6jfg20T2w2whts4vJ8aI6V3k +HagrXuz4xwKBgQDWhMhZFq109w4QTxrTFYmuCAVkr07ETj9hi5DccQ2J1AnUE3x5 +/f7dZEeXpl3BdUSeRboHR0oF0hmZirerVeG5m7+/wWJ9hvY/o0H2UIhlGZxFPKGe +64B7hiofa2eBqIUtiYC1pAfTho4smMFFkVUuXQiwewBX2hxVrQZpsxu1JwKBgEwH +fXuqvPase1ks9A5Fa2cZzWoqeNArPdrS1mAS/hMnHsiiRLgiWSpkAilQGiO/KYas +oBMFXfBx+WAaqacjDugz/eOkqcYCkB8a3pZJmgMyyF08aMLw7LntgdY85T9VWsDL +fzhCjZADHc9sbjunlTFTRGfh2ChjUhCZHd5zZfo/AoGBANk7kXrHZlAsmEEoeA8R +yVpIaTIu64SzCsn4lWzh02zuSB20uNzYdNYBkHT/JHMvV4ctxjAXjDWI8aYzHaHY +KDYy4jUp2TeTPBpqwS24KzFaFx0y2U99TWrzt6sQJr7Y9NlR7S0znc/L7wwFobjr +XVdlU40OaPP7xs0er/tWVAPY +-----END PRIVATE KEY-----"##; + + #[cfg(feature = "rsa-signature")] + const RSA2048_PUBLIC_KEY: &str = r##"-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq47XSMXpppi/VcH6e6qs +MrX0Q2akDOlzVnAIt8fo+XRxRSCilkJLSe3gk0WEKwcI0LObG9InjVGMEL0yB+d8 +/T6ipdWwiJP+j2oKNiTQUAFD/Q0rPs3GGAtU2MDzNdpsSKs+rR1QWmwbhuPTqKlh +oFOCeWJF748jRT1StfnXjy2pZuP41CW+FaW6aTeTUPamqkUwd70D1vYtw7stoXn8 +YexRnysfQcK7SnFeCZozfSIz6XY2U3sf/Bhd3iInj5ByCUvPMPJZAuBdN9OhLmjE +bOislqY9WqH/i7zBuoiEQIoHNCv/+LU8HygC0ZMuLVvSQGHt3BQAg7pbtlVyhmra +tQIDAQAB +-----END PUBLIC KEY-----"##; + #[test] fn test_from_bytes() { let ed25519_kp = ed25519_compact::KeyPair::from_seed(ed25519_compact::Seed::default()); let ed25519_sk = ed25519_kp.sk.seed().to_vec(); let ed25519_pk = ed25519_kp.pk.as_ref(); - let sk = SecretKey::from_bytes(AlgorithmName::Ed25519, &ed25519_sk).unwrap(); + let sk = SecretKey::from_bytes(&AlgorithmName::Ed25519, &ed25519_sk).unwrap(); assert!(matches!(sk, SecretKey::Ed25519(_))); - let pk = PublicKey::from_bytes(AlgorithmName::Ed25519, ed25519_pk).unwrap(); + let pk = PublicKey::from_bytes(&AlgorithmName::Ed25519, ed25519_pk).unwrap(); assert!(matches!(pk, PublicKey::Ed25519(_))); let mut rng = rand_085::thread_rng(); let es256_sk = p256::ecdsa::SigningKey::random(&mut rng); let es256_pk = es256_sk.verifying_key(); - let sk = SecretKey::from_bytes(AlgorithmName::EcdsaP256Sha256, es256_sk.to_bytes().as_ref()).unwrap(); + let sk = SecretKey::from_bytes(&AlgorithmName::EcdsaP256Sha256, es256_sk.to_bytes().as_ref()).unwrap(); assert!(matches!(sk, SecretKey::EcdsaP256Sha256(_))); let pk_bytes = es256_pk.as_affine().to_bytes(); - let pk = PublicKey::from_bytes(AlgorithmName::EcdsaP256Sha256, pk_bytes.as_ref()).unwrap(); + let pk = PublicKey::from_bytes(&AlgorithmName::EcdsaP256Sha256, pk_bytes.as_ref()).unwrap(); assert!(matches!(pk, PublicKey::EcdsaP256Sha256(_))); } #[test] fn test_from_pem() { - let sk = SecretKey::from_pem(P256_SECERT_KEY).unwrap(); + let sk = SecretKey::from_pem(&AlgorithmName::EcdsaP256Sha256, P256_SECRET_KEY).unwrap(); assert!(matches!(sk, SecretKey::EcdsaP256Sha256(_))); - let pk = PublicKey::from_pem(P256_PUBLIC_KEY).unwrap(); + let pk = PublicKey::from_pem(&AlgorithmName::EcdsaP256Sha256, P256_PUBLIC_KEY).unwrap(); assert!(matches!(pk, PublicKey::EcdsaP256Sha256(_))); - let sk = SecretKey::from_pem(P384_SECERT_KEY).unwrap(); + let sk = SecretKey::from_pem(&AlgorithmName::EcdsaP384Sha384, P384_SECRET_KEY).unwrap(); assert!(matches!(sk, SecretKey::EcdsaP384Sha384(_))); - let pk = PublicKey::from_pem(P384_PUBLIC_KEY).unwrap(); + let pk = PublicKey::from_pem(&AlgorithmName::EcdsaP384Sha384, P384_PUBLIC_KEY).unwrap(); assert!(matches!(pk, PublicKey::EcdsaP384Sha384(_))); - let sk = SecretKey::from_pem(EDDSA_SECRET_KEY).unwrap(); + let sk = SecretKey::from_pem(&AlgorithmName::Ed25519, EDDSA_SECRET_KEY).unwrap(); assert!(matches!(sk, SecretKey::Ed25519(_))); - let pk = PublicKey::from_pem(EDDSA_PUBLIC_KEY).unwrap(); + let pk = PublicKey::from_pem(&AlgorithmName::Ed25519, EDDSA_PUBLIC_KEY).unwrap(); assert!(matches!(pk, PublicKey::Ed25519(_))); } + #[cfg(feature = "rsa-signature")] + #[test] + fn test_from_pem_rsa() { + let sk = SecretKey::from_pem(&AlgorithmName::RsaV1_5Sha256, RSA2048_SECRET_KEY).unwrap(); + assert!(matches!(sk, SecretKey::RsaV1_5Sha256(_))); + let pk = PublicKey::from_pem(&AlgorithmName::RsaV1_5Sha256, RSA2048_PUBLIC_KEY).unwrap(); + assert!(matches!(pk, PublicKey::RsaV1_5Sha256(_))); + + let sk = SecretKey::from_pem(&AlgorithmName::RsaPssSha512, RSA2048_SECRET_KEY).unwrap(); + assert!(matches!(sk, SecretKey::RsaPssSha512(_))); + let pk = PublicKey::from_pem(&AlgorithmName::RsaPssSha512, RSA2048_PUBLIC_KEY).unwrap(); + assert!(matches!(pk, PublicKey::RsaPssSha512(_))); + } + #[test] fn test_sign_verify() { use super::super::{SigningKey, VerifyingKey}; - let sk = SecretKey::from_pem(P256_SECERT_KEY).unwrap(); - let pk = PublicKey::from_pem(P256_PUBLIC_KEY).unwrap(); + let sk = SecretKey::from_pem(&AlgorithmName::EcdsaP256Sha256, P256_SECRET_KEY).unwrap(); + let pk = PublicKey::from_pem(&AlgorithmName::EcdsaP256Sha256, P256_PUBLIC_KEY).unwrap(); let data = b"hello world"; let signature = sk.sign(data).unwrap(); pk.verify(data, &signature).unwrap(); assert!(pk.verify(b"hello", &signature).is_err()); - let sk = SecretKey::from_pem(P384_SECERT_KEY).unwrap(); - let pk = PublicKey::from_pem(P384_PUBLIC_KEY).unwrap(); + let sk = SecretKey::from_pem(&AlgorithmName::EcdsaP384Sha384, P384_SECRET_KEY).unwrap(); + let pk = PublicKey::from_pem(&AlgorithmName::EcdsaP384Sha384, P384_PUBLIC_KEY).unwrap(); let data = b"hello world"; let signature = sk.sign(data).unwrap(); pk.verify(data, &signature).unwrap(); assert!(pk.verify(b"hello", &signature).is_err()); - let sk = SecretKey::from_pem(EDDSA_SECRET_KEY).unwrap(); - let pk = PublicKey::from_pem(EDDSA_PUBLIC_KEY).unwrap(); + let sk = SecretKey::from_pem(&AlgorithmName::Ed25519, EDDSA_SECRET_KEY).unwrap(); + let pk = PublicKey::from_pem(&AlgorithmName::Ed25519, EDDSA_PUBLIC_KEY).unwrap(); + let data = b"hello world"; + let signature = sk.sign(data).unwrap(); + pk.verify(data, &signature).unwrap(); + assert!(pk.verify(b"hello", &signature).is_err()); + } + + #[cfg(feature = "rsa-signature")] + #[test] + fn test_sign_verify_rsa() { + use super::super::{SigningKey, VerifyingKey}; + let sk = SecretKey::from_pem(&AlgorithmName::RsaV1_5Sha256, RSA2048_SECRET_KEY).unwrap(); + let pk = PublicKey::from_pem(&AlgorithmName::RsaV1_5Sha256, RSA2048_PUBLIC_KEY).unwrap(); + let data = b"hello world"; + let signature = sk.sign(data).unwrap(); + pk.verify(data, &signature).unwrap(); + assert!(pk.verify(b"hello", &signature).is_err()); + + let sk = SecretKey::from_pem(&AlgorithmName::RsaPssSha512, RSA2048_SECRET_KEY).unwrap(); + let pk = PublicKey::from_pem(&AlgorithmName::RsaPssSha512, RSA2048_PUBLIC_KEY).unwrap(); let data = b"hello world"; let signature = sk.sign(data).unwrap(); pk.verify(data, &signature).unwrap(); @@ -415,20 +626,36 @@ MCowBQYDK2VwAyEA1ixMQcxO46PLlgQfYS46ivFd+n0CcDHSKUnuhm3i1O0= #[test] fn test_kid() -> HttpSigResult<()> { use super::super::VerifyingKey; - let sk = SecretKey::from_pem(P256_SECERT_KEY)?; - let pk = PublicKey::from_pem(P256_PUBLIC_KEY)?; + let sk = SecretKey::from_pem(&AlgorithmName::EcdsaP256Sha256, P256_SECRET_KEY)?; + let pk = PublicKey::from_pem(&AlgorithmName::EcdsaP256Sha256, P256_PUBLIC_KEY)?; assert_eq!(sk.public_key().key_id(), pk.key_id()); assert_eq!(pk.key_id(), "k34r3Nqfak67bhJSXTjTRo5tCIr1Bsre1cPoJ3LJ9xE="); - let sk = SecretKey::from_pem(P384_SECERT_KEY)?; - let pk = PublicKey::from_pem(P384_PUBLIC_KEY)?; + let sk = SecretKey::from_pem(&AlgorithmName::EcdsaP384Sha384, P384_SECRET_KEY)?; + let pk = PublicKey::from_pem(&AlgorithmName::EcdsaP384Sha384, P384_PUBLIC_KEY)?; assert_eq!(sk.public_key().key_id(), pk.key_id()); assert_eq!(pk.key_id(), "JluSJKLaQsbGcgg1Ves4FfP/Kf7qS11RT88TvU0eNSo="); - let sk = SecretKey::from_pem(EDDSA_SECRET_KEY)?; - let pk = PublicKey::from_pem(EDDSA_PUBLIC_KEY)?; + let sk = SecretKey::from_pem(&AlgorithmName::Ed25519, EDDSA_SECRET_KEY)?; + let pk = PublicKey::from_pem(&AlgorithmName::Ed25519, EDDSA_PUBLIC_KEY)?; assert_eq!(sk.public_key().key_id(), pk.key_id()); assert_eq!(pk.key_id(), "gjrE7ACMxgzYfFHgabgf4kLTg1eKIdsJ94AiFTFj1is="); Ok(()) } + + #[cfg(feature = "rsa-signature")] + #[test] + fn test_kid_rsa() -> HttpSigResult<()> { + use super::super::VerifyingKey; + let sk = SecretKey::from_pem(&AlgorithmName::RsaV1_5Sha256, RSA2048_SECRET_KEY)?; + let pk = PublicKey::from_pem(&AlgorithmName::RsaV1_5Sha256, RSA2048_PUBLIC_KEY)?; + assert_eq!(sk.public_key().key_id(), pk.key_id()); + assert_eq!(pk.key_id(), "NoJFUyf2XUdhrTK66RlrGEemIlr1tOScYVeNVCv+5Ns="); + + let sk = SecretKey::from_pem(&AlgorithmName::RsaPssSha512, RSA2048_SECRET_KEY)?; + let pk = PublicKey::from_pem(&AlgorithmName::RsaPssSha512, RSA2048_PUBLIC_KEY)?; + assert_eq!(sk.public_key().key_id(), pk.key_id()); + assert_eq!(pk.key_id(), "NoJFUyf2XUdhrTK66RlrGEemIlr1tOScYVeNVCv+5Ns="); // same as above nothing changes for RSA + Ok(()) + } } diff --git a/httpsig/src/crypto/mod.rs b/httpsig/src/crypto/mod.rs index 89b129a..ff02cd0 100644 --- a/httpsig/src/crypto/mod.rs +++ b/httpsig/src/crypto/mod.rs @@ -1,26 +1,35 @@ mod asymmetric; mod symmetric; -use crate::error::HttpSigResult; +use crate::error::{HttpSigError, HttpSigResult}; pub use asymmetric::{PublicKey, SecretKey}; pub use symmetric::SharedKey; +#[derive(Debug, PartialEq, Eq)] /// Algorithm names pub enum AlgorithmName { HmacSha256, EcdsaP256Sha256, EcdsaP384Sha384, Ed25519, + #[cfg(feature = "rsa-signature")] + RsaV1_5Sha256, + #[cfg(feature = "rsa-signature")] + RsaPssSha512, } impl AlgorithmName { - pub fn as_str(&self) -> &str { + pub fn as_str(&self) -> &'static str { match self { AlgorithmName::HmacSha256 => "hmac-sha256", AlgorithmName::EcdsaP256Sha256 => "ecdsa-p256-sha256", AlgorithmName::EcdsaP384Sha384 => "ecdsa-p384-sha384", AlgorithmName::Ed25519 => "ed25519", + #[cfg(feature = "rsa-signature")] + AlgorithmName::RsaV1_5Sha256 => "rsa-v1_5-sha256", + #[cfg(feature = "rsa-signature")] + AlgorithmName::RsaPssSha512 => "rsa-pss-sha512", } } } @@ -31,6 +40,24 @@ impl std::fmt::Display for AlgorithmName { } } +impl core::str::FromStr for AlgorithmName { + type Err = HttpSigError; + + fn from_str(s: &str) -> Result { + match s { + "hmac-sha256" => Ok(Self::HmacSha256), + "ecdsa-p256-sha256" => Ok(Self::EcdsaP256Sha256), + "ecdsa-p384-sha384" => Ok(Self::EcdsaP384Sha384), + "ed25519" => Ok(Self::Ed25519), + #[cfg(feature = "rsa-signature")] + "rsa-v1_5-sha256" => Ok(Self::RsaV1_5Sha256), + #[cfg(feature = "rsa-signature")] + "rsa-pss-sha512" => Ok(Self::RsaPssSha512), + _ => Err(HttpSigError::InvalidAlgorithmName(s.to_string())), + } + } +} + /// SigningKey trait pub trait SigningKey { fn sign(&self, data: &[u8]) -> HttpSigResult>; diff --git a/httpsig/src/crypto/symmetric.rs b/httpsig/src/crypto/symmetric.rs index ad36584..c112cb6 100644 --- a/httpsig/src/crypto/symmetric.rs +++ b/httpsig/src/crypto/symmetric.rs @@ -20,10 +20,16 @@ pub enum SharedKey { impl SharedKey { /// Create a new shared key from base64 encoded string - pub fn from_base64(key: &str) -> HttpSigResult { + pub fn from_base64(alg: &AlgorithmName, key: &str) -> HttpSigResult { debug!("Create SharedKey from base64 string"); let key = general_purpose::STANDARD.decode(key)?; - Ok(SharedKey::HmacSha256(key)) + match alg { + AlgorithmName::HmacSha256 => Ok(SharedKey::HmacSha256(key)), + _ => Err(HttpSigError::InvalidAlgorithmName(format!( + "Unsupported algorithm for SharedKey: {}", + alg + ))), + } } } @@ -58,7 +64,8 @@ impl super::VerifyingKey for SharedKey { debug!("Verify HmacSha256"); let mut mac = HmacSha256::new_from_slice(key).unwrap(); mac.update(data); - mac.verify_slice(expected_mac) + mac + .verify_slice(expected_mac) .map_err(|_| HttpSigError::InvalidSignature("Invalid MAC".to_string())) } } diff --git a/httpsig/src/error.rs b/httpsig/src/error.rs index dda6cae..0a342d0 100644 --- a/httpsig/src/error.rs +++ b/httpsig/src/error.rs @@ -57,6 +57,10 @@ pub enum HttpSigError { #[error("Expired signature params: {0}")] ExpiredSignatureParams(String), + /// Invalid algorithm name + #[error("Invalid algorithm name: {0}")] + InvalidAlgorithmName(String), + /* ----- Other errors ----- */ /// NotYetImplemented #[error("Not yet implemented: {0}")] diff --git a/httpsig/src/lib.rs b/httpsig/src/lib.rs index e45b7ea..2eb8512 100644 --- a/httpsig/src/lib.rs +++ b/httpsig/src/lib.rs @@ -57,8 +57,8 @@ Signature: sig-b26=:wqcAqbmYJ2ji2glfAMaRy4gruYYnx2nEFN2HN6jrnDnQCK1u02Gb04v9EDgw #[test] fn test_using_test_vector_ed25519() { - let sk = SecretKey::from_pem(EDDSA_SECRET_KEY).unwrap(); - let pk = PublicKey::from_pem(EDDSA_PUBLIC_KEY).unwrap(); + let sk = SecretKey::from_pem(&AlgorithmName::Ed25519, EDDSA_SECRET_KEY).unwrap(); + let pk = PublicKey::from_pem(&AlgorithmName::Ed25519, EDDSA_PUBLIC_KEY).unwrap(); assert_eq!(pk.key_id(), sk.public_key().key_id()); let data = EDDSA_SIGNATURE_BASE.as_bytes(); @@ -86,7 +86,7 @@ Signature: sig-b26=:wqcAqbmYJ2ji2glfAMaRy4gruYYnx2nEFN2HN6jrnDnQCK1u02Gb04v9EDgw #[test] fn test_using_test_vector_hmac_sha256() { - let sk = SharedKey::from_base64(HMACSHA256_SECRET_KEY).unwrap(); + let sk = SharedKey::from_base64(&AlgorithmName::HmacSha256, HMACSHA256_SECRET_KEY).unwrap(); let data = HMACSHA256_SIGNATURE_BASE.as_bytes(); let binary_signature = general_purpose::STANDARD.decode(HMACSHA256_SIGNATURE_VALUE).unwrap(); @@ -123,8 +123,8 @@ Signature: sig-b26=:wqcAqbmYJ2ji2glfAMaRy4gruYYnx2nEFN2HN6jrnDnQCK1u02Gb04v9EDgw .collect::>(); let signature_base = HttpSignatureBase::try_new(&component_lines, &signature_params).unwrap(); - let sk = SecretKey::from_pem(EDDSA_SECRET_KEY).unwrap(); - let pk = PublicKey::from_pem(EDDSA_PUBLIC_KEY).unwrap(); + let sk = SecretKey::from_pem(&AlgorithmName::Ed25519, EDDSA_SECRET_KEY).unwrap(); + let pk = PublicKey::from_pem(&AlgorithmName::Ed25519, EDDSA_PUBLIC_KEY).unwrap(); let signature_bytes = sk.sign(&signature_base.as_bytes()).unwrap(); let verification_result = pk.verify(&signature_base.as_bytes(), &signature_bytes); @@ -141,7 +141,7 @@ Signature: sig-b26=:wqcAqbmYJ2ji2glfAMaRy4gruYYnx2nEFN2HN6jrnDnQCK1u02Gb04v9EDgw // sender let signature_params = HttpSignatureParams::try_from(SIGNATURE_PARAMS).unwrap(); let signature_base = HttpSignatureBase::try_new(&component_lines, &signature_params).unwrap(); - let sk = SecretKey::from_pem(EDDSA_SECRET_KEY).unwrap(); + let sk = SecretKey::from_pem(&AlgorithmName::Ed25519, EDDSA_SECRET_KEY).unwrap(); let signature_headers = signature_base.build_signature_headers(&sk, Some("sig-b26")).unwrap(); let signature_params_header_string = signature_headers.signature_input_header_value(); let signature_header_string = signature_headers.signature_header_value(); @@ -154,7 +154,7 @@ Signature: sig-b26=:wqcAqbmYJ2ji2glfAMaRy4gruYYnx2nEFN2HN6jrnDnQCK1u02Gb04v9EDgw let received_signature_headers = header_map.get("sig-b26").unwrap(); let received_signature_base = HttpSignatureBase::try_new(&component_lines, received_signature_headers.signature_params()).unwrap(); - let pk = PublicKey::from_pem(EDDSA_PUBLIC_KEY).unwrap(); + let pk = PublicKey::from_pem(&AlgorithmName::Ed25519, EDDSA_PUBLIC_KEY).unwrap(); let verification_result = received_signature_base.verify_signature_headers(&pk, received_signature_headers); assert!(verification_result.is_ok()); } diff --git a/httpsig/src/signature_params.rs b/httpsig/src/signature_params.rs index 788db68..7dba407 100644 --- a/httpsig/src/signature_params.rs +++ b/httpsig/src/signature_params.rs @@ -266,7 +266,7 @@ MCowBQYDK2VwAyEA1ixMQcxO46PLlgQfYS46ivFd+n0CcDHSKUnuhm3i1O0= #[test] fn test_set_key_info() { let mut params = HttpSignatureParams::try_new(&build_covered_components()).unwrap(); - params.set_key_info(&SecretKey::from_pem(EDDSA_SECRET_KEY).unwrap()); + params.set_key_info(&SecretKey::from_pem(&AlgorithmName::Ed25519, EDDSA_SECRET_KEY).unwrap()); assert_eq!(params.keyid, Some(EDDSA_KEY_ID.to_string())); assert_eq!(params.alg, Some("ed25519".to_string())); }