feat: init commit
This commit is contained in:
@@ -6,5 +6,9 @@ edition = "2021"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
hex = "0.4.3"
|
||||||
hmac-sha256 = "1.1.7"
|
hmac-sha256 = "1.1.7"
|
||||||
hmac-sm3 = "0.1.0"
|
hmac-sm3 = "0.1.0"
|
||||||
|
sha256 = "1.4.0"
|
||||||
|
sm3 = "0.4.2"
|
||||||
|
zeroize = "1.6.0"
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
mod v4;
|
||||||
|
// mod v4_algorithm;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
println!("Hello, world!");
|
println!("Hello, world!");
|
||||||
}
|
}
|
||||||
|
|||||||
25
src/v4/access_keys.rs
Normal file
25
src/v4/access_keys.rs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
|
pub struct AccessKey {
|
||||||
|
pub access_key_id: String,
|
||||||
|
pub access_key_secret: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for AccessKey {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.access_key_id.zeroize();
|
||||||
|
self.access_key_secret.zeroize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DerivedAccessKey {
|
||||||
|
pub access_key_id: String,
|
||||||
|
pub derived_access_key_secret: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for DerivedAccessKey {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.access_key_id.zeroize();
|
||||||
|
self.derived_access_key_secret.zeroize();
|
||||||
|
}
|
||||||
|
}
|
||||||
60
src/v4/algorithm.rs
Normal file
60
src/v4/algorithm.rs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
use sm3::{Digest, Sm3};
|
||||||
|
|
||||||
|
use crate::v4::constants::{ACS4_HMAC_SHA256, ACS4_HMAC_SM3};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum SignAlgorithm {
|
||||||
|
Sha256,
|
||||||
|
Sm3,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SignAlgorithm {
|
||||||
|
pub fn digest(&self, messge: &[u8]) -> String {
|
||||||
|
inner_digest(self, messge)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hmac_sign(&self, message: &[u8], key: &[u8]) -> Vec<u8> {
|
||||||
|
inner_hmac_sign(self, message, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_aliyun_name(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
SignAlgorithm::Sha256 => ACS4_HMAC_SHA256,
|
||||||
|
SignAlgorithm::Sm3 => ACS4_HMAC_SM3,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inner_digest(sign_algorithm: &SignAlgorithm, message: &[u8]) -> String {
|
||||||
|
match sign_algorithm {
|
||||||
|
SignAlgorithm::Sha256 => sha256::digest(message),
|
||||||
|
SignAlgorithm::Sm3 => {
|
||||||
|
let mut sm3 = Sm3::new();
|
||||||
|
sm3.update(&message);
|
||||||
|
hex::encode(sm3.finalize().as_slice())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inner_hmac_sign(sign_algorithm: &SignAlgorithm, message: &[u8], key: &[u8]) -> Vec<u8> {
|
||||||
|
match sign_algorithm {
|
||||||
|
SignAlgorithm::Sha256 => {
|
||||||
|
let mut hsha256 = hmac_sha256::HMAC::new(key);
|
||||||
|
hsha256.update(message);
|
||||||
|
hsha256.finalize().to_vec()
|
||||||
|
}
|
||||||
|
SignAlgorithm::Sm3 => hmac_sm3::hmac_sm3(key, message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_digest() {
|
||||||
|
assert_eq!(
|
||||||
|
"a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e",
|
||||||
|
SignAlgorithm::Sha256.digest(b"Hello World")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
"77015816143ee627f4fa410b6dad2bdb9fcbdf1e061a452a686b8711a484c5d7",
|
||||||
|
SignAlgorithm::Sm3.digest(b"Hello World")
|
||||||
|
);
|
||||||
|
}
|
||||||
127
src/v4/aliyun_util.rs
Normal file
127
src/v4/aliyun_util.rs
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
use sm3::Digest;
|
||||||
|
|
||||||
|
use crate::v4::access_keys::DerivedAccessKey;
|
||||||
|
use crate::v4::algorithm::SignAlgorithm;
|
||||||
|
use crate::v4::common_util::{join_slices, percent_encode};
|
||||||
|
use crate::v4::constants::{ALIYUN_V4, ALIYUN_V4_REQUEST, HEADER_CONTENT_TYPE, HEADER_HOST, HEADER_X_ACS_PREFIX, REGION_CENTER};
|
||||||
|
|
||||||
|
fn get_authorization(sign_algorithm: SignAlgorithm,
|
||||||
|
access_key: &DerivedAccessKey,
|
||||||
|
date: &str, region: &str, product: &str,
|
||||||
|
pathname: &str,
|
||||||
|
method: &str,
|
||||||
|
query: &BTreeMap<String, String>,
|
||||||
|
headers: &BTreeMap<String, String>,
|
||||||
|
payload: &str) -> String {
|
||||||
|
let signature = get_signature(sign_algorithm, &access_key.derived_access_key_secret,
|
||||||
|
pathname, method, query, headers, payload);
|
||||||
|
let signed_headers_str = get_signed_headers(headers).iter().map(|(k, _)| k.as_str()).collect::<Vec<_>>().join(";");
|
||||||
|
|
||||||
|
let mut authorization = String::with_capacity(512);
|
||||||
|
authorization.push_str(sign_algorithm.as_aliyun_name());
|
||||||
|
authorization.push_str(" Credential=");
|
||||||
|
authorization.push_str(&access_key.access_key_id);
|
||||||
|
authorization.push('/');
|
||||||
|
authorization.push_str(date);
|
||||||
|
authorization.push('/');
|
||||||
|
authorization.push_str(region);
|
||||||
|
authorization.push('/');
|
||||||
|
authorization.push_str(product);
|
||||||
|
authorization.push_str("/aliyun_v4_request,SignedHeaders=");
|
||||||
|
authorization.push_str(&signed_headers_str);
|
||||||
|
authorization.push_str(",Signature=");
|
||||||
|
authorization.push_str(&signature);
|
||||||
|
authorization
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_signature(sign_algorithm: SignAlgorithm,
|
||||||
|
signing_key: &[u8],
|
||||||
|
pathname: &str,
|
||||||
|
method: &str,
|
||||||
|
query: &BTreeMap<String, String>,
|
||||||
|
headers: &BTreeMap<String, String>,
|
||||||
|
payload: &str) -> String {
|
||||||
|
let canonical_uri = if pathname.is_empty() { "/" } else { pathname };
|
||||||
|
let canonicalized_resource = build_canonicalized_resource(query);
|
||||||
|
let canonicalized_headers = build_canonicalized_headers(headers);
|
||||||
|
let signed_headers_str = get_signed_headers(headers).iter().map(|(k, _)| k.as_str()).collect::<Vec<_>>().join(";");
|
||||||
|
|
||||||
|
let mut string_to_sign = String::new();
|
||||||
|
string_to_sign.push_str(method);
|
||||||
|
string_to_sign.push('\n');
|
||||||
|
string_to_sign.push_str(canonical_uri);
|
||||||
|
string_to_sign.push('\n');
|
||||||
|
string_to_sign.push_str(&canonicalized_resource);
|
||||||
|
string_to_sign.push('\n');
|
||||||
|
string_to_sign.push_str(&canonicalized_headers);
|
||||||
|
string_to_sign.push('\n');
|
||||||
|
string_to_sign.push_str(&signed_headers_str);
|
||||||
|
string_to_sign.push('\n');
|
||||||
|
string_to_sign.push_str(payload);
|
||||||
|
|
||||||
|
let string_to_sign_digest_hex = sign_algorithm.digest(string_to_sign.as_bytes());
|
||||||
|
string_to_sign.push_str(sign_algorithm.as_aliyun_name());
|
||||||
|
string_to_sign.push('\n');
|
||||||
|
string_to_sign.push_str(&string_to_sign_digest_hex);
|
||||||
|
|
||||||
|
hex::encode(sign_algorithm.hmac_sign(string_to_sign.as_bytes(), signing_key))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_signing_key(sign_algorithm: SignAlgorithm, secret: &str, date: &str, region: &str, product: &str) -> Vec<u8> {
|
||||||
|
let sc1 = join_slices(ALIYUN_V4.as_bytes(), secret.as_bytes());
|
||||||
|
let sc2 = sign_algorithm.hmac_sign(date.as_bytes(), &sc1);
|
||||||
|
let sc3 = sign_algorithm.hmac_sign(region.as_bytes(), &sc2);
|
||||||
|
let sc4 = sign_algorithm.hmac_sign(product.as_bytes(), &sc3);
|
||||||
|
sign_algorithm.hmac_sign(ALIYUN_V4_REQUEST.as_bytes(), &sc4)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_region(product: &str, endpoint: &str) -> String {
|
||||||
|
if product.is_empty() || endpoint.is_empty() {
|
||||||
|
return REGION_CENTER.into();
|
||||||
|
}
|
||||||
|
let popcode = product.to_lowercase();
|
||||||
|
let mut region = endpoint.replace(popcode.as_str(), "");
|
||||||
|
region = region.replace("aliyuncs.com", "");
|
||||||
|
region = region.replace(".", "");
|
||||||
|
|
||||||
|
if region.is_empty() { REGION_CENTER.into() } else { region }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_canonicalized_resource(query: &BTreeMap<String, String>) -> String {
|
||||||
|
let mut canonicalized_resources = Vec::with_capacity(query.len());
|
||||||
|
for (key, value) in query {
|
||||||
|
let mut resource = String::with_capacity(key.len() + 1 + value.len());
|
||||||
|
resource.push_str(&percent_encode(key));
|
||||||
|
if !value.is_empty() {
|
||||||
|
resource.push('=');
|
||||||
|
resource.push_str(&percent_encode(value));
|
||||||
|
}
|
||||||
|
canonicalized_resources.push(resource);
|
||||||
|
}
|
||||||
|
canonicalized_resources.join("&")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_canonicalized_headers(headers: &BTreeMap<String, String>) -> String {
|
||||||
|
let mut canonicalized_headers = String::new();
|
||||||
|
let signed_headers = get_signed_headers(headers);
|
||||||
|
for (key, value) in &signed_headers {
|
||||||
|
canonicalized_headers.push_str(key);
|
||||||
|
canonicalized_headers.push(':');
|
||||||
|
canonicalized_headers.push_str(value);
|
||||||
|
canonicalized_headers.push('\n');
|
||||||
|
}
|
||||||
|
canonicalized_headers
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_signed_headers(headers: &BTreeMap<String, String>) -> Vec<(String, &String)> {
|
||||||
|
let mut signed_headers = Vec::with_capacity(headers.len());
|
||||||
|
for (key, value) in headers {
|
||||||
|
let lower_key = key.to_lowercase();
|
||||||
|
if lower_key.starts_with(HEADER_X_ACS_PREFIX) || (lower_key == HEADER_HOST) || (lower_key == HEADER_CONTENT_TYPE) {
|
||||||
|
signed_headers.push((lower_key, value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
signed_headers
|
||||||
|
}
|
||||||
39
src/v4/common_util.rs
Normal file
39
src/v4/common_util.rs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
pub(crate) fn percent_encode(message: &str) -> String {
|
||||||
|
let mut encoded = String::with_capacity(message.len() + 16);
|
||||||
|
message.as_bytes().iter().for_each(|b| {
|
||||||
|
match b {
|
||||||
|
b if (b >= &b'a' && b <= &b'z')
|
||||||
|
|| (b >= &b'A' && b <= &b'Z')
|
||||||
|
|| (b >= &b'0' && b <= &b'9') => encoded.push(*b as char),
|
||||||
|
b'~' | b'_' | b'-' | b'.' => encoded.push(*b as char),
|
||||||
|
_ => encoded.push_str(&format!("%{:X}", b)),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
encoded
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn join_slices(part1: &[u8], part2: &[u8]) -> Vec<u8> {
|
||||||
|
let mut joined = part1.to_vec();
|
||||||
|
joined.extend_from_slice(part2);
|
||||||
|
joined
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_percent_encode() {
|
||||||
|
assert_eq!("0123456789", percent_encode("0123456789"));
|
||||||
|
assert_eq!("abcdefghijklmnopqrstuvwxyz", percent_encode("abcdefghijklmnopqrstuvwxyz"));
|
||||||
|
assert_eq!("ABCDEFGHIJKLMNOPQRSTUVWXYZ", percent_encode("ABCDEFGHIJKLMNOPQRSTUVWXYZ"));
|
||||||
|
assert_eq!("Hello%20World%21", percent_encode("Hello World!"));
|
||||||
|
assert_eq!("~%21%40%23%24%25%5E%26%2A%28%29_%2B-%3D", percent_encode("~!@#$%^&*()_+-="));
|
||||||
|
assert_eq!("%E8%BF%99%E6%98%AF%E4%B8%80%E4%B8%AA%E6%B5%8B%E8%AF%95", percent_encode("这是一个测试"));
|
||||||
|
assert_eq!("%F0%9F%98%84", percent_encode("😄"));
|
||||||
|
assert_eq!("%2C.%2F%3C%3E%3F%3B%27%3A%22%5B%5D%5C%7B%7D%7C", percent_encode(",./<>?;':\"[]\\{}|"));
|
||||||
|
assert_eq!("%21%27%28%29%2A-._~", percent_encode("!'()*-._~"));
|
||||||
|
assert_eq!("%21%23%24%26%27%28%29%2A%2B%2C-.%2F%3A%3B%3D%3F%40_~", percent_encode("!#$&'()*+,-./:;=?@_~"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_join_slices() {
|
||||||
|
assert_eq!(&b"Hello World"[..], &join_slices(b"Hello", b" World")[..])
|
||||||
|
}
|
||||||
18
src/v4/constants.rs
Normal file
18
src/v4/constants.rs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
pub(crate) const ACS4_HMAC_SHA256: &str = "ACS4-HMAC-SHA256";
|
||||||
|
pub(crate) const ACS4_HMAC_SM3: &str = "ACS4-HMAC-SM3";
|
||||||
|
|
||||||
|
pub(crate) const REGION_CENTER: &str = "center";
|
||||||
|
pub(crate) const ALIYUN_V4: &str = "aliyun_v4";
|
||||||
|
pub(crate) const ALIYUN_V4_REQUEST: &str = "aliyun_v4_request";
|
||||||
|
|
||||||
|
pub(crate) const HEADER_X_ACS_PREFIX: &str = "x-acs-";
|
||||||
|
pub(crate) const HEADER_HOST: &str = "host";
|
||||||
|
pub(crate) const HEADER_ACCEPT: &str = "accept";
|
||||||
|
pub(crate) const HEADER_USER_AGENT: &str = "user-agent";
|
||||||
|
pub(crate) const HEADER_CONTENT_TYPE: &str = "content-type";
|
||||||
|
pub(crate) const HEADER_X_ASC_VERSION: &str = "x-acs-version";
|
||||||
|
pub(crate) const HEADER_X_ASC_ACTION: &str = "x-acs-action";
|
||||||
|
pub(crate) const HEADER_X_ASC_DATE: &str = "x-acs-date";
|
||||||
|
pub(crate) const HEADER_X_ASC_SIGNATURE_NONCE: &str = "x-acs-signature-nonce";
|
||||||
|
|
||||||
|
pub(crate) const CONTENT_TYPE_APPLICATION_JSON: &str = "application/json";
|
||||||
5
src/v4/mod.rs
Normal file
5
src/v4/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
mod constants;
|
||||||
|
mod algorithm;
|
||||||
|
mod access_keys;
|
||||||
|
mod common_util;
|
||||||
|
mod aliyun_util;
|
||||||
Reference in New Issue
Block a user