1use chrono::Utc;
3use ring::hmac;
4use sha1::{Digest, Sha1};
5use std::collections::HashMap;
6use std::str;
7use urlencoding::{decode, encode};
8
9pub struct Signer<'a> {
11 method: &'a str,
12 url_path: &'a str,
13 headers: Option<&'a HashMap<String, String>>,
14 query: Option<&'a HashMap<String, String>>,
15}
16
17impl<'a> Signer<'a> {
18 pub fn new(
19 method: &'a str,
20 url_path: &'a str,
21 headers: Option<&'a HashMap<String, String>>,
22 query: Option<&'a HashMap<String, String>>,
23 ) -> Self {
24 Self {
25 method,
26 url_path,
27 headers,
28 query,
29 }
30 }
31
32 fn get_key_time(&self, valid_seconds: u32) -> String {
33 let start = Utc::now().timestamp();
34 let end = start + valid_seconds as i64;
35 format!("{};{}", start, end)
36 }
37
38 fn get_sign_key(&self, key_time: &str, secret_key: &str) -> String {
39 let key = hmac::Key::new(
40 hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY,
41 secret_key.to_string().as_bytes(),
42 );
43 let signature = hmac::sign(&key, key_time.as_bytes());
44 let s: Vec<String> = signature
45 .as_ref()
46 .iter()
47 .map(|x| format!("{:02x?}", x))
48 .collect();
49 s.join("")
50 }
51
52 fn encode_data(&self, data: &HashMap<String, String>) -> HashMap<String, String> {
53 let mut res = HashMap::new();
54 for (k, v) in data.iter() {
55 res.insert(encode(k).to_string().to_lowercase(), encode(v).to_string());
56 }
57 res
58 }
59
60 fn get_url_param_list(&self) -> String {
61 if let Some(query) = self.query {
62 let mut keys: Vec<String> = Vec::new();
63 let encoded_data = self.encode_data(query);
64 for k in encoded_data.keys() {
65 keys.push(k.to_string());
66 }
67 keys.sort();
68 return keys.join(";");
69 }
70 "".to_string()
71 }
72
73 fn get_http_parameters(&self) -> String {
74 if let Some(query) = self.query {
75 let mut keys: Vec<String> = Vec::new();
76 let encoded_data = self.encode_data(query);
77 for k in encoded_data.keys() {
78 keys.push(k.to_string());
79 }
80 keys.sort();
81 let mut res: Vec<String> = Vec::new();
82 for key in keys {
83 let v = encoded_data.get(&key).unwrap();
84 res.push([key, v.to_string()].join("="));
85 }
86 return res.join("&");
87 }
88 "".to_string()
89 }
90
91 fn get_header_list(&self) -> String {
92 if let Some(headers) = self.headers {
93 let mut keys: Vec<String> = Vec::new();
94 let encoded_data = self.encode_data(headers);
95 for k in encoded_data.keys() {
96 keys.push(k.to_string());
97 }
98 keys.sort();
99 return keys.join(";");
100 }
101 "".to_string()
102 }
103
104 fn get_heades(&self) -> String {
105 if let Some(headers) = self.headers {
106 let mut keys: Vec<String> = Vec::new();
107 let encoded_data = self.encode_data(headers);
108 for k in encoded_data.keys() {
109 keys.push(k.to_string());
110 }
111 keys.sort();
112 let mut res: Vec<String> = Vec::new();
113 for key in keys {
114 let v = encoded_data.get(&key).unwrap();
115 res.push([key, v.to_string()].join("="));
116 }
117 return res.join("&");
118 }
119 "".to_string()
120 }
121
122 fn get_http_string(&self) -> String {
123 let s = [
124 self.method.to_string(),
125 decode(self.url_path).unwrap().to_string(),
126 self.get_http_parameters(),
127 self.get_heades(),
128 ];
129 s.join("\n") + "\n"
130 }
131
132 fn get_string_to_sign(&self, key_time: &'a str) -> String {
133 let mut s = vec!["sha1".to_string(), key_time.to_string()];
134 let http_string = self.get_http_string();
135 let mut hasher = Sha1::new();
136 hasher.update(&http_string);
137 let result = hasher.finalize();
138 let digest: Vec<String> = result
139 .as_slice()
140 .iter()
141 .map(|x| format!("{:02x?}", x))
142 .collect();
143 s.push(digest.join(""));
144 s.join("\n") + "\n"
145 }
146
147 pub fn get_signature(&self, secret_key: &str, secret_id: &str, valid_seconds: u32) -> String {
148 let key_time = self.get_key_time(valid_seconds);
149 let string_to_sign = self.get_string_to_sign(&key_time);
150 let sign_key = self.get_sign_key(&key_time, secret_key);
151 let signature = self.get_sign_key(&string_to_sign, &sign_key);
152 let header_list = self.get_header_list();
153 let param_list = self.get_url_param_list();
154 format!("q-sign-algorithm=sha1&q-ak={}&q-sign-time={}&q-key-time={}&q-header-list={}&q-url-param-list={}&q-signature={}", secret_id, key_time, key_time, header_list, param_list, signature)
155 }
156}
157
158#[cfg(test)]
159mod test {
160 use crate::signer::Signer;
161 use std::collections::HashMap;
162
163 #[test]
164 fn test_get_key_time() {
165 let signer = Signer::new("", "", None, None);
166 println!("{}", signer.get_key_time(100));
167 }
168
169 #[test]
170 fn test_get_url_param_list() {
171 let mut query = HashMap::new();
172 query.insert("a".to_string(), "a ".to_string());
173 query.insert("B".to_string(), " b".to_string());
174 let signer = Signer::new("", "", None, Some(&query));
175 let s = signer.get_url_param_list();
176 assert_eq!(s, "a;b");
177 let s = signer.get_http_parameters();
178 assert_eq!(s, "a=a%20&b=%20b");
179 }
180
181 #[test]
182 fn test_get_http_string() {
183 let mut query = HashMap::new();
184 query.insert("a".to_string(), "a ".to_string());
185 query.insert("B".to_string(), " b".to_string());
186 let mut headers = HashMap::new();
187 headers.insert("h".to_string(), "h".to_string());
188 headers.insert("user-agent".to_string(), "test".to_string());
189 let signer = Signer::new("get", "/path", Some(&headers), Some(&query));
190 assert_eq!(
191 signer.get_http_string(),
192 "get\n/path\na=a%20&b=%20b\nh=h&user-agent=test\n"
193 );
194 assert_eq!(
195 signer.get_string_to_sign("1648999396;1648999496"),
196 "sha1\n1648999396;1648999496\n963bfe30ee40d402ee00506981bab650e72134f6\n"
197 );
198 }
199
200 #[test]
201 fn test_get_signature() {
202 let mut headers = HashMap::new();
203 headers.insert("Content-Type".to_string(), "text/plain".to_string());
204 headers.insert("Content-Length".to_string(), "13".to_string());
205 headers.insert(
206 "Host".to_string(),
207 "examplebucket-1250000000.cos.ap-beijing.myqcloud.com".to_string(),
208 );
209 headers.insert(
210 "Content-MD5".to_string(),
211 "mQ/fVh815F3k6TAUm8m0eg==".to_string(),
212 );
213 headers.insert("x-cos-acl".to_string(), "private".to_string());
214 headers.insert(
215 "x-cos-grant-read".to_string(),
216 "uin=\"100000000011\"".to_string(),
217 );
218 headers.insert(
219 "Date".to_string(),
220 "Thu, 16 May 2019 06:45:51 GMT".to_string(),
221 );
222 let signer = Signer::new(
223 "put",
224 "/exampleobject(%E8%85%BE%E8%AE%AF%E4%BA%91)",
225 Some(&headers),
226 None,
227 );
228 let key_time = "1557989151;1557996351";
229 assert_eq!(
230 signer.get_sign_key(key_time, "BQYIM75p8x0iWVFSIgqEKwFprpRSVHlz"),
231 "eb2519b498b02ac213cb1f3d1a3d27a3b3c9bc5f"
232 );
233
234 assert_eq!(signer.get_url_param_list(), "");
235 assert_eq!(signer.get_http_parameters(), "");
236 assert_eq!(
237 signer.get_header_list(),
238 "content-length;content-md5;content-type;date;host;x-cos-acl;x-cos-grant-read"
239 );
240
241 assert_eq!(signer.get_heades(), "content-length=13&content-md5=mQ%2FfVh815F3k6TAUm8m0eg%3D%3D&content-type=text%2Fplain&date=Thu%2C%2016%20May%202019%2006%3A45%3A51%20GMT&host=examplebucket-1250000000.cos.ap-beijing.myqcloud.com&x-cos-acl=private&x-cos-grant-read=uin%3D%22100000000011%22");
242
243 assert_eq!(signer.get_http_string(), "put\n/exampleobject(腾讯云)\n\ncontent-length=13&content-md5=mQ%2FfVh815F3k6TAUm8m0eg%3D%3D&content-type=text%2Fplain&date=Thu%2C%2016%20May%202019%2006%3A45%3A51%20GMT&host=examplebucket-1250000000.cos.ap-beijing.myqcloud.com&x-cos-acl=private&x-cos-grant-read=uin%3D%22100000000011%22\n");
244
245 assert_eq!(
246 signer.get_string_to_sign(key_time),
247 "sha1\n1557989151;1557996351\n8b2751e77f43a0995d6e9eb9477f4b685cca4172\n"
248 );
249 }
250}