// MinIO Rust Library for Amazon S3 Compatible Cloud Storage // Copyright 2022 MinIO, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use crate::s3::utils::{ get_canonical_headers, get_canonical_query_string, sha256_hash, to_amz_date, to_signer_date, Multimap, UtcTime, }; use hex::encode as hexencode; use hmac::{Hmac, Mac}; use hyper::http::Method; use sha2::Sha256; pub fn hmac_hash(key: &[u8], data: &[u8]) -> Vec { let mut hasher = Hmac::::new_from_slice(key).expect("HMAC can take key of any size"); hasher.update(data); return hasher.finalize().into_bytes().to_vec(); } pub fn hmac_hash_hex(key: &[u8], data: &[u8]) -> String { return hexencode(hmac_hash(key, data)); } pub fn get_scope(date: UtcTime, region: &str, service_name: &str) -> String { return format!( "{}/{}/{}/aws4_request", to_signer_date(date), region, service_name ); } pub fn get_canonical_request_hash( method: &Method, uri: &str, query_string: &str, headers: &str, signed_headers: &str, content_sha256: &str, ) -> String { // CanonicalRequest = // HTTPRequestMethod + '\n' + // CanonicalURI + '\n' + // CanonicalQueryString + '\n' + // CanonicalHeaders + '\n\n' + // SignedHeaders + '\n' + // HexEncode(Hash(RequestPayload)) let canonical_request = format!( "{}\n{}\n{}\n{}\n\n{}\n{}", method, uri, query_string, headers, signed_headers, content_sha256 ); return sha256_hash(canonical_request.as_bytes()); } pub fn get_string_to_sign(date: UtcTime, scope: &str, canonical_request_hash: &str) -> String { return format!( "AWS4-HMAC-SHA256\n{}\n{}\n{}", to_amz_date(date), scope, canonical_request_hash ); } pub fn get_signing_key( secret_key: &str, date: UtcTime, region: &str, service_name: &str, ) -> Vec { let mut key: Vec = b"AWS4".to_vec(); key.extend(secret_key.as_bytes()); let date_key = hmac_hash(key.as_slice(), to_signer_date(date).as_bytes()); let date_region_key = hmac_hash(date_key.as_slice(), region.as_bytes()); let date_region_service_key = hmac_hash(date_region_key.as_slice(), service_name.as_bytes()); return hmac_hash(date_region_service_key.as_slice(), b"aws4_request"); } pub fn get_signature(signing_key: &[u8], string_to_sign: &[u8]) -> String { hmac_hash_hex(signing_key, string_to_sign) } pub fn get_authorization( access_key: &str, scope: &str, signed_headers: &str, signature: &str, ) -> String { return format!( "AWS4-HMAC-SHA256 Credential={}/{}, SignedHeaders={}, Signature={}", access_key, scope, signed_headers, signature ); } pub fn sign_v4( service_name: &str, method: &Method, uri: &str, region: &str, headers: &mut Multimap, query_params: &Multimap, access_key: &str, secret_key: &str, content_sha256: &str, date: UtcTime, ) { let scope = get_scope(date, region, service_name); let (signed_headers, canonical_headers) = get_canonical_headers(headers); let canonical_query_string = get_canonical_query_string(query_params); let canonical_request_hash = get_canonical_request_hash( method, uri, &canonical_query_string, &canonical_headers, &signed_headers, content_sha256, ); let string_to_sign = get_string_to_sign(date, &scope, &canonical_request_hash); let signing_key = get_signing_key(secret_key, date, region, service_name); let signature = get_signature(signing_key.as_slice(), string_to_sign.as_bytes()); let authorization = get_authorization(access_key, &scope, &signed_headers, &signature); headers.insert("Authorization".to_string(), authorization); } pub fn sign_v4_s3( method: &Method, uri: &str, region: &str, headers: &mut Multimap, query_params: &Multimap, access_key: &str, secret_key: &str, content_sha256: &str, date: UtcTime, ) { sign_v4( "s3", method, uri, region, headers, query_params, access_key, secret_key, content_sha256, date, ) } pub fn sign_v4_sts( method: &Method, uri: &str, region: &str, headers: &mut Multimap, query_params: &Multimap, access_key: &str, secret_key: &str, content_sha256: &str, date: UtcTime, ) { sign_v4( "sts", method, uri, region, headers, query_params, access_key, secret_key, content_sha256, date, ) } pub fn presign_v4( method: &Method, host: &str, uri: &str, region: &str, query_params: &mut Multimap, access_key: &str, secret_key: &str, date: UtcTime, expires: u32, ) { let scope = get_scope(date, region, "s3"); let canonical_headers = "host:".to_string() + host; let signed_headers = "host"; query_params.insert( "X-Amz-Algorithm".to_string(), "AWS4-HMAC-SHA256".to_string(), ); query_params.insert( "X-Amz-Credential".to_string(), access_key.to_string() + "/" + &scope, ); query_params.insert("X-Amz-Date".to_string(), to_amz_date(date)); query_params.insert("X-Amz-Expires".to_string(), expires.to_string()); query_params.insert( "X-Amz-SignedHeaders".to_string(), signed_headers.to_string(), ); let canonical_query_string = get_canonical_query_string(query_params); let canonical_request_hash = get_canonical_request_hash( method, uri, &canonical_query_string, &canonical_headers, &signed_headers, "UNSIGNED-PAYLOAD", ); let string_to_sign = get_string_to_sign(date, &scope, &canonical_request_hash); let signing_key = get_signing_key(secret_key, date, region, "s3"); let signature = get_signature(signing_key.as_slice(), string_to_sign.as_bytes()); query_params.insert("X-Amz-Signature".to_string(), signature); } pub fn post_presign_v4( string_to_sign: &str, secret_key: &str, date: UtcTime, region: &str, ) -> String { let signing_key = get_signing_key(secret_key, date, region, "s3"); return get_signature(signing_key.as_slice(), string_to_sign.as_bytes()); }