zenlayercloud_sdk/
signer.rs

1use anyhow::Context;
2use hmac::{Hmac, Mac};
3use log::debug;
4use sha2::{Digest, Sha256};
5use crate::Error;
6
7use crate::credentials::AccessKeyCredential;
8
9type HS256 = Hmac<Sha256>;
10
11pub trait Signer {
12    fn sign_request(&self, req: &mut reqwest::Request) -> Result<(), Error>;
13}
14
15/// [ZC2-HMAC-SHA256](https://docs.console.zenlayer.com/api-reference/api-introduction/instruction/sign) Signer implementation
16pub struct Zc2HS256Signer {
17    algorithm: &'static str,
18    credential: AccessKeyCredential,
19}
20
21impl Zc2HS256Signer {
22    pub fn new(credential: AccessKeyCredential) -> Self {
23        Zc2HS256Signer {
24            algorithm: "ZC2-HMAC-SHA256",
25            credential,
26        }
27    }
28}
29
30impl Signer for Zc2HS256Signer {
31    fn sign_request(&self, req: &mut reqwest::Request) -> Result<(), Error> {
32        use reqwest::header::HeaderValue;
33
34        let canonical_uri = "/";
35        let canonical_query_string = "";
36        let canonical_headers = format!(
37            "content-type:{}\nhost:{}\n",
38            req.headers()["Content-Type"]
39                .to_str()
40                .context("header Content-Type")?,
41            req.headers()["Host"].to_str().context("header Host")?,
42        );
43
44        let signed_headers = "content-type;host";
45
46        let body_bytes = req
47            .body()
48            .context("payload not set")?
49            .as_bytes()
50            .context("failed to read payload")?;
51        let hashed_request_payload = hex_sha256(body_bytes);
52        debug!("payload={}", String::from_utf8_lossy(body_bytes));
53
54        let canonical_request = format!(
55            "{}\n{}\n{}\n{}\n{}\n{}",
56            req.method(),
57            canonical_uri,
58            canonical_query_string,
59            canonical_headers,
60            signed_headers,
61            &hashed_request_payload,
62        );
63
64        let timestamp = req.headers()["x-zc-timestamp"].to_str().context("header x-zc-timestamp")?;
65        let string_to_sign = format!(
66            "{}\n{}\n{}",
67            self.algorithm,
68            &timestamp,
69            hex_sha256(canonical_request.as_bytes()),
70        );
71
72        let mut hs256 =
73            HS256::new_from_slice(&self.credential.access_key_password.as_bytes()).unwrap();
74        hs256.update(string_to_sign.as_bytes());
75        let signature = format!("{:x}", hs256.finalize().into_bytes());
76
77        let authorization = format!(
78            "{} Credential={}, SignedHeaders={}, Signature={}",
79            self.algorithm, self.credential.access_key_id, signed_headers, signature,
80        );
81        req.headers_mut()
82            .insert("Authorization", HeaderValue::from_str(&authorization).context("set header Authorization")?);
83        req.headers_mut().insert(
84            "X-ZC-Signature-Method",
85            HeaderValue::from_str(self.algorithm).context("set header X-ZC-Signature-Method")?,
86        );
87
88        Ok(())
89    }
90}
91
92fn hex_sha256(data: &[u8]) -> String {
93    format!("{:x}", Sha256::digest(data))
94}