Skip to content

Commit 19ac193

Browse files
committed
chore(refactor): add tests for validation
1 parent 2b2994a commit 19ac193

1 file changed

Lines changed: 103 additions & 0 deletions

File tree

httpsig-hyper/src/hyper_http.rs

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1477,4 +1477,107 @@ ii+31DW+YulmysZKQKDvuk96TARuWMO/vDbhk777a2QF3bgNoIj8UPMwnw==
14771477
let result = req.set_message_signature_sync(&signature_params, &secret_key, None);
14781478
assert!(result.is_err(), "expected Err when using `@status` on request, got Ok");
14791479
}
1480+
1481+
// ---- RFC 9421 §2.2: Derived component extraction values ----
1482+
1483+
#[test]
1484+
fn test_extract_derived_components_values() {
1485+
let req = build_query_request();
1486+
// URI: https://example.com/path?foo=bar&id=123&x=y
1487+
let req_or_res = RequestOrResponse::Request(&req);
1488+
1489+
// @method (§2.2.1)
1490+
let id = HttpMessageComponentId::try_from("@method").unwrap();
1491+
let c = extract_derived_component(&req_or_res, &id).unwrap();
1492+
assert_eq!(c.to_string(), "\"@method\": GET");
1493+
1494+
// @target-uri (§2.2.2)
1495+
let id = HttpMessageComponentId::try_from("@target-uri").unwrap();
1496+
let c = extract_derived_component(&req_or_res, &id).unwrap();
1497+
assert_eq!(c.to_string(), "\"@target-uri\": https://example.com/path?foo=bar&id=123&x=y");
1498+
1499+
// @authority (§2.2.3)
1500+
let id = HttpMessageComponentId::try_from("@authority").unwrap();
1501+
let c = extract_derived_component(&req_or_res, &id).unwrap();
1502+
assert_eq!(c.to_string(), "\"@authority\": example.com");
1503+
1504+
// @scheme (§2.2.4)
1505+
let id = HttpMessageComponentId::try_from("@scheme").unwrap();
1506+
let c = extract_derived_component(&req_or_res, &id).unwrap();
1507+
assert_eq!(c.to_string(), "\"@scheme\": https");
1508+
1509+
// @path (§2.2.6)
1510+
let id = HttpMessageComponentId::try_from("@path").unwrap();
1511+
let c = extract_derived_component(&req_or_res, &id).unwrap();
1512+
assert_eq!(c.to_string(), "\"@path\": /path");
1513+
1514+
// @query (§2.2.7)
1515+
let id = HttpMessageComponentId::try_from("@query").unwrap();
1516+
let c = extract_derived_component(&req_or_res, &id).unwrap();
1517+
assert_eq!(c.to_string(), "\"@query\": ?foo=bar&id=123&x=y");
1518+
1519+
// @query-param;name="id" (§2.2.8)
1520+
let id = HttpMessageComponentId::try_from("\"@query-param\";name=\"id\"").unwrap();
1521+
let c = extract_derived_component(&req_or_res, &id).unwrap();
1522+
assert_eq!(c.to_string(), "\"@query-param\";name=\"id\": 123");
1523+
}
1524+
1525+
// ---- RFC 9421 §2.4: @query-param;name="...";req on response ----
1526+
1527+
#[tokio::test]
1528+
async fn test_response_with_query_param_req_sign_verify() {
1529+
// Build a request with query params
1530+
let req = build_query_request();
1531+
// Build a response
1532+
let body = Full::new(bytes::Bytes::new()).map_err(|never| match never {}).boxed();
1533+
let mut res: Response<BoxBody> = Response::builder().status(200).body(body).unwrap();
1534+
1535+
// Response signature covering @status + @query-param;name="id";req
1536+
let covered = ["@status", "\"@query-param\";name=\"id\";req"];
1537+
let covered_components = covered
1538+
.iter()
1539+
.map(|v| HttpMessageComponentId::try_from(*v))
1540+
.collect::<Result<Vec<_>, _>>()
1541+
.unwrap();
1542+
1543+
let secret_key = SecretKey::from_pem(&AlgorithmName::Ed25519, EDDSA_SECRET_KEY).unwrap();
1544+
let mut signature_params = HttpSignatureParams::try_new(&covered_components).unwrap();
1545+
signature_params.set_key_info(&secret_key);
1546+
1547+
res
1548+
.set_message_signature(&signature_params, &secret_key, None, Some(&req))
1549+
.await
1550+
.unwrap();
1551+
1552+
assert!(req.headers().get("signature-input").is_none(), "request should not be modified");
1553+
assert!(res.headers().get("signature-input").is_some(), "signature-input header is missing on response");
1554+
assert!(res.headers().get("signature").is_some(), "signature header is missing on response");
1555+
1556+
let public_key = PublicKey::from_pem(&AlgorithmName::Ed25519, EDDSA_PUBLIC_KEY).unwrap();
1557+
let verification_res = res.verify_message_signature(&public_key, None, Some(&req)).await;
1558+
assert!(verification_res.is_ok(), "signature verification failed: {:?}", verification_res.err());
1559+
}
1560+
1561+
// ---- RFC 9421: Response must reject request-derived components without `req` ----
1562+
1563+
#[tokio::test]
1564+
async fn test_response_rejects_derived_component_without_req() {
1565+
let body = Full::new(bytes::Bytes::new()).map_err(|never| match never {}).boxed();
1566+
let mut res: Response<BoxBody> = Response::builder().status(200).body(body).unwrap();
1567+
1568+
// `@method` without `req` on a response — must fail
1569+
let covered = vec![
1570+
HttpMessageComponentId::try_from("@status").unwrap(),
1571+
HttpMessageComponentId::try_from("@method").unwrap(),
1572+
];
1573+
1574+
let secret_key = SecretKey::from_pem(&AlgorithmName::Ed25519, EDDSA_SECRET_KEY).unwrap();
1575+
let mut signature_params = HttpSignatureParams::try_new(&covered).unwrap();
1576+
signature_params.set_key_info(&secret_key);
1577+
1578+
let result = res
1579+
.set_message_signature(&signature_params, &secret_key, None, None as Option<&Request<()>>)
1580+
.await;
1581+
assert!(result.is_err(), "expected Err when using `@method` without `req` on response");
1582+
}
14801583
}

0 commit comments

Comments
 (0)