1mod error;
8mod hyper_content_digest;
9mod hyper_http;
10
11const CONTENT_DIGEST_HEADER: &str = "content-digest";
15
16pub enum ContentDigestType {
18 Sha256,
19 Sha512,
20}
21
22impl std::fmt::Display for ContentDigestType {
23 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24 match self {
25 ContentDigestType::Sha256 => write!(f, "sha-256"),
26 ContentDigestType::Sha512 => write!(f, "sha-512"),
27 }
28 }
29}
30
31impl std::str::FromStr for ContentDigestType {
32 type Err = error::HyperDigestError;
33 fn from_str(s: &str) -> Result<Self, Self::Err> {
34 match s {
35 "sha-256" => Ok(ContentDigestType::Sha256),
36 "sha-512" => Ok(ContentDigestType::Sha512),
37 _ => Err(error::HyperDigestError::InvalidContentDigestType(s.to_string())),
38 }
39 }
40}
41
42pub use error::{HyperDigestError, HyperDigestResult, HyperSigError, HyperSigResult};
43pub use httpsig::prelude;
44pub use hyper_content_digest::{ContentDigest, RequestContentDigest, ResponseContentDigest};
45pub use hyper_http::{MessageSignature, MessageSignatureReq, MessageSignatureRes};
46
47#[cfg(test)]
49mod tests {
50 use super::{prelude::*, *};
51 use http::{Request, Response};
52 use http_body_util::Full;
53 use httpsig::prelude::{PublicKey, SecretKey};
54
55 type BoxBody = http_body_util::combinators::BoxBody<bytes::Bytes, crate::error::HyperDigestError>;
56
57 const EDDSA_SECRET_KEY: &str = r##"-----BEGIN PRIVATE KEY-----
58MC4CAQAwBQYDK2VwBCIEIDSHAE++q1BP7T8tk+mJtS+hLf81B0o6CFyWgucDFN/C
59-----END PRIVATE KEY-----
60"##;
61 const EDDSA_PUBLIC_KEY: &str = r##"-----BEGIN PUBLIC KEY-----
62MCowBQYDK2VwAyEA1ixMQcxO46PLlgQfYS46ivFd+n0CcDHSKUnuhm3i1O0=
63-----END PUBLIC KEY-----
64"##;
65 const COVERED_COMPONENTS_REQ: &[&str] = &["@method", "date", "content-type", "content-digest"];
68 const COVERED_COMPONENTS_RES: &[&str] = &["@status", "\"@method\";req", "date", "content-type", "\"content-digest\";req"];
69
70 async fn build_request() -> Request<BoxBody> {
71 let body = Full::new(&b"{\"hello\": \"world\"}"[..]);
72 let req = Request::builder()
73 .method("GET")
74 .uri("https://example.com/parameters?var=this%20is%20a%20big%0Amultiline%20value&bar=with+plus+whitespace&fa%C3%A7ade%22%3A%20=something")
75 .header("date", "Sun, 09 May 2021 18:30:00 GMT")
76 .header("content-type", "application/json")
77 .header("content-type", "application/json-patch+json")
78 .body(body)
79 .unwrap();
80 req.set_content_digest(&ContentDigestType::Sha256).await.unwrap()
81 }
82
83 async fn build_response() -> Response<BoxBody> {
84 let body = Full::new(&b"{\"hello\": \"world!!\"}"[..]);
85 let res = Response::builder()
86 .status(200)
87 .header("date", "Sun, 09 May 2021 18:30:00 GMT")
88 .header("content-type", "application/json")
89 .header("content-type", "application/json-patch+json")
90 .body(body)
91 .unwrap();
92 res.set_content_digest(&ContentDigestType::Sha256).await.unwrap()
93 }
94
95 #[test]
96 fn test_content_digest_type() {
97 assert_eq!(ContentDigestType::Sha256.to_string(), "sha-256");
98 assert_eq!(ContentDigestType::Sha512.to_string(), "sha-512");
99 }
100
101 #[tokio::test]
102 async fn test_set_verify_request() {
103 let mut req = build_request().await;
106
107 let secret_key = SecretKey::from_pem(EDDSA_SECRET_KEY).unwrap();
108
109 let covered_components = COVERED_COMPONENTS_REQ
110 .iter()
111 .map(|v| message_component::HttpMessageComponentId::try_from(*v))
112 .collect::<Result<Vec<_>, _>>()
113 .unwrap();
114 let mut signature_params = HttpSignatureParams::try_new(&covered_components).unwrap();
115
116 signature_params.set_key_info(&secret_key);
118
119 req
121 .set_message_signature(&signature_params, &secret_key, Some("custom_sig_name"))
122 .await
123 .unwrap();
124 let signature_input = req.headers().get("signature-input").unwrap().to_str().unwrap();
125 let signature = req.headers().get("signature").unwrap().to_str().unwrap();
126 assert!(signature_input.starts_with(r##"custom_sig_name=("##));
127 assert!(signature.starts_with(r##"custom_sig_name=:"##));
128
129 let public_key = PublicKey::from_pem(EDDSA_PUBLIC_KEY).unwrap();
131 let verification_res = req.verify_message_signature(&public_key, None).await;
132 assert!(verification_res.is_ok());
133
134 let key_id = public_key.key_id();
136 let verification_res = req.verify_message_signature(&public_key, Some(&key_id)).await;
137 assert!(verification_res.is_ok());
138
139 let verification_res = req.verify_message_signature(&public_key, Some("NotFoundKeyId")).await;
140 assert!(verification_res.is_err());
141 }
142
143 #[tokio::test]
144 async fn test_set_verify_response() {
145 let req = build_request().await;
148 let mut res = build_response().await;
149
150 let secret_key = SecretKey::from_pem(EDDSA_SECRET_KEY).unwrap();
151
152 let covered_components = COVERED_COMPONENTS_RES
153 .iter()
154 .map(|v| message_component::HttpMessageComponentId::try_from(*v))
155 .collect::<Result<Vec<_>, _>>()
156 .unwrap();
157 let mut signature_params = HttpSignatureParams::try_new(&covered_components).unwrap();
158
159 signature_params.set_key_info(&secret_key);
161
162 res
164 .set_message_signature(&signature_params, &secret_key, Some("custom_sig_name"), Some(&req))
165 .await
166 .unwrap();
167 let signature_input = res.headers().get("signature-input").unwrap().to_str().unwrap();
168 let signature = res.headers().get("signature").unwrap().to_str().unwrap();
169 assert!(signature_input.starts_with(r##"custom_sig_name=("##));
170 assert!(signature.starts_with(r##"custom_sig_name=:"##));
171
172 let public_key = PublicKey::from_pem(EDDSA_PUBLIC_KEY).unwrap();
174 let verification_res = res.verify_message_signature(&public_key, None, Some(&req)).await;
175 assert!(verification_res.is_ok());
176 let verification_res = res
177 .verify_message_signature(&public_key, None, None as Option<&Request<()>>)
178 .await;
179 assert!(verification_res.is_err());
180
181 let key_id = public_key.key_id();
183 let verification_res = res.verify_message_signature(&public_key, Some(&key_id), Some(&req)).await;
184 assert!(verification_res.is_ok());
185
186 let verification_res = res
187 .verify_message_signature(&public_key, Some("NotFoundKeyId"), Some(&req))
188 .await;
189 assert!(verification_res.is_err());
190 }
191}