httpsig_hyper/
lib.rs

1//! # httpsig-hyper
2//!
3//! `httpsig-hyper` is a crate that provides a convenient API for `Hyper` users to handle HTTP signatures.
4//! This crate extends hyper's https request and response messages with the ability to generate and verify HTTP signatures.
5//! Additionally it also provides a way to set and verify content-digest header.
6
7mod error;
8mod hyper_content_digest;
9mod hyper_http;
10
11// hyper's http specific extension to generate and verify http signature
12
13/// content-digest header name
14const CONTENT_DIGEST_HEADER: &str = "content-digest";
15
16/// content-digest header type
17pub 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/* ----------------------------------------------------------------- */
48#[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 EDDSA_KEY_ID: &str = "gjrE7ACMxgzYfFHgabgf4kLTg1eKIdsJ94AiFTFj1is";
66
67  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    // show usage of set_message_signature and verify_message_signature
104
105    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    // set key information, alg and keyid
117    signature_params.set_key_info(&secret_key);
118
119    // set custom signature name
120    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    // verify without checking key_id
130    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    // verify with checking key_id
135    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    // show usage of set_message_signature and verify_message_signature
146
147    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    // set key information, alg and keyid
160    signature_params.set_key_info(&secret_key);
161
162    // set custom signature name, and `req` field param if needed (e.g., request method, uri, content-digest, etc.) included only in response
163    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    // verify without checking key_id, request must be provided if `req` field param is included
173    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    // verify with checking key_id
182    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}