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, pub user_agent: String, pub sign_algorithm: SignAlgorithm, pub headers: BTreeMap, pub stream: Option>, } fn add_common_headers(header: &mut BTreeMap, 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, headers: &BTreeMap, 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::>().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, headers: &BTreeMap, 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::>().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 { 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 { 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 { 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) -> 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 }