Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 48 additions & 12 deletions services/aliyun-oss/src/sign_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ use reqsign_core::time::Timestamp;
use reqsign_core::{Context, Error, SignRequest, SigningRequest};
use std::collections::HashSet;
use std::fmt::Write;
use std::sync::LazyLock;
use std::fmt::{Debug, Formatter};
use std::sync::{Arc, LazyLock, Mutex};
use std::time::Duration;

const CONTENT_MD5: &str = "content-md5";
Expand Down Expand Up @@ -92,6 +93,29 @@ pub struct RequestSigner {
region: Option<String>,
signing_version: SigningVersion,
time: Option<Timestamp>,
v4_signing_key_cache: Mutex<Option<CachedV4SigningKey>>,
}

/// Cached OSS V4 signing key.
// NOTE: `region` is NOT part of the cache key;
// it is expected to stay static over the lifetime
// of the `RequestSigner` instance.
struct CachedV4SigningKey {
/// Days since the Unix epoch (UTC) (derived from the `date` we'd sign)
day: i64,
/// The secret the key was derived from, used to detect rotation.
secret: String,
/// The derived signing key.
key: Arc<[u8]>,
}

impl Debug for CachedV4SigningKey {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CachedV4SigningKey")
.field("day", &self.day)
.field("secret_prefix", &&self.secret[..3])
.finish_non_exhaustive()
}
}

impl RequestSigner {
Expand All @@ -102,6 +126,7 @@ impl RequestSigner {
region: None,
signing_version: SigningVersion::V1,
time: None,
v4_signing_key_cache: Mutex::new(None),
}
}

Expand Down Expand Up @@ -503,7 +528,8 @@ impl RequestSigner {
let scope = self.v4_scope(signing_time, region);
let string_to_sign =
self.build_v4_string_to_sign(signing_time, &scope, &canonical_request)?;
let signature = self.build_v4_signature(cred, signing_time, region, &string_to_sign);
let signing_key = self.v4_signing_key(&cred.access_key_secret, region, signing_time);
let signature = hex_hmac_sha256(&signing_key, string_to_sign.as_bytes());

if expires_in.is_some() {
signing_req.query_push(X_OSS_SIGNATURE, signature);
Expand Down Expand Up @@ -716,21 +742,31 @@ impl RequestSigner {
Ok(s)
}

fn build_v4_signature(
&self,
cred: &Credential,
signing_time: Timestamp,
region: &str,
string_to_sign: &str,
) -> String {
fn v4_signing_key(&self, secret: &str, region: &str, signing_time: Timestamp) -> Arc<[u8]> {
let day = signing_time.as_second().div_euclid(86_400);

let mut slot = self.v4_signing_key_cache.lock().expect("lock poisoned");
if let Some(cached) = slot.as_ref() {
// *NOT* comparing region.
if cached.day == day && cached.secret == secret {
return Arc::clone(&cached.key);
}
}

let date_key = hmac_sha256(
format!("aliyun_v4{}", cred.access_key_secret).as_bytes(),
format!("aliyun_v4{secret}").as_bytes(),
signing_time.format_date().as_bytes(),
);
let region_key = hmac_sha256(&date_key, region.as_bytes());
let service_key = hmac_sha256(&region_key, OSS_V4_SERVICE.as_bytes());
let signing_key = hmac_sha256(&service_key, OSS_V4_REQUEST.as_bytes());
hex_hmac_sha256(&signing_key, string_to_sign.as_bytes())
let key: Arc<[u8]> = hmac_sha256(&service_key, OSS_V4_REQUEST.as_bytes()).into();

*slot = Some(CachedV4SigningKey {
day,
secret: secret.to_owned(),
key: Arc::clone(&key),
});
key
}

fn v4_scope(&self, signing_time: Timestamp, region: &str) -> String {
Expand Down
68 changes: 51 additions & 17 deletions services/aws-v4/src/sign_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ use reqsign_core::hash::{hex_hmac_sha256, hex_sha256, hmac_sha256};
use reqsign_core::time::Timestamp;
use reqsign_core::{Context, Result, SignRequest, SigningRequest};
use std::fmt::Write;
use std::fmt::{Debug, Formatter};
use std::sync::{Arc, Mutex};
use std::time::Duration;

/// RequestSigner that implement AWS SigV4.
Expand All @@ -37,8 +39,31 @@ use std::time::Duration;
pub struct RequestSigner {
service: String,
region: String,

time: Option<Timestamp>,
signing_key_cache: Mutex<Option<CachedV4SigningKey>>,
}

/// Cached SigV4 signing key.
//
// NOTE: `region` and `service` are NOT part of the cache key,
// as those are expected to stay static over the lifetime
// of the `RequestSigner` instance.
struct CachedV4SigningKey {
/// Days since the Unix epoch (UTC) (derived from the `date` we'd sign)
day: i64,
/// The secret the key was derived from, used to detect rotation.
secret: String,
/// The derived signing key.
key: Arc<[u8]>,
}

impl Debug for CachedV4SigningKey {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CachedV4SigningKey")
.field("day", &self.day)
.field("secret_prefix", &&self.secret[..3])
.finish_non_exhaustive()
}
}

impl RequestSigner {
Expand All @@ -47,11 +72,34 @@ impl RequestSigner {
Self {
service: service.into(),
region: region.into(),

time: None,
signing_key_cache: Mutex::new(None),
}
}

fn signing_key(&self, secret: &str, now: Timestamp) -> Arc<[u8]> {
let day = now.as_second().div_euclid(86_400);

let mut slot = self.signing_key_cache.lock().expect("lock poisoned");
if let Some(cached) = slot.as_ref() {
// *NOT* comparing region and service.
if cached.day == day && cached.secret == secret {
return Arc::clone(&cached.key);
}
}

let sign_date = hmac_sha256(secret.as_bytes(), now.format_date().as_bytes());
let sign_region = hmac_sha256(sign_date.as_slice(), &self.region.as_bytes());
let sign_service = hmac_sha256(sign_region.as_slice(), &self.service.as_bytes());
let key: Arc<[u8]> = hmac_sha256(sign_service.as_slice(), "aws4_request".as_bytes()).into();
*slot = Some(CachedV4SigningKey {
day,
secret: secret.to_owned(),
key: Arc::clone(&key),
});
key
}

/// Specify the signing time.
///
/// # Note
Expand Down Expand Up @@ -129,8 +177,7 @@ impl SignRequest for RequestSigner {
};
debug!("calculated string to sign: {string_to_sign}");

let signing_key =
generate_signing_key(&cred.secret_access_key, now, &self.region, &self.service);
let signing_key = self.signing_key(&cred.secret_access_key, now);
let signature = hex_hmac_sha256(&signing_key, string_to_sign.as_bytes());

if expires_in.is_some() {
Expand Down Expand Up @@ -348,19 +395,6 @@ fn canonicalize_query(
Ok(())
}

fn generate_signing_key(secret: &str, time: Timestamp, region: &str, service: &str) -> Vec<u8> {
// Sign secret
let secret = format!("AWS4{secret}");
// Sign date
let sign_date = hmac_sha256(secret.as_bytes(), time.format_date().as_bytes());
// Sign region
let sign_region = hmac_sha256(sign_date.as_slice(), region.as_bytes());
// Sign service
let sign_service = hmac_sha256(sign_region.as_slice(), service.as_bytes());
// Sign request
hmac_sha256(sign_service.as_slice(), "aws4_request".as_bytes())
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
Loading