cos_rust_sdk/
auth.rs

1//! 认证模块
2//!
3//! 实现腾讯云 COS 的签名算法和认证逻辑
4
5use crate::error::{CosError, Result};
6use chrono::{DateTime, Utc};
7use hmac::{Hmac, Mac};
8use sha1::{Digest, Sha1};
9use std::collections::HashMap;
10use url::Url;
11
12type HmacSha1 = Hmac<Sha1>;
13
14/// 认证信息
15#[derive(Debug, Clone)]
16pub struct Auth {
17    pub secret_id: String,
18    pub secret_key: String,
19}
20
21impl Auth {
22    /// 创建新的认证实例
23    pub fn new<S: Into<String>>(secret_id: S, secret_key: S) -> Self {
24        Self {
25            secret_id: secret_id.into(),
26            secret_key: secret_key.into(),
27        }
28    }
29
30    /// 生成授权签名
31    pub fn sign(
32        &self,
33        method: &str,
34        uri: &str,
35        headers: &HashMap<String, String>,
36        params: &HashMap<String, String>,
37        start_time: DateTime<Utc>,
38        end_time: DateTime<Utc>,
39    ) -> Result<String> {
40        // 1. 生成 KeyTime
41        let key_time = format!("{};{}", start_time.timestamp(), end_time.timestamp());
42
43        // 2. 生成 SignKey
44        let sign_key = self.hmac_sha1(&key_time)?;
45
46        // 3. 生成 HttpString
47        let http_string = self.build_http_string(method, uri, headers, params)?;
48
49        // 4. 生成 StringToSign
50        let string_to_sign = format!("sha1\n{}\n{}\n", key_time, self.sha1(&http_string)?);
51
52        // 5. 生成 Signature
53        let signature = self.hmac_sha1_with_key(&string_to_sign, &sign_key)?;
54
55        // 6. 生成 Authorization
56        let authorization = format!(
57            "q-sign-algorithm=sha1&q-ak={}&q-sign-time={}&q-key-time={}&q-header-list={}&q-url-param-list={}&q-signature={}",
58            self.secret_id,
59            key_time,
60            key_time,
61            self.build_header_list(headers),
62            self.build_param_list(params),
63            signature
64        );
65
66        Ok(authorization)
67    }
68
69    /// 构建 HTTP 字符串
70    fn build_http_string(
71        &self,
72        method: &str,
73        uri: &str,
74        headers: &HashMap<String, String>,
75        params: &HashMap<String, String>,
76    ) -> Result<String> {
77        let method = method.to_lowercase();
78        let uri_path = self.encode_uri_path(uri)?;
79        let params_string = self.build_params_string(params);
80        let headers_string = self.build_headers_string(headers);
81
82        Ok(format!(
83            "{}\n{}\n{}\n{}\n",
84            method, uri_path, params_string, headers_string
85        ))
86    }
87
88    /// 编码 URI 路径
89    fn encode_uri_path(&self, uri: &str) -> Result<String> {
90        let url = Url::parse(&format!("http://example.com{}", uri))
91            .map_err(|e| CosError::other(format!("Invalid URI: {}", e)))?;
92        Ok(url.path().to_string())
93    }
94
95    /// 构建参数字符串
96    fn build_params_string(&self, params: &HashMap<String, String>) -> String {
97        let mut sorted_params: Vec<_> = params.iter().collect();
98        sorted_params.sort_by_key(|(k, _)| k.to_lowercase());
99
100        sorted_params
101            .iter()
102            .map(|(k, v)| format!("{}={}", k.to_lowercase(), urlencoding::encode(v)))
103            .collect::<Vec<_>>()
104            .join("&")
105    }
106
107    /// 构建请求头字符串
108    fn build_headers_string(&self, headers: &HashMap<String, String>) -> String {
109        let mut sorted_headers: Vec<_> = headers.iter().collect();
110        sorted_headers.sort_by_key(|(k, _)| k.to_lowercase());
111
112        sorted_headers
113            .iter()
114            .map(|(k, v)| format!("{}={}", k.to_lowercase(), urlencoding::encode(v)))
115            .collect::<Vec<_>>()
116            .join("&")
117    }
118
119    /// 构建请求头列表
120    fn build_header_list(&self, headers: &HashMap<String, String>) -> String {
121        let mut header_keys: Vec<_> = headers.keys().map(|k| k.to_lowercase()).collect();
122        header_keys.sort();
123        header_keys.join(";")
124    }
125
126    /// 构建参数列表
127    fn build_param_list(&self, params: &HashMap<String, String>) -> String {
128        let mut param_keys: Vec<_> = params.keys().map(|k| k.to_lowercase()).collect();
129        param_keys.sort();
130        param_keys.join(";")
131    }
132
133    /// HMAC-SHA1 签名
134    fn hmac_sha1(&self, data: &str) -> Result<String> {
135        self.hmac_sha1_with_key(data, &self.secret_key)
136    }
137
138    /// 使用指定密钥进行 HMAC-SHA1 签名
139    fn hmac_sha1_with_key(&self, data: &str, key: &str) -> Result<String> {
140        let mut mac = HmacSha1::new_from_slice(key.as_bytes())
141            .map_err(|e| CosError::auth(format!("HMAC key error: {}", e)))?;
142        mac.update(data.as_bytes());
143        let result = mac.finalize();
144        Ok(hex::encode(result.into_bytes()))
145    }
146
147    /// SHA1 哈希
148    fn sha1(&self, data: &str) -> Result<String> {
149        let mut hasher = Sha1::new();
150        hasher.update(data.as_bytes());
151        Ok(hex::encode(hasher.finalize()))
152    }
153}
154
155/// URL 编码工具
156mod urlencoding {
157    pub fn encode(input: &str) -> String {
158        url::form_urlencoded::byte_serialize(input.as_bytes()).collect()
159    }
160}
161
162#[cfg(test)]
163mod tests {
164    use super::*;
165    use chrono::TimeZone;
166
167    #[test]
168    fn test_auth_sign() {
169        let auth = Auth::new("test_secret_id", "test_secret_key");
170        let mut headers = HashMap::new();
171        headers.insert("host".to_string(), "example.com".to_string());
172        headers.insert("content-type".to_string(), "application/json".to_string());
173
174        let mut params = HashMap::new();
175        params.insert("param1".to_string(), "value1".to_string());
176
177        let start_time = Utc.timestamp_opt(1234567890, 0).unwrap();
178        let end_time = Utc.timestamp_opt(1234567890 + 3600, 0).unwrap();
179
180        let result = auth.sign("GET", "/test", &headers, &params, start_time, end_time);
181        assert!(result.is_ok());
182    }
183
184    #[test]
185    fn test_build_params_string() {
186        let auth = Auth::new("id", "key");
187        let mut params = HashMap::new();
188        params.insert("b".to_string(), "value2".to_string());
189        params.insert("a".to_string(), "value1".to_string());
190
191        let result = auth.build_params_string(&params);
192        assert_eq!(result, "a=value1&b=value2");
193    }
194}