httpsig/
lib.rs

1//! # httpsig
2//!
3//! `httpsig` is a library for raw HTTP Signature.
4//! It provides a simple API for signing and verifying HTTP messages, and does not depends on any HTTP framework.
5//! It simply handles header keys and values in `String` or `&str` as HTTP message components, which means it can be used in any HTTP framework but requires complicated handling of HTTP headers.
6//! We thus recommend to use [`httpsig-hyper`](https://crates.io/crates/httpsig-hyper) for `Hyper`` users, which provides a more convenient API.
7
8mod crypto;
9mod error;
10mod message_component;
11mod signature_base;
12mod signature_params;
13mod trace;
14mod util;
15
16pub mod prelude {
17  pub mod message_component {
18    pub use crate::message_component::{
19      DerivedComponentName, HttpMessageComponent, HttpMessageComponentId, HttpMessageComponentName, HttpMessageComponentParam,
20    };
21  }
22
23  pub use crate::{
24    crypto::{AlgorithmName, PublicKey, SecretKey, SharedKey, SigningKey, VerifyingKey},
25    error::{HttpSigError, HttpSigResult},
26    signature_base::{HttpSignature, HttpSignatureBase, HttpSignatureHeaders, HttpSignatureHeadersMap},
27    signature_params::HttpSignatureParams,
28  };
29}
30
31/* ----------------------------------------------------------------- */
32#[cfg(test)]
33mod tests {
34  use super::prelude::*;
35  use base64::{engine::general_purpose, Engine as _};
36
37  /* ----------------------------------------------------------------- */
38  // params from https://datatracker.ietf.org/doc/html/rfc9421#name-signing-a-request-using-ed2
39  const EDDSA_SECRET_KEY: &str = r##"-----BEGIN PRIVATE KEY-----
40MC4CAQAwBQYDK2VwBCIEIJ+DYvh6SEqVTm50DFtMDoQikTmiCqirVv9mWG9qfSnF
41-----END PRIVATE KEY-----
42"##;
43  const EDDSA_PUBLIC_KEY: &str = r##"-----BEGIN PUBLIC KEY-----
44MCowBQYDK2VwAyEAJrQLj5P/89iXES9+vFgrIy29clF9CC/oPPsw3c5D0bs=
45-----END PUBLIC KEY-----
46"##;
47  const EDDSA_SIGNATURE_BASE: &str = r##""date": Tue, 20 Apr 2021 02:07:55 GMT
48"@method": POST
49"@path": /foo
50"@authority": example.com
51"content-type": application/json
52"content-length": 18
53"@signature-params": ("date" "@method" "@path" "@authority" "content-type" "content-length");created=1618884473;keyid="test-key-ed25519""##;
54  const EDDSA_SIGNATURE_VALUE: &str = "wqcAqbmYJ2ji2glfAMaRy4gruYYnx2nEFN2HN6jrnDnQCK1u02Gb04v9EDgwUPiu4A0w6vuQv5lIp5WPpBKRCw==";
55  const _EDDSA_SIGNATURE_RESULT: &str = r##"Signature-Input: sig-b26=("date" "@method" "@path" "@authority" "content-type" "content-length");created=1618884473;keyid="test-key-ed25519"
56Signature: sig-b26=:wqcAqbmYJ2ji2glfAMaRy4gruYYnx2nEFN2HN6jrnDnQCK1u02Gb04v9EDgwUPiu4A0w6vuQv5lIp5WPpBKRCw==:"##;
57
58  #[test]
59  fn test_using_test_vector_ed25519() {
60    let sk = SecretKey::from_pem(EDDSA_SECRET_KEY).unwrap();
61    let pk = PublicKey::from_pem(EDDSA_PUBLIC_KEY).unwrap();
62    assert_eq!(pk.key_id(), sk.public_key().key_id());
63
64    let data = EDDSA_SIGNATURE_BASE.as_bytes();
65    let binary_signature = general_purpose::STANDARD.decode(EDDSA_SIGNATURE_VALUE).unwrap();
66    let verification_result = pk.verify(data, &binary_signature);
67    assert!(verification_result.is_ok());
68
69    let signature = sk.sign(EDDSA_SIGNATURE_BASE.as_bytes()).unwrap();
70    let signature_value = general_purpose::STANDARD.encode(signature);
71    // println!("{}", signature_value);
72    let signature_bytes = general_purpose::STANDARD.decode(signature_value).unwrap();
73    let verification_result = pk.verify(EDDSA_SIGNATURE_BASE.as_bytes(), &signature_bytes);
74    assert!(verification_result.is_ok());
75  }
76
77  /* ----------------------------------------------------------------- */
78  // params from https://datatracker.ietf.org/doc/html/rfc9421#name-signing-a-request-using-hma
79  const HMACSHA256_SECRET_KEY: &str =
80    r##"uzvJfB4u3N0Jy4T7NZ75MDVcr8zSTInedJtkgcu46YW4XByzNJjxBdtjUkdJPBtbmHhIDi6pcl8jsasjlTMtDQ=="##;
81  const HMACSHA256_SIGNATURE_BASE: &str = r##""date": Tue, 20 Apr 2021 02:07:55 GMT
82"@authority": example.com
83"content-type": application/json
84"@signature-params": ("date" "@authority" "content-type");created=1618884473;keyid="test-shared-secret""##;
85  const HMACSHA256_SIGNATURE_VALUE: &str = r##"pxcQw6G3AjtMBQjwo8XzkZf/bws5LelbaMk5rGIGtE8="##;
86
87  #[test]
88  fn test_using_test_vector_hmac_sha256() {
89    let sk = SharedKey::from_base64(HMACSHA256_SECRET_KEY).unwrap();
90
91    let data = HMACSHA256_SIGNATURE_BASE.as_bytes();
92    let binary_signature = general_purpose::STANDARD.decode(HMACSHA256_SIGNATURE_VALUE).unwrap();
93    let verification_result = sk.verify(data, &binary_signature);
94    assert!(verification_result.is_ok());
95
96    let signature = sk.sign(HMACSHA256_SIGNATURE_BASE.as_bytes()).unwrap();
97    let signature_value = general_purpose::STANDARD.encode(signature);
98    assert_eq!(signature_value, HMACSHA256_SIGNATURE_VALUE.to_string());
99
100    let signature_bytes = general_purpose::STANDARD.decode(signature_value).unwrap();
101    let verification_result = sk.verify(HMACSHA256_SIGNATURE_BASE.as_bytes(), &signature_bytes);
102    assert!(verification_result.is_ok());
103  }
104
105  /* ----------------------------------------------------------------- */
106  const COMPONENT_LINES: &[&str] = &[
107    r##""date": Tue, 20 Apr 2021 02:07:55 GMT"##,
108    r##""@method": POST"##,
109    r##""@path": /foo"##,
110    r##""@authority": example.com"##,
111    r##""content-type": application/json"##,
112    r##""content-length": 18"##,
113  ];
114  const SIGNATURE_PARAMS: &str =
115    r##"("date" "@method" "@path" "@authority" "content-type" "content-length");created=1618884473;keyid="test-key-ed25519""##;
116
117  #[test]
118  fn test_with_directly_using_crypto_api() {
119    let signature_params = HttpSignatureParams::try_from(SIGNATURE_PARAMS).unwrap();
120    let component_lines = COMPONENT_LINES
121      .iter()
122      .map(|&line| message_component::HttpMessageComponent::try_from(line).unwrap())
123      .collect::<Vec<_>>();
124
125    let signature_base = HttpSignatureBase::try_new(&component_lines, &signature_params).unwrap();
126    let sk = SecretKey::from_pem(EDDSA_SECRET_KEY).unwrap();
127    let pk = PublicKey::from_pem(EDDSA_PUBLIC_KEY).unwrap();
128
129    let signature_bytes = sk.sign(&signature_base.as_bytes()).unwrap();
130    let verification_result = pk.verify(&signature_base.as_bytes(), &signature_bytes);
131    assert!(verification_result.is_ok());
132  }
133
134  #[test]
135  fn test_with_build_signature_api() {
136    let component_lines = COMPONENT_LINES
137      .iter()
138      .map(|&line| message_component::HttpMessageComponent::try_from(line).unwrap())
139      .collect::<Vec<_>>();
140
141    // sender
142    let signature_params = HttpSignatureParams::try_from(SIGNATURE_PARAMS).unwrap();
143    let signature_base = HttpSignatureBase::try_new(&component_lines, &signature_params).unwrap();
144    let sk = SecretKey::from_pem(EDDSA_SECRET_KEY).unwrap();
145    let signature_headers = signature_base.build_signature_headers(&sk, Some("sig-b26")).unwrap();
146    let signature_params_header_string = signature_headers.signature_input_header_value();
147    let signature_header_string = signature_headers.signature_header_value();
148
149    assert_eq!(signature_params_header_string, format!("sig-b26={}", SIGNATURE_PARAMS));
150    assert!(signature_header_string.starts_with("sig-b26=:") && signature_header_string.ends_with(':'));
151
152    // receiver
153    let header_map = HttpSignatureHeaders::try_parse(&signature_header_string, &signature_params_header_string).unwrap();
154    let received_signature_headers = header_map.get("sig-b26").unwrap();
155    let received_signature_base =
156      HttpSignatureBase::try_new(&component_lines, received_signature_headers.signature_params()).unwrap();
157    let pk = PublicKey::from_pem(EDDSA_PUBLIC_KEY).unwrap();
158    let verification_result = received_signature_base.verify_signature_headers(&pk, received_signature_headers);
159    assert!(verification_result.is_ok());
160  }
161}