1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
mod crypto;
mod message_component;
mod signature_base;
mod signature_params;
mod trace;
mod util;

pub mod prelude {
  pub mod message_component {
    pub use crate::message_component::{
      DerivedComponentName, HttpMessageComponent, HttpMessageComponentId, HttpMessageComponentName, HttpMessageComponentParam,
    };
  }

  pub use crate::{
    crypto::{PublicKey, SecretKey, SharedKey, SigningKey, VerifyingKey},
    signature_base::{HttpSignature, HttpSignatureBase, HttpSignatureHeaders},
    signature_params::HttpSignatureParams,
  };
}

/* ----------------------------------------------------------------- */
#[cfg(test)]
mod tests {

  use super::prelude::*;
  use base64::{engine::general_purpose, Engine as _};
  // params from https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures#name-signing-a-request-using-ed2
  const EDDSA_SECRET_KEY: &str = r##"-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIJ+DYvh6SEqVTm50DFtMDoQikTmiCqirVv9mWG9qfSnF
-----END PRIVATE KEY-----
"##;
  const EDDSA_PUBLIC_KEY: &str = r##"-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAJrQLj5P/89iXES9+vFgrIy29clF9CC/oPPsw3c5D0bs=
-----END PUBLIC KEY-----
"##;
  const SIGNATURE_BASE: &str = r##""date": Tue, 20 Apr 2021 02:07:55 GMT
"@method": POST
"@path": /foo
"@authority": example.com
"content-type": application/json
"content-length": 18
"@signature-params": ("date" "@method" "@path" "@authority" "content-type" "content-length");created=1618884473;keyid="test-key-ed25519""##;
  const SIGNATURE_VALUE: &str = "wqcAqbmYJ2ji2glfAMaRy4gruYYnx2nEFN2HN6jrnDnQCK1u02Gb04v9EDgwUPiu4A0w6vuQv5lIp5WPpBKRCw==";
  const _SIGNATURE_RESULT: &str = r##"Signature-Input: sig-b26=("date" "@method" "@path" "@authority" \
  "content-type" "content-length");created=1618884473\
  ;keyid="test-key-ed25519"
Signature: sig-b26=:wqcAqbmYJ2ji2glfAMaRy4gruYYnx2nEFN2HN6jrnDnQCK1\
  u02Gb04v9EDgwUPiu4A0w6vuQv5lIp5WPpBKRCw==:"##;

  #[test]
  fn test_using_test_vector() {
    let sk = SecretKey::from_pem(EDDSA_SECRET_KEY).unwrap();
    let pk = PublicKey::from_pem(EDDSA_PUBLIC_KEY).unwrap();
    assert_eq!(pk.key_id(), sk.public_key().key_id());

    let data = SIGNATURE_BASE.as_bytes();
    let binary_signature = general_purpose::STANDARD.decode(SIGNATURE_VALUE).unwrap();
    let verification_result = pk.verify(data, &binary_signature);
    assert!(verification_result.is_ok());

    let signature = sk.sign(SIGNATURE_BASE.as_bytes()).unwrap();
    let signature_value = general_purpose::STANDARD.encode(signature);
    // println!("{}", signature_value);
    let signature_bytes = general_purpose::STANDARD.decode(signature_value).unwrap();
    let verification_result = pk.verify(SIGNATURE_BASE.as_bytes(), &signature_bytes);
    assert!(verification_result.is_ok());
  }

  const COMPONENT_LINES: &[&str] = &[
    r##""date": Tue, 20 Apr 2021 02:07:55 GMT"##,
    r##""@method": POST"##,
    r##""@path": /foo"##,
    r##""@authority": example.com"##,
    r##""content-type": application/json"##,
    r##""content-length": 18"##,
  ];
  const SIGNATURE_PARAMS: &str =
    r##"("date" "@method" "@path" "@authority" "content-type" "content-length");created=1618884473;keyid="test-key-ed25519""##;

  #[test]
  fn test_with_directly_using_crypto_api() {
    let signature_params = HttpSignatureParams::try_from(SIGNATURE_PARAMS).unwrap();
    let component_lines = COMPONENT_LINES
      .iter()
      .map(|&line| message_component::HttpMessageComponent::try_from(line).unwrap())
      .collect::<Vec<_>>();

    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 signature_bytes = sk.sign(&signature_base.as_bytes()).unwrap();
    let verification_result = pk.verify(&signature_base.as_bytes(), &signature_bytes);
    assert!(verification_result.is_ok());
  }

  #[test]
  fn test_with_build_signature_api() {
    let component_lines = COMPONENT_LINES
      .iter()
      .map(|&line| message_component::HttpMessageComponent::try_from(line).unwrap())
      .collect::<Vec<_>>();

    // 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 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();

    assert_eq!(signature_params_header_string, format!("sig-b26={}", SIGNATURE_PARAMS));
    assert!(signature_header_string.starts_with("sig-b26=:") && signature_header_string.ends_with(':'));

    // receiver
    let header_map = HttpSignatureHeaders::try_parse(&signature_header_string, &signature_params_header_string).unwrap();
    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 verification_result = received_signature_base.verify_signature_headers(&pk, received_signature_headers);
    assert!(verification_result.is_ok());
  }
}