222 lines
8.4 KiB
Rust
222 lines
8.4 KiB
Rust
use std::collections::BTreeMap;
|
|
use std::sync::atomic::{AtomicU64, Ordering};
|
|
use std::time::SystemTime;
|
|
|
|
use rand::random;
|
|
|
|
use crate::common_util::{join_slices, percent_encode};
|
|
use crate::map_util::BTreeMapAddKv;
|
|
use crate::sign_algorithm::SignAlgorithm;
|
|
use crate::v4::access_key::{AccessKey, DerivedAccessKey};
|
|
use crate::v4::constant::{ALIYUN_V4, ALIYUN_V4_REQUEST, CONTENT_TYPE_APPLICATION_JSON, HEADER_ACCEPT, HEADER_AUTHORIZATION, HEADER_CONTENT_TYPE, HEADER_HOST, HEADER_USER_AGENT, HEADER_X_ACS_PREFIX, HEADER_X_ASC_ACCESS_KEY_ID, HEADER_X_ASC_ACTION, HEADER_X_ASC_CONTENT_SHA256, HEADER_X_ASC_CONTENT_SM3, HEADER_X_ASC_DATE, HEADER_X_ASC_SECURITY_TOKEN, HEADER_X_ASC_SIGNATURE_NONCE, HEADER_X_ASC_VERSION, REGION_CENTER};
|
|
|
|
const SEQ: AtomicU64 = AtomicU64::new(0);
|
|
|
|
struct Request {
|
|
pub version: String,
|
|
pub region: String,
|
|
pub product: String,
|
|
pub action: String,
|
|
pub method: String,
|
|
pub pathname: String,
|
|
pub access_key: Option<AccessKey>,
|
|
pub user_agent: String,
|
|
pub sign_algorithm: SignAlgorithm,
|
|
pub headers: BTreeMap<String, String>,
|
|
pub stream: Option<Vec<u8>>,
|
|
}
|
|
|
|
fn add_common_headers(header: &mut BTreeMap<String, String>, request: &Request) {
|
|
let (ymd, date) = get_timestamp();
|
|
|
|
header.insert_kv(HEADER_HOST, "endpoint"); // TODO
|
|
header.insert_kv(HEADER_X_ASC_VERSION, &request.version);
|
|
header.insert_kv(HEADER_X_ASC_ACTION, &request.action);
|
|
header.insert_kv(HEADER_USER_AGENT, &request.user_agent);
|
|
header.insert_kv(HEADER_X_ASC_DATE, date);
|
|
header.insert_kv(HEADER_X_ASC_SIGNATURE_NONCE, get_nonce());
|
|
header.insert_kv(HEADER_ACCEPT, CONTENT_TYPE_APPLICATION_JSON);
|
|
|
|
// TODO BODY ...
|
|
|
|
// TODO signature ...
|
|
match &request.sign_algorithm {
|
|
SignAlgorithm::Sha1 => panic!("SHA1 in V4 is NOT supported!"),
|
|
SignAlgorithm::Sha256 => {
|
|
header.insert_kv(HEADER_X_ASC_CONTENT_SHA256, "");
|
|
}
|
|
SignAlgorithm::Sm3 => {
|
|
header.insert_kv(HEADER_X_ASC_CONTENT_SM3, "");
|
|
}
|
|
}
|
|
|
|
let ymd = "yyyymmdd";
|
|
let query = BTreeMap::new();
|
|
|
|
if let Some(access_key) = &request.access_key {
|
|
if let Some(security_token) = &access_key.security_token {
|
|
header.insert_kv(HEADER_X_ASC_ACCESS_KEY_ID, &access_key.access_key_id);
|
|
header.insert_kv(HEADER_X_ASC_SECURITY_TOKEN, security_token);
|
|
}
|
|
|
|
let signing_key = get_signing_key(
|
|
&request.sign_algorithm,
|
|
&access_key.access_key_secret,
|
|
ymd,
|
|
&request.region,
|
|
&request.product,
|
|
);
|
|
let derived_access_key = DerivedAccessKey {
|
|
access_key_id: access_key.access_key_id.clone(),
|
|
derived_access_key_secret: signing_key,
|
|
};
|
|
let authorization = get_authorization(
|
|
&request.sign_algorithm,
|
|
&derived_access_key,
|
|
ymd,
|
|
&request.region,
|
|
&request.product,
|
|
&request.pathname,
|
|
&request.method,
|
|
&query,
|
|
header,
|
|
"payload", // TODO payload
|
|
);
|
|
header.insert_kv(HEADER_AUTHORIZATION, authorization);
|
|
}
|
|
}
|
|
|
|
|
|
fn get_timestamp() -> (String, String) {
|
|
// TODO ...
|
|
("yyyymmdd".into(), "yyyy-mm-dd".into())
|
|
}
|
|
|
|
|
|
fn get_nonce() -> String {
|
|
let seq = SEQ.fetch_add(1, Ordering::Relaxed);
|
|
let now = SystemTime::now();
|
|
let rand_bytes: [u8; 32] = random();
|
|
let seed = format!("{}-{:?}-{:?}", seq, now, rand_bytes);
|
|
"nonce-".to_string() + &SignAlgorithm::Sha256.digest(seed.as_bytes())
|
|
}
|
|
|
|
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_v4());
|
|
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_v4());
|
|
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
|
|
}
|