From 6eeef5fb60e1be5f031ec2ce7a90f8397746eb65 Mon Sep 17 00:00:00 2001 From: Jun Kurihara Date: Fri, 13 Feb 2026 22:13:23 +0900 Subject: [PATCH 1/7] feat: support rsassa-pkcs-v1.5-sha256 and rsassa-pss-sha512, braking api changes --- Cargo.toml | 2 +- httpsig-hyper/Cargo.toml | 2 +- httpsig-hyper/README.md | 3 +- httpsig-hyper/examples/hyper-request.rs | 8 +- httpsig-hyper/examples/hyper-response.rs | 8 +- httpsig-hyper/src/hyper_http.rs | 93 ++++--- httpsig-hyper/src/lib.rs | 26 +- httpsig/Cargo.toml | 10 + httpsig/src/crypto/asymmetric.rs | 302 +++++++++++++++++++---- httpsig/src/crypto/mod.rs | 31 ++- httpsig/src/crypto/symmetric.rs | 13 +- httpsig/src/error.rs | 4 + httpsig/src/lib.rs | 14 +- httpsig/src/signature_params.rs | 2 +- 14 files changed, 405 insertions(+), 113 deletions(-) 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/httpsig-hyper/Cargo.toml b/httpsig-hyper/Cargo.toml index e58f8a1..79522e1 100644 --- a/httpsig-hyper/Cargo.toml +++ b/httpsig-hyper/Cargo.toml @@ -18,7 +18,7 @@ blocking = ["futures/executor"] [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, 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> { 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, 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,28 @@ 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> { 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))) + .filter_map(|(name, headers)| { + let alg = headers + .signature_params() + .alg + .clone() + .map(|a| AlgorithmName::from_str(&a)) + .transpose() + .ok() + .flatten(); + let key_id = headers.signature_params().keyid.clone(); + if let (Some(alg), Some(key_id)) = (alg, key_id) { + Some((name.clone(), (alg, key_id))) + } else { + None + } + }) .collect(); Ok(res) } @@ -904,7 +921,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 +1070,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 +1079,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 +1089,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 +1107,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 +1115,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 +1124,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 +1132,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 +1146,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 +1154,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 +1175,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,25 +1183,29 @@ 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; + 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, AlgorithmName::Ed25519); + assert_eq!(key_ids[0].1, "gjrE7ACMxgzYfFHgabgf4kLTg1eKIdsJ94AiFTFj1is="); } const P256_SECERT_KEY: &str = r##"-----BEGIN PRIVATE KEY----- @@ -1202,11 +1223,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_SECERT_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 +1238,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 +1260,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 +1276,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..33b4e55 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, 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, 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, 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, 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..7ed04b4 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 = ["rsasig"] +rsasig = ["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..ad1bfbd 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 = "rsasig")] +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 = "rsasig")] + /// 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 = "rsasig")] + /// rsa-v1_5-sha256 + RsaV1_5Sha256(pkcs1v15::SigningKey), + #[cfg(feature = "rsasig")] + 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 = "rsasig")] + 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 = "rsasig")] + 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,39 @@ 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 = "rsasig")] + algorithm_oids::rsaEncryption => 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 +156,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 = "rsasig")] + Self::RsaV1_5Sha256(key) => PublicKey::RsaV1_5Sha256(key.verifying_key()), + #[cfg(feature = "rsasig")] + Self::RsaPssSha512(key) => PublicKey::RsaPssSha512(key.verifying_key()), } } } @@ -142,6 +189,18 @@ impl super::SigningKey for SecretKey { let sig = sk.sign(data, Some(ed25519_compact::Noise::default())); Ok(sig.as_ref().to_vec()) } + #[cfg(feature = "rsasig")] + Self::RsaV1_5Sha256(sk) => { + debug!("Sign RsaV1_5Sha256"); + let sig = sk.sign_with_rng(&mut rand::rng(), data); + Ok(sig.to_vec()) + } + #[cfg(feature = "rsasig")] + Self::RsaPssSha512(sk) => { + debug!("Sign RsaPssSha512"); + let sig = sk.sign_with_rng(&mut rand::rng(), data); + Ok(sig.to_vec()) + } } } @@ -181,11 +240,17 @@ pub enum PublicKey { EcdsaP384Sha384(EcPublicKey), /// ed25519 Ed25519(Ed25519PublicKey), + #[cfg(feature = "rsasig")] + /// rsa-v1_5-sha256 + RsaV1_5Sha256(pkcs1v15::VerifyingKey), + #[cfg(feature = "rsasig")] + /// 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 +267,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 = "rsasig")] + AlgorithmName::RsaV1_5Sha256 => { + debug!("Read RSA public key"); + // read RsaPublicKey in SubjectPublicKeyInfo format in PKCS#8, which is DER encoded RSAPublicKey in PKCS#1 + 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 = "rsasig")] + AlgorithmName::RsaPssSha512 => { + debug!("Read RSA-PSS public key"); + // read RsaPublicKey in SubjectPublicKeyInfo format in PKCS#8, which is DER encoded RSAPublicKey in PKCS#1 + 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 +296,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 +308,35 @@ 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()))?, - ), + .ok_or(HttpSigError::ParsePublicKeyError("Invalid public key".to_string()))? + } + // rsa + #[cfg(feature = "rsasig")] + algorithm_oids::rsaEncryption => spki_ref + .subject_public_key + .as_bytes() + .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 +371,27 @@ impl super::VerifyingKey for PublicKey { pk.verify(data, &sig) .map_err(|e| HttpSigError::InvalidSignature(e.to_string())) } + #[cfg(feature = "rsasig")] + 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 = "rsasig")] + 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 (SubjectPublicKeyInfo) as defined in PKCS#1. fn key_id(&self) -> String { use base64::{engine::general_purpose, Engine as _}; @@ -291,6 +399,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 = "rsasig")] + Self::RsaV1_5Sha256(vk) => vk + .as_ref() + .to_pkcs1_der() + .map(|der| der.as_bytes().to_vec()) + .unwrap_or_default(), + #[cfg(feature = "rsasig")] + Self::RsaPssSha512(vk) => vk + .as_ref() + .to_pkcs1_der() + .map(|der| der.as_bytes().to_vec()) + .unwrap_or_default(), }; let mut hasher = ::new(); hasher.update(&bytes); @@ -304,6 +424,10 @@ impl super::VerifyingKey for PublicKey { Self::EcdsaP256Sha256(_) => AlgorithmName::EcdsaP256Sha256, Self::EcdsaP384Sha384(_) => AlgorithmName::EcdsaP384Sha384, Self::Ed25519(_) => AlgorithmName::Ed25519, + #[cfg(feature = "rsasig")] + Self::RsaV1_5Sha256(_) => AlgorithmName::RsaV1_5Sha256, + #[cfg(feature = "rsasig")] + Self::RsaPssSha512(_) => AlgorithmName::RsaPssSha512, } } } @@ -315,7 +439,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 +450,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 +473,137 @@ MCowBQYDK2VwAyEA1ixMQcxO46PLlgQfYS46ivFd+n0CcDHSKUnuhm3i1O0= -----END PUBLIC KEY----- "##; + #[cfg(feature = "rsasig")] + 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 = "rsasig")] + 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 = "rsasig")] + #[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 = "rsasig")] + #[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 +613,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 = "rsasig")] + #[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..74c1bb4 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 = "rsasig")] + RsaV1_5Sha256, + #[cfg(feature = "rsasig")] + 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 = "rsasig")] + AlgorithmName::RsaV1_5Sha256 => "rsa-v1_5-sha256", + #[cfg(feature = "rsasig")] + 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 = "rsasig")] + "rsa-v1_5-sha256" => Ok(Self::RsaV1_5Sha256), + #[cfg(feature = "rsasig")] + "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())); } From b3e917394899ea90e325f29c1b71acd9929ed9c5 Mon Sep 17 00:00:00 2001 From: Jun Kurihara Date: Fri, 13 Feb 2026 22:19:47 +0900 Subject: [PATCH 2/7] update docs --- README.md | 30 +++++++++++++++++++++--------- httpsig-hyper/Cargo.toml | 3 ++- httpsig/Cargo.toml | 2 +- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 92f94e2..acc29fe 100644 --- a/README.md +++ b/README.md @@ -17,12 +17,10 @@ This crates provides a basic library [httpsig](./httpsig) and [its extension](./ - [x] Ed25519 - [x] ECDSA-P256 using SHA-256 - [ ] 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., [Mervin 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 79522e1..146ee2d 100644 --- a/httpsig-hyper/Cargo.toml +++ b/httpsig-hyper/Cargo.toml @@ -13,8 +13,9 @@ rust-version.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] -default = ["blocking"] +default = ["blocking", "rsasig"] blocking = ["futures/executor"] +rsasig = ["httpsig/rsasig"] [dependencies] diff --git a/httpsig/Cargo.toml b/httpsig/Cargo.toml index 7ed04b4..04d4bc4 100644 --- a/httpsig/Cargo.toml +++ b/httpsig/Cargo.toml @@ -13,7 +13,7 @@ rust-version.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] -default = ["rsasig"] +default = [] rsasig = ["rsa"] [dependencies] From ea8a07e33de101f2ccd5b33dc07cc9be1056f830 Mon Sep 17 00:00:00 2001 From: Jun Kurihara Date: Fri, 13 Feb 2026 22:21:01 +0900 Subject: [PATCH 3/7] chore: remove rsasig feature from the default --- httpsig-hyper/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httpsig-hyper/Cargo.toml b/httpsig-hyper/Cargo.toml index 146ee2d..a9d4fc4 100644 --- a/httpsig-hyper/Cargo.toml +++ b/httpsig-hyper/Cargo.toml @@ -13,7 +13,7 @@ rust-version.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] -default = ["blocking", "rsasig"] +default = ["blocking"] blocking = ["futures/executor"] rsasig = ["httpsig/rsasig"] From f85034ef977f171d5415c1b13695a4e20d474edc Mon Sep 17 00:00:00 2001 From: Jun Kurihara Date: Fri, 13 Feb 2026 22:36:22 +0900 Subject: [PATCH 4/7] fix: update api signature of get_alg_key_ids --- README.md | 2 +- httpsig-hyper/src/hyper_http.rs | 22 ++++++++++------------ httpsig-hyper/src/lib.rs | 8 ++++---- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index acc29fe..608665e 100644 --- a/README.md +++ b/README.md @@ -127,7 +127,7 @@ async fn verifier(res: &Response, sent_req: &Request) -> HttpSigResult< // 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 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(); diff --git a/httpsig-hyper/src/hyper_http.rs b/httpsig-hyper/src/hyper_http.rs index ed3587d..67e728f 100644 --- a/httpsig-hyper/src/hyper_http.rs +++ b/httpsig-hyper/src/hyper_http.rs @@ -24,7 +24,7 @@ pub trait MessageSignature { fn has_message_signature(&self) -> bool; /// Extract all key ids for signature bases contained in the request headers - fn get_alg_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,7 +243,7 @@ where } /// Extract all signature bases contained in the request headers - fn get_alg_key_ids(&self) -> HyperSigResult> { + fn get_alg_key_ids(&self) -> HyperSigResult, Option)>> { let req_or_res = RequestOrResponse::Request(self); get_alg_key_ids_inner(&req_or_res) } @@ -358,7 +358,7 @@ where } /// Extract all key ids for signature bases contained in the response headers - fn get_alg_key_ids(&self) -> Result, Self::Error> { + fn get_alg_key_ids(&self) -> Result, Option)>, Self::Error> { let req_or_res = RequestOrResponse::Response(self); get_alg_key_ids_inner(&req_or_res) } @@ -596,7 +596,7 @@ fn has_message_signature_inner(headers: &HeaderMap) -> bool { /// get key ids inner function fn get_alg_key_ids_inner( req_or_res: &RequestOrResponse, -) -> HyperSigResult> { +) -> HyperSigResult, Option)>> { let signature_headers_map = extract_signature_headers_with_name(req_or_res)?; let res = signature_headers_map .iter() @@ -610,11 +610,7 @@ fn get_alg_key_ids_inner( .ok() .flatten(); let key_id = headers.signature_params().keyid.clone(); - if let (Some(alg), Some(key_id)) = (alg, key_id) { - Some((name.clone(), (alg, key_id))) - } else { - None - } + Some((name.clone(), (alg, key_id))) }) .collect(); Ok(res) @@ -1185,7 +1181,9 @@ MCowBQYDK2VwAyEA1ixMQcxO46PLlgQfYS46ivFd+n0CcDHSKUnuhm3i1O0= let org_key_id = VerifyingKey::key_id(&secret_key); let (alg, key_id) = req.get_alg_key_ids().unwrap().into_iter().next().unwrap().1; - assert_eq!(org_key_id, *key_id); + 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()); @@ -1204,8 +1202,8 @@ MCowBQYDK2VwAyEA1ixMQcxO46PLlgQfYS46ivFd+n0CcDHSKUnuhm3i1O0= req.set_message_signature(&signature_params, &secret_key, None).await.unwrap(); let key_ids = req.get_alg_key_ids().unwrap(); assert_eq!(key_ids.len(), 1); - assert_eq!(key_ids[0].0, AlgorithmName::Ed25519); - assert_eq!(key_ids[0].1, "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----- diff --git a/httpsig-hyper/src/lib.rs b/httpsig-hyper/src/lib.rs index 33b4e55..691a392 100644 --- a/httpsig-hyper/src/lib.rs +++ b/httpsig-hyper/src/lib.rs @@ -147,7 +147,7 @@ MCowBQYDK2VwAyEA1ixMQcxO46PLlgQfYS46ivFd+n0CcDHSKUnuhm3i1O0= // verify without checking key_id // 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, EDDSA_PUBLIC_KEY).unwrap(); + 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()); @@ -192,7 +192,7 @@ MCowBQYDK2VwAyEA1ixMQcxO46PLlgQfYS46ivFd+n0CcDHSKUnuhm3i1O0= // 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, EDDSA_PUBLIC_KEY).unwrap(); + 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 @@ -230,7 +230,7 @@ MCowBQYDK2VwAyEA1ixMQcxO46PLlgQfYS46ivFd+n0CcDHSKUnuhm3i1O0= req.set_message_signature_sync(&signature_params, &secret_key, None).unwrap(); let (alg, _key_id) = req.get_alg_key_ids().unwrap().into_iter().next().unwrap().1; - let public_key = PublicKey::from_pem(&alg, EDDSA_PUBLIC_KEY).unwrap(); + 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()); } @@ -256,7 +256,7 @@ MCowBQYDK2VwAyEA1ixMQcxO46PLlgQfYS46ivFd+n0CcDHSKUnuhm3i1O0= .unwrap(); let (alg, _key_id) = res.get_alg_key_ids().unwrap().into_iter().next().unwrap().1; - let public_key = PublicKey::from_pem(&alg, EDDSA_PUBLIC_KEY).unwrap(); + 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()); } From c5e161d34c40e0c36d9e82b8f73f169de353899a Mon Sep 17 00:00:00 2001 From: Jun Kurihara Date: Fri, 13 Feb 2026 22:41:08 +0900 Subject: [PATCH 5/7] fix: fix inner process of get_alg_key_ids --- httpsig-hyper/src/hyper_http.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/httpsig-hyper/src/hyper_http.rs b/httpsig-hyper/src/hyper_http.rs index 67e728f..ec041f1 100644 --- a/httpsig-hyper/src/hyper_http.rs +++ b/httpsig-hyper/src/hyper_http.rs @@ -600,7 +600,8 @@ fn get_alg_key_ids_inner( let signature_headers_map = extract_signature_headers_with_name(req_or_res)?; let res = signature_headers_map .iter() - .filter_map(|(name, headers)| { + .map(|(name, headers)| { + // Unknown or unsupported algorithm strings are mapped to None let alg = headers .signature_params() .alg @@ -610,7 +611,7 @@ fn get_alg_key_ids_inner( .ok() .flatten(); let key_id = headers.signature_params().keyid.clone(); - Some((name.clone(), (alg, key_id))) + (name.clone(), (alg, key_id)) }) .collect(); Ok(res) From 1a9aa4227acaa8c371b5faa835fbe2e56e7fcbfb Mon Sep 17 00:00:00 2001 From: Jun Kurihara Date: Sat, 14 Feb 2026 01:03:56 +0900 Subject: [PATCH 6/7] refactor: update several internal processes --- README.md | 6 +-- httpsig-hyper/Cargo.toml | 4 +- httpsig-hyper/src/hyper_http.rs | 4 +- httpsig/Cargo.toml | 2 +- httpsig/src/crypto/asymmetric.rs | 87 ++++++++++++++++++-------------- httpsig/src/crypto/mod.rs | 12 ++--- 6 files changed, 64 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index 608665e..cb32b3b 100644 --- a/README.md +++ b/README.md @@ -16,11 +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 -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., [Mervin 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`. +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`) @@ -127,7 +127,7 @@ async fn verifier(res: &Response, sent_req: &Request) -> HttpSigResult< // 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 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(); diff --git a/httpsig-hyper/Cargo.toml b/httpsig-hyper/Cargo.toml index a9d4fc4..a5bc42a 100644 --- a/httpsig-hyper/Cargo.toml +++ b/httpsig-hyper/Cargo.toml @@ -13,9 +13,9 @@ rust-version.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] -default = ["blocking"] +default = ["blocking", "rsa-signature"] blocking = ["futures/executor"] -rsasig = ["httpsig/rsasig"] +rsa-signature = ["httpsig/rsa-signature"] [dependencies] diff --git a/httpsig-hyper/src/hyper_http.rs b/httpsig-hyper/src/hyper_http.rs index ec041f1..ad9b2ac 100644 --- a/httpsig-hyper/src/hyper_http.rs +++ b/httpsig-hyper/src/hyper_http.rs @@ -1207,7 +1207,7 @@ MCowBQYDK2VwAyEA1ixMQcxO46PLlgQfYS46ivFd+n0CcDHSKUnuhm3i1O0= 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 @@ -1226,7 +1226,7 @@ ii+31DW+YulmysZKQKDvuk96TARuWMO/vDbhk777a2QF3bgNoIj8UPMwnw== 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(&AlgorithmName::EcdsaP256Sha256, 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); diff --git a/httpsig/Cargo.toml b/httpsig/Cargo.toml index 04d4bc4..b20d835 100644 --- a/httpsig/Cargo.toml +++ b/httpsig/Cargo.toml @@ -14,7 +14,7 @@ rust-version.workspace = true [features] default = [] -rsasig = ["rsa"] +rsa-signature = ["rsa"] [dependencies] thiserror = { version = "2.0.18" } diff --git a/httpsig/src/crypto/asymmetric.rs b/httpsig/src/crypto/asymmetric.rs index ad1bfbd..f2a6787 100644 --- a/httpsig/src/crypto/asymmetric.rs +++ b/httpsig/src/crypto/asymmetric.rs @@ -14,7 +14,7 @@ use pkcs8::{der::Decode, Document, PrivateKeyInfo}; use sha2::{Digest, Sha256, Sha384}; use spki::SubjectPublicKeyInfoRef; -#[cfg(feature = "rsasig")] +#[cfg(feature = "rsa-signature")] use rsa::{ pkcs1::{DecodeRsaPrivateKey, DecodeRsaPublicKey, EncodeRsaPublicKey}, pkcs1v15, pss, @@ -29,7 +29,7 @@ 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 = "rsasig")] + #[cfg(feature = "rsa-signature")] /// OID for `id-rsaEncryption`, if you're curious pub const rsaEncryption: &str = "1.2.840.113549.1.1.1"; } @@ -53,10 +53,10 @@ pub enum SecretKey { EcdsaP256Sha256(EcSecretKey), /// ed25519 Ed25519(Ed25519SecretKey), - #[cfg(feature = "rsasig")] + #[cfg(feature = "rsa-signature")] /// rsa-v1_5-sha256 RsaV1_5Sha256(pkcs1v15::SigningKey), - #[cfg(feature = "rsasig")] + #[cfg(feature = "rsa-signature")] RsaPssSha512(pss::SigningKey), } @@ -81,14 +81,14 @@ impl SecretKey { let sk = ed25519_compact::KeyPair::from_seed(ed25519_compact::Seed::new(seed)).sk; Ok(Self::Ed25519(sk)) } - #[cfg(feature = "rsasig")] + #[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 = "rsasig")] + #[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 @@ -133,8 +133,15 @@ impl SecretKey { &pki.private_key[2..] } // rsa - #[cfg(feature = "rsasig")] - algorithm_oids::rsaEncryption => pki.private_key, + #[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(alg, sk_bytes)?; @@ -156,9 +163,9 @@ 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 = "rsasig")] + #[cfg(feature = "rsa-signature")] Self::RsaV1_5Sha256(key) => PublicKey::RsaV1_5Sha256(key.verifying_key()), - #[cfg(feature = "rsasig")] + #[cfg(feature = "rsa-signature")] Self::RsaPssSha512(key) => PublicKey::RsaPssSha512(key.verifying_key()), } } @@ -189,13 +196,13 @@ impl super::SigningKey for SecretKey { let sig = sk.sign(data, Some(ed25519_compact::Noise::default())); Ok(sig.as_ref().to_vec()) } - #[cfg(feature = "rsasig")] + #[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 = "rsasig")] + #[cfg(feature = "rsa-signature")] Self::RsaPssSha512(sk) => { debug!("Sign RsaPssSha512"); let sig = sk.sign_with_rng(&mut rand::rng(), data); @@ -240,10 +247,10 @@ pub enum PublicKey { EcdsaP384Sha384(EcPublicKey), /// ed25519 Ed25519(Ed25519PublicKey), - #[cfg(feature = "rsasig")] + #[cfg(feature = "rsa-signature")] /// rsa-v1_5-sha256 RsaV1_5Sha256(pkcs1v15::VerifyingKey), - #[cfg(feature = "rsasig")] + #[cfg(feature = "rsa-signature")] /// rsa-pss-sha512 RsaPssSha512(pss::VerifyingKey), } @@ -267,17 +274,17 @@ impl PublicKey { let pk = ed25519_compact::PublicKey::from_slice(bytes).map_err(|e| HttpSigError::ParsePublicKeyError(e.to_string()))?; Ok(Self::Ed25519(pk)) } - #[cfg(feature = "rsasig")] + #[cfg(feature = "rsa-signature")] AlgorithmName::RsaV1_5Sha256 => { debug!("Read RSA public key"); - // read RsaPublicKey in SubjectPublicKeyInfo format in PKCS#8, which is DER encoded RSAPublicKey in PKCS#1 + // 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 = "rsasig")] + #[cfg(feature = "rsa-signature")] AlgorithmName::RsaPssSha512 => { debug!("Read RSA-PSS public key"); - // read RsaPublicKey in SubjectPublicKeyInfo format in PKCS#8, which is DER encoded RSAPublicKey in PKCS#1 + // 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))) } @@ -329,11 +336,17 @@ impl PublicKey { .ok_or(HttpSigError::ParsePublicKeyError("Invalid public key".to_string()))? } // rsa - #[cfg(feature = "rsasig")] - algorithm_oids::rsaEncryption => spki_ref - .subject_public_key - .as_bytes() - .ok_or(HttpSigError::ParsePublicKeyError("Invalid public key".to_string()))?, + #[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()))? + } _ => return Err(HttpSigError::ParsePublicKeyError("Unsupported algorithm".to_string())), }; Self::from_bytes(alg, pk_bytes) @@ -371,14 +384,14 @@ impl super::VerifyingKey for PublicKey { pk.verify(data, &sig) .map_err(|e| HttpSigError::InvalidSignature(e.to_string())) } - #[cfg(feature = "rsasig")] + #[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 = "rsasig")] + #[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()))?; @@ -391,7 +404,7 @@ impl super::VerifyingKey for PublicKey { /// 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 (SubjectPublicKeyInfo) as defined in PKCS#1. + /// - 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 _}; @@ -399,18 +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 = "rsasig")] + #[cfg(feature = "rsa-signature")] Self::RsaV1_5Sha256(vk) => vk .as_ref() .to_pkcs1_der() .map(|der| der.as_bytes().to_vec()) - .unwrap_or_default(), - #[cfg(feature = "rsasig")] + .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_default(), + .unwrap_or(b"rsa-der-serialization-failed".to_vec()), }; let mut hasher = ::new(); hasher.update(&bytes); @@ -424,9 +437,9 @@ impl super::VerifyingKey for PublicKey { Self::EcdsaP256Sha256(_) => AlgorithmName::EcdsaP256Sha256, Self::EcdsaP384Sha384(_) => AlgorithmName::EcdsaP384Sha384, Self::Ed25519(_) => AlgorithmName::Ed25519, - #[cfg(feature = "rsasig")] + #[cfg(feature = "rsa-signature")] Self::RsaV1_5Sha256(_) => AlgorithmName::RsaV1_5Sha256, - #[cfg(feature = "rsasig")] + #[cfg(feature = "rsa-signature")] Self::RsaPssSha512(_) => AlgorithmName::RsaPssSha512, } } @@ -473,7 +486,7 @@ MCowBQYDK2VwAyEA1ixMQcxO46PLlgQfYS46ivFd+n0CcDHSKUnuhm3i1O0= -----END PUBLIC KEY----- "##; - #[cfg(feature = "rsasig")] + #[cfg(feature = "rsa-signature")] const RSA2048_SECRET_KEY: &str = r##"-----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCrjtdIxemmmL9V wfp7qqwytfRDZqQM6XNWcAi3x+j5dHFFIKKWQktJ7eCTRYQrBwjQs5sb0ieNUYwQ @@ -503,7 +516,7 @@ KDYy4jUp2TeTPBpqwS24KzFaFx0y2U99TWrzt6sQJr7Y9NlR7S0znc/L7wwFobjr XVdlU40OaPP7xs0er/tWVAPY -----END PRIVATE KEY-----"##; - #[cfg(feature = "rsasig")] + #[cfg(feature = "rsa-signature")] const RSA2048_PUBLIC_KEY: &str = r##"-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq47XSMXpppi/VcH6e6qs MrX0Q2akDOlzVnAIt8fo+XRxRSCilkJLSe3gk0WEKwcI0LObG9InjVGMEL0yB+d8 @@ -552,7 +565,7 @@ tQIDAQAB assert!(matches!(pk, PublicKey::Ed25519(_))); } - #[cfg(feature = "rsasig")] + #[cfg(feature = "rsa-signature")] #[test] fn test_from_pem_rsa() { let sk = SecretKey::from_pem(&AlgorithmName::RsaV1_5Sha256, RSA2048_SECRET_KEY).unwrap(); @@ -591,7 +604,7 @@ tQIDAQAB assert!(pk.verify(b"hello", &signature).is_err()); } - #[cfg(feature = "rsasig")] + #[cfg(feature = "rsa-signature")] #[test] fn test_sign_verify_rsa() { use super::super::{SigningKey, VerifyingKey}; @@ -630,7 +643,7 @@ tQIDAQAB Ok(()) } - #[cfg(feature = "rsasig")] + #[cfg(feature = "rsa-signature")] #[test] fn test_kid_rsa() -> HttpSigResult<()> { use super::super::VerifyingKey; diff --git a/httpsig/src/crypto/mod.rs b/httpsig/src/crypto/mod.rs index 74c1bb4..ff02cd0 100644 --- a/httpsig/src/crypto/mod.rs +++ b/httpsig/src/crypto/mod.rs @@ -13,9 +13,9 @@ pub enum AlgorithmName { EcdsaP256Sha256, EcdsaP384Sha384, Ed25519, - #[cfg(feature = "rsasig")] + #[cfg(feature = "rsa-signature")] RsaV1_5Sha256, - #[cfg(feature = "rsasig")] + #[cfg(feature = "rsa-signature")] RsaPssSha512, } @@ -26,9 +26,9 @@ impl AlgorithmName { AlgorithmName::EcdsaP256Sha256 => "ecdsa-p256-sha256", AlgorithmName::EcdsaP384Sha384 => "ecdsa-p384-sha384", AlgorithmName::Ed25519 => "ed25519", - #[cfg(feature = "rsasig")] + #[cfg(feature = "rsa-signature")] AlgorithmName::RsaV1_5Sha256 => "rsa-v1_5-sha256", - #[cfg(feature = "rsasig")] + #[cfg(feature = "rsa-signature")] AlgorithmName::RsaPssSha512 => "rsa-pss-sha512", } } @@ -49,9 +49,9 @@ impl core::str::FromStr for AlgorithmName { "ecdsa-p256-sha256" => Ok(Self::EcdsaP256Sha256), "ecdsa-p384-sha384" => Ok(Self::EcdsaP384Sha384), "ed25519" => Ok(Self::Ed25519), - #[cfg(feature = "rsasig")] + #[cfg(feature = "rsa-signature")] "rsa-v1_5-sha256" => Ok(Self::RsaV1_5Sha256), - #[cfg(feature = "rsasig")] + #[cfg(feature = "rsa-signature")] "rsa-pss-sha512" => Ok(Self::RsaPssSha512), _ => Err(HttpSigError::InvalidAlgorithmName(s.to_string())), } From 2b0ab651b87d241cf11900b7633b902528162233 Mon Sep 17 00:00:00 2001 From: Jun Kurihara Date: Sat, 14 Feb 2026 01:11:30 +0900 Subject: [PATCH 7/7] chore: make rsa-signature non-default --- httpsig-hyper/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httpsig-hyper/Cargo.toml b/httpsig-hyper/Cargo.toml index a5bc42a..de941b9 100644 --- a/httpsig-hyper/Cargo.toml +++ b/httpsig-hyper/Cargo.toml @@ -13,7 +13,7 @@ rust-version.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] -default = ["blocking", "rsa-signature"] +default = ["blocking"] blocking = ["futures/executor"] rsa-signature = ["httpsig/rsa-signature"]