diff --git a/Cargo.toml b/Cargo.toml index 0feb155b..90c7dbd5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,15 +32,14 @@ anyhow = "1" async-trait = "0.1" base64 = "0.22" bytes = "1" -chrono = "0.4.35" criterion = { version = "0.7.0", features = ["async_tokio", "html_reports"] } dotenv = "0.15" env_logger = "0.11" form_urlencoded = "1" hex = "0.4" hmac = "0.12" -home = "0.5" http = "1" +jiff = "0.2" log = "0.4" macro_rules_attribute = "0.2.0" once_cell = "1" diff --git a/core/Cargo.toml b/core/Cargo.toml index 124566c3..e0f50376 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -32,11 +32,11 @@ anyhow.workspace = true async-trait.workspace = true base64.workspace = true bytes.workspace = true -chrono.workspace = true form_urlencoded.workspace = true hex.workspace = true hmac.workspace = true http.workspace = true +jiff.workspace = true log.workspace = true percent-encoding.workspace = true sha1.workspace = true diff --git a/core/src/time.rs b/core/src/time.rs index b8cb500b..d2fd5f69 100644 --- a/core/src/time.rs +++ b/core/src/time.rs @@ -18,68 +18,25 @@ //! Time related utils. use crate::Error; -use chrono::format::Fixed; -use chrono::format::Item; -use chrono::format::Numeric; -use chrono::format::Pad; -use chrono::SecondsFormat; -use chrono::Utc; -/// DateTime is the alias for chrono::DateTime. -pub type DateTime = chrono::DateTime; +/// DateTime is the alias for `jiff::Timestamp`. +pub type Timestamp = jiff::Timestamp; /// Create datetime of now. -pub fn now() -> DateTime { - Utc::now() +pub fn now() -> Timestamp { + Timestamp::now() } -/// DATE is a time format like `20220301` -const DATE: &[Item<'static>] = &[ - Item::Numeric(Numeric::Year, Pad::Zero), - Item::Numeric(Numeric::Month, Pad::Zero), - Item::Numeric(Numeric::Day, Pad::Zero), -]; - /// Format time into date: `20220301` -pub fn format_date(t: DateTime) -> String { - t.format_with_items(DATE.iter()).to_string() +pub fn format_date(t: Timestamp) -> String { + t.strftime("%Y%m%d").to_string() } -/// ISO8601 is a time format like `20220313T072004Z`. -const ISO8601: &[Item<'static>] = &[ - Item::Numeric(Numeric::Year, Pad::Zero), - Item::Numeric(Numeric::Month, Pad::Zero), - Item::Numeric(Numeric::Day, Pad::Zero), - Item::Literal("T"), - Item::Numeric(Numeric::Hour, Pad::Zero), - Item::Numeric(Numeric::Minute, Pad::Zero), - Item::Numeric(Numeric::Second, Pad::Zero), - Item::Literal("Z"), -]; - /// Format time into ISO8601: `20220313T072004Z` -pub fn format_iso8601(t: DateTime) -> String { - t.format_with_items(ISO8601.iter()).to_string() +pub fn format_iso8601(t: Timestamp) -> String { + t.strftime("%Y%m%dT%H%M%SZ").to_string() } -/// HTTP_DATE is a time format like `Sun, 06 Nov 1994 08:49:37 GMT`. -const HTTP_DATE: &[Item<'static>] = &[ - Item::Fixed(Fixed::ShortWeekdayName), - Item::Literal(", "), - Item::Numeric(Numeric::Day, Pad::Zero), - Item::Literal(" "), - Item::Fixed(Fixed::ShortMonthName), - Item::Literal(" "), - Item::Numeric(Numeric::Year, Pad::Zero), - Item::Literal(" "), - Item::Numeric(Numeric::Hour, Pad::Zero), - Item::Literal(":"), - Item::Numeric(Numeric::Minute, Pad::Zero), - Item::Literal(":"), - Item::Numeric(Numeric::Second, Pad::Zero), - Item::Literal(" GMT"), -]; - /// Format time into http date: `Sun, 06 Nov 1994 08:49:37 GMT` /// /// ## Note @@ -88,13 +45,13 @@ const HTTP_DATE: &[Item<'static>] = &[ /// /// - Timezone is fixed to GMT. /// - Day must be 2 digit. -pub fn format_http_date(t: DateTime) -> String { - t.format_with_items(HTTP_DATE.iter()).to_string() +pub fn format_http_date(t: Timestamp) -> String { + t.strftime("%a, %d %b %Y %T GMT").to_string() } /// Format time into RFC3339: `2022-03-13T07:20:04Z` -pub fn format_rfc3339(t: DateTime) -> String { - t.to_rfc3339_opts(SecondsFormat::Secs, true) +pub fn format_rfc3339(t: Timestamp) -> String { + t.strftime("%FT%TZ").to_string() } /// Parse time from RFC3339. @@ -104,22 +61,31 @@ pub fn format_rfc3339(t: DateTime) -> String { /// - `2022-03-13T07:20:04Z` /// - `2022-03-01T08:12:34+00:00` /// - `2022-03-01T08:12:34.00+00:00` -pub fn parse_rfc3339(s: &str) -> crate::Result { - Ok(chrono::DateTime::parse_from_rfc3339(s) - .map_err(|err| { - Error::unexpected(format!("parse '{s}' into rfc3339 failed")).with_source(err) - })? - .with_timezone(&Utc)) +pub fn parse_rfc3339(s: &str) -> crate::Result { + s.parse().map_err(|err| { + Error::unexpected(format!("parse '{s}' into rfc3339 failed")).with_source(err) + }) +} + +/// Parse time from RFC2822. +/// +/// All of them are valid time: +/// +/// - `Sat, 13 Jul 2024 15:09:59 -0400` +/// - `Mon, 15 Aug 2022 16:50:12 GMT` +pub fn parse_rfc2822(s: &str) -> crate::Result { + let zoned = jiff::fmt::rfc2822::parse(s).map_err(|err| { + Error::unexpected(format!("parse '{s}' into rfc2822 failed")).with_source(err) + })?; + Ok(zoned.timestamp()) } #[cfg(test)] mod tests { - use chrono::TimeZone; - use super::*; - fn test_time() -> DateTime { - Utc.with_ymd_and_hms(2022, 3, 1, 8, 12, 34).unwrap() + fn test_time() -> Timestamp { + "2022-03-01T08:12:34Z".parse().unwrap() } #[test] diff --git a/licenserc.toml b/licenserc.toml index 47a56e53..1937a227 100644 --- a/licenserc.toml +++ b/licenserc.toml @@ -17,4 +17,4 @@ headerPath = "Apache-2.0-ASF.txt" -includes = ['**/*.rs', '**/*.yml', '**/*.yaml', '**/*.toml'] \ No newline at end of file +includes = ['**/*.rs', '**/*.yml', '**/*.yaml', '**/*.toml'] diff --git a/services/aliyun-oss/Cargo.toml b/services/aliyun-oss/Cargo.toml index 55216843..ea71e8f3 100644 --- a/services/aliyun-oss/Cargo.toml +++ b/services/aliyun-oss/Cargo.toml @@ -29,8 +29,8 @@ repository.workspace = true [dependencies] anyhow.workspace = true async-trait.workspace = true -chrono.workspace = true http.workspace = true +jiff.workspace = true log.workspace = true once_cell.workspace = true percent-encoding.workspace = true diff --git a/services/aliyun-oss/src/credential.rs b/services/aliyun-oss/src/credential.rs index d7c02299..9ec61ed1 100644 --- a/services/aliyun-oss/src/credential.rs +++ b/services/aliyun-oss/src/credential.rs @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -use reqsign_core::time::{now, DateTime}; +use reqsign_core::time::{now, Timestamp}; use reqsign_core::utils::Redact; use reqsign_core::SigningCredential; use std::fmt::{Debug, Formatter}; @@ -30,7 +30,7 @@ pub struct Credential { /// Security token for aliyun services. pub security_token: Option, /// Expiration time for this credential. - pub expires_in: Option, + pub expires_in: Option, } impl Debug for Credential { @@ -54,7 +54,7 @@ impl SigningCredential for Credential { // Take 120s as buffer to avoid edge cases. if let Some(valid) = self .expires_in - .map(|v| v > now() + chrono::TimeDelta::try_minutes(2).expect("in bounds")) + .map(|v| v > now() + jiff::SignedDuration::from_mins(2)) { return valid; } diff --git a/services/aliyun-oss/src/sign_request.rs b/services/aliyun-oss/src/sign_request.rs index 37b4adbf..08df07b6 100644 --- a/services/aliyun-oss/src/sign_request.rs +++ b/services/aliyun-oss/src/sign_request.rs @@ -22,7 +22,7 @@ use http::HeaderValue; use once_cell::sync::Lazy; use percent_encoding::utf8_percent_encode; use reqsign_core::hash::base64_hmac_sha1; -use reqsign_core::time::{format_http_date, now, DateTime}; +use reqsign_core::time::{format_http_date, now, Timestamp}; use reqsign_core::Result; use reqsign_core::{Context, SignRequest}; use std::collections::HashSet; @@ -35,7 +35,7 @@ const CONTENT_MD5: &str = "content-md5"; #[derive(Debug)] pub struct RequestSigner { bucket: String, - time: Option, + time: Option, } impl RequestSigner { @@ -54,12 +54,12 @@ impl RequestSigner { /// We should always take current time to sign requests. /// Only use this function for testing. #[cfg(test)] - pub fn with_time(mut self, time: DateTime) -> Self { + pub fn with_time(mut self, time: Timestamp) -> Self { self.time = Some(time); self } - fn get_time(&self) -> DateTime { + fn get_time(&self) -> Timestamp { self.time.unwrap_or_else(now) } } @@ -97,7 +97,7 @@ impl RequestSigner { &self, req: &mut http::request::Parts, cred: &Credential, - signing_time: DateTime, + signing_time: Timestamp, ) -> Result<()> { let string_to_sign = self.build_string_to_sign(req, cred, signing_time, None)?; let signature = @@ -125,11 +125,11 @@ impl RequestSigner { &self, req: &mut http::request::Parts, cred: &Credential, - signing_time: DateTime, + signing_time: Timestamp, expires: Duration, ) -> Result<()> { let expiration_time = signing_time - + chrono::TimeDelta::from_std(expires).map_err(|e| { + + jiff::SignedDuration::try_from(expires).map_err(|e| { reqsign_core::Error::request_invalid(format!("Invalid expiration duration: {e}")) })?; let string_to_sign = self.build_string_to_sign(req, cred, signing_time, Some(expires))?; @@ -154,7 +154,7 @@ impl RequestSigner { query_pairs.push(("OSSAccessKeyId".to_string(), cred.access_key_id.clone())); query_pairs.push(( "Expires".to_string(), - expiration_time.timestamp().to_string(), + expiration_time.as_second().to_string(), )); query_pairs.push(( "Signature".to_string(), @@ -200,7 +200,7 @@ impl RequestSigner { &self, req: &http::request::Parts, cred: &Credential, - signing_time: DateTime, + signing_time: Timestamp, expires: Option, ) -> Result { let mut s = String::new(); @@ -229,12 +229,12 @@ impl RequestSigner { match expires { Some(expires_duration) => { let expiration_time = signing_time - + chrono::TimeDelta::from_std(expires_duration).map_err(|e| { + + jiff::SignedDuration::try_from(expires_duration).map_err(|e| { reqsign_core::Error::request_invalid(format!( "Invalid expiration duration: {e}" )) })?; - writeln!(&mut s, "{}", expiration_time.timestamp())?; + writeln!(&mut s, "{}", expiration_time.as_second())?; } None => { writeln!(&mut s, "{}", format_http_date(signing_time))?; diff --git a/services/aws-v4/Cargo.toml b/services/aws-v4/Cargo.toml index 80df2309..47acb30c 100644 --- a/services/aws-v4/Cargo.toml +++ b/services/aws-v4/Cargo.toml @@ -34,9 +34,9 @@ name = "aws" anyhow.workspace = true async-trait.workspace = true bytes = "1.7.2" -chrono.workspace = true form_urlencoded.workspace = true http.workspace = true +jiff.workspace = true log.workspace = true percent-encoding.workspace = true quick-xml.workspace = true diff --git a/services/aws-v4/src/credential.rs b/services/aws-v4/src/credential.rs index 6e952e8a..a0a1dbb9 100644 --- a/services/aws-v4/src/credential.rs +++ b/services/aws-v4/src/credential.rs @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -use reqsign_core::time::{now, DateTime}; +use reqsign_core::time::{now, Timestamp}; use reqsign_core::utils::Redact; use reqsign_core::SigningCredential; use std::fmt::{Debug, Formatter}; @@ -30,7 +30,7 @@ pub struct Credential { /// Session token for aws services. pub session_token: Option, /// Expiration time for this credential. - pub expires_in: Option, + pub expires_in: Option, } impl Debug for Credential { @@ -54,7 +54,7 @@ impl SigningCredential for Credential { // Take 120s as buffer to avoid edge cases. if let Some(valid) = self .expires_in - .map(|v| v > now() + chrono::TimeDelta::try_minutes(2).expect("in bounds")) + .map(|v| v > now() + jiff::SignedDuration::from_mins(2)) { return valid; } diff --git a/services/aws-v4/src/provide_credential/cognito.rs b/services/aws-v4/src/provide_credential/cognito.rs index 52ce42bf..1de3730d 100644 --- a/services/aws-v4/src/provide_credential/cognito.rs +++ b/services/aws-v4/src/provide_credential/cognito.rs @@ -17,9 +17,9 @@ use crate::Credential; use async_trait::async_trait; -use chrono::DateTime; use http::{Method, Request, StatusCode}; use log::debug; +use reqsign_core::time::Timestamp; use reqsign_core::{Context, Error, ProvideCredential, Result}; use serde::Deserialize; use serde_json::json; @@ -224,8 +224,8 @@ impl CognitoIdentityCredentialProvider { .map_err(|e| Error::unexpected(format!("failed to parse credentials response: {e}")))?; let creds = result.credentials; - let expires_in = DateTime::from_timestamp(creds.expiration, 0) - .ok_or_else(|| Error::unexpected("invalid expiration timestamp".to_string()))?; + let expires_in = Timestamp::from_second(creds.expiration) + .map_err(|e| Error::unexpected(format!("invalid expiration date: {e}")))?; Ok(Credential { access_key_id: creds.access_key_id, diff --git a/services/aws-v4/src/provide_credential/imds.rs b/services/aws-v4/src/provide_credential/imds.rs index 61db8b5c..a25af580 100644 --- a/services/aws-v4/src/provide_credential/imds.rs +++ b/services/aws-v4/src/provide_credential/imds.rs @@ -21,7 +21,7 @@ use async_trait::async_trait; use bytes::Bytes; use http::header::CONTENT_LENGTH; use http::Method; -use reqsign_core::time::{now, parse_rfc3339, DateTime}; +use reqsign_core::time::{now, parse_rfc3339, Timestamp}; use reqsign_core::{Context, Error, ProvideCredential, Result}; use serde::Deserialize; use std::sync::{Arc, Mutex}; @@ -29,14 +29,14 @@ use std::sync::{Arc, Mutex}; #[derive(Debug, Clone)] pub struct IMDSv2CredentialProvider { endpoint: Option, - token: Arc>, + token: Arc>, } impl Default for IMDSv2CredentialProvider { fn default() -> Self { Self { endpoint: None, - token: Arc::new(Mutex::new((String::new(), DateTime::default()))), + token: Arc::new(Mutex::new((String::new(), Timestamp::default()))), } } } @@ -105,8 +105,8 @@ impl IMDSv2CredentialProvider { } let ec2_token = resp.into_body(); // Set expires_in to 10 minutes to enforce re-read. - let expires_in = now() + chrono::TimeDelta::try_seconds(21600).expect("in bounds") - - chrono::TimeDelta::try_seconds(600).expect("in bounds"); + let expires_in = + now() + jiff::SignedDuration::from_secs(21600) - jiff::SignedDuration::from_secs(600); { *self.token.lock().expect("lock poisoned") = (ec2_token.clone(), expires_in); diff --git a/services/aws-v4/src/provide_credential/process.rs b/services/aws-v4/src/provide_credential/process.rs index e6720486..87821999 100644 --- a/services/aws-v4/src/provide_credential/process.rs +++ b/services/aws-v4/src/provide_credential/process.rs @@ -17,9 +17,9 @@ use crate::Credential; use async_trait::async_trait; -use chrono::{DateTime, Utc}; use ini::Ini; use log::debug; +use reqsign_core::time::Timestamp; use reqsign_core::{Context, Error, ProvideCredential, Result}; use serde::Deserialize; @@ -213,7 +213,7 @@ impl ProvideCredential for ProcessCredentialProvider { let expires_in = if let Some(exp_str) = &output.expiration { - Some(exp_str.parse::>().map_err(|e| { + Some(exp_str.parse::().map_err(|e| { Error::unexpected(format!("failed to parse expiration time: {e}")) })?) } else { diff --git a/services/aws-v4/src/provide_credential/s3_express_session.rs b/services/aws-v4/src/provide_credential/s3_express_session.rs index fc79a3b0..723b2bb1 100644 --- a/services/aws-v4/src/provide_credential/s3_express_session.rs +++ b/services/aws-v4/src/provide_credential/s3_express_session.rs @@ -20,6 +20,7 @@ use async_trait::async_trait; use bytes::Bytes; use http::{header, Method, Request}; use log::debug; +use reqsign_core::time::parse_rfc3339; use reqsign_core::{Context, Error, ProvideCredential, Result, SignRequest}; use serde::Deserialize; @@ -160,13 +161,12 @@ impl S3ExpressSessionProvider { // Parse expiration time from ISO8601 format let expiration = - chrono::DateTime::parse_from_rfc3339(&create_session_resp.credentials.expiration) - .map_err(|e| { - Error::unexpected(format!( - "Failed to parse expiration time '{}': {e}", - create_session_resp.credentials.expiration - )) - })?; + parse_rfc3339(&create_session_resp.credentials.expiration).map_err(|e| { + Error::unexpected(format!( + "Failed to parse expiration time '{}': {e}", + create_session_resp.credentials.expiration + )) + })?; // Convert to Credential with expiration time let creds = create_session_resp.credentials; @@ -174,7 +174,7 @@ impl S3ExpressSessionProvider { access_key_id: creds.access_key_id, secret_access_key: creds.secret_access_key, session_token: Some(creds.session_token), - expires_in: Some(expiration.into()), + expires_in: Some(expiration), }) } diff --git a/services/aws-v4/src/provide_credential/sso.rs b/services/aws-v4/src/provide_credential/sso.rs index b0004229..a0adaecd 100644 --- a/services/aws-v4/src/provide_credential/sso.rs +++ b/services/aws-v4/src/provide_credential/sso.rs @@ -17,10 +17,10 @@ use crate::Credential; use async_trait::async_trait; -use chrono::{DateTime, Utc}; use http::{Method, Request, StatusCode}; use ini::Ini; use log::{debug, warn}; +use reqsign_core::time::{now, Timestamp}; use reqsign_core::{Context, Error, ProvideCredential, Result}; use serde::Deserialize; @@ -218,11 +218,11 @@ impl SSOCredentialProvider { })?; // Check if token is expired - let expires_at: DateTime = token.expires_at.parse().map_err(|e| { + let expires_at = token.expires_at.parse::().map_err(|e| { Error::unexpected(format!("failed to parse expiration time: {e}")) })?; - if expires_at <= Utc::now() { + if expires_at <= now() { warn!("SSO token is expired"); return Ok(None); } @@ -286,8 +286,8 @@ impl SSOCredentialProvider { .map_err(|e| Error::unexpected(format!("failed to parse SSO credentials: {e}")))?; let role_creds = creds.role_credentials; - let expires_in = DateTime::from_timestamp_millis(role_creds.expiration) - .ok_or_else(|| Error::unexpected("invalid expiration timestamp".to_string()))?; + let expires_in = Timestamp::from_millisecond(role_creds.expiration) + .map_err(|e| Error::unexpected(format!("invalid expiration timestamp: {e}")))?; Ok(Credential { access_key_id: role_creds.access_key_id, diff --git a/services/aws-v4/src/sign_request.rs b/services/aws-v4/src/sign_request.rs index 2aa7e527..6b27ddfc 100644 --- a/services/aws-v4/src/sign_request.rs +++ b/services/aws-v4/src/sign_request.rs @@ -26,7 +26,7 @@ use http::{header, HeaderValue}; use log::debug; use percent_encoding::{percent_decode_str, utf8_percent_encode}; use reqsign_core::hash::{hex_hmac_sha256, hex_sha256, hmac_sha256}; -use reqsign_core::time::{format_date, format_iso8601, now, DateTime}; +use reqsign_core::time::{format_date, format_iso8601, now, Timestamp}; use reqsign_core::{Context, Result, SignRequest, SigningRequest}; use std::fmt::Write; use std::time::Duration; @@ -39,7 +39,7 @@ pub struct RequestSigner { service: String, region: String, - time: Option, + time: Option, } impl RequestSigner { @@ -60,7 +60,7 @@ impl RequestSigner { /// We should always take current time to sign requests. /// Only use this function for testing. #[cfg(test)] - pub fn with_time(mut self, time: DateTime) -> Self { + pub fn with_time(mut self, time: Timestamp) -> Self { self.time = Some(time); self } @@ -233,7 +233,7 @@ fn canonicalize_header( ctx: &mut SigningRequest, cred: &Credential, expires_in: Option, - now: DateTime, + now: Timestamp, ) -> Result<()> { // Header names and values need to be normalized according to Step 4 of https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html for (_, value) in ctx.headers.iter_mut() { @@ -298,7 +298,7 @@ fn canonicalize_query( ctx: &mut SigningRequest, cred: &Credential, expires_in: Option, - now: DateTime, + now: Timestamp, service: &str, region: &str, ) -> Result<()> { @@ -351,7 +351,7 @@ fn canonicalize_query( Ok(()) } -fn generate_signing_key(secret: &str, time: DateTime, region: &str, service: &str) -> Vec { +fn generate_signing_key(secret: &str, time: Timestamp, region: &str, service: &str) -> Vec { // Sign secret let secret = format!("AWS4{secret}"); // Sign date diff --git a/services/azure-storage/Cargo.toml b/services/azure-storage/Cargo.toml index 788cb83c..ca3cfe7a 100644 --- a/services/azure-storage/Cargo.toml +++ b/services/azure-storage/Cargo.toml @@ -31,9 +31,9 @@ anyhow.workspace = true async-trait.workspace = true base64.workspace = true bytes.workspace = true -chrono.workspace = true form_urlencoded.workspace = true http.workspace = true +jiff.workspace = true log.workspace = true percent-encoding.workspace = true reqsign-core.workspace = true diff --git a/services/azure-storage/src/account_sas.rs b/services/azure-storage/src/account_sas.rs index bd04ee8d..7eff18a6 100644 --- a/services/azure-storage/src/account_sas.rs +++ b/services/azure-storage/src/account_sas.rs @@ -19,7 +19,7 @@ use reqsign_core::Result; use reqsign_core::hash; use reqsign_core::time; -use reqsign_core::time::DateTime; +use reqsign_core::time::Timestamp; /// The default parameters that make up a SAS token /// https://learn.microsoft.com/en-us/rest/api/storageservices/create-account-sas#specify-the-account-sas-parameters @@ -35,15 +35,15 @@ pub struct AccountSharedAccessSignature { resource: String, resource_type: String, permissions: String, - expiry: DateTime, - start: Option, + expiry: Timestamp, + start: Option, ip: Option, protocol: Option, } impl AccountSharedAccessSignature { /// Create a SAS token signer with default parameters - pub fn new(account: String, key: String, expiry: DateTime) -> Self { + pub fn new(account: String, key: String, expiry: Timestamp) -> Self { Self { account, key, @@ -125,14 +125,14 @@ mod tests { use super::*; - fn test_time() -> DateTime { - DateTime::from_str("2022-03-01T08:12:34Z").unwrap() + fn test_time() -> Timestamp { + Timestamp::from_str("2022-03-01T08:12:34Z").unwrap() } #[test] fn test_can_generate_sas_token() { let key = hash::base64_encode("key".as_bytes()); - let expiry = test_time() + chrono::TimeDelta::try_minutes(5).expect("in bounds"); + let expiry = test_time() + jiff::SignedDuration::from_mins(5); let sign = AccountSharedAccessSignature::new("account".to_string(), key, expiry); let token_content = sign.token().expect("token decode failed"); let token = token_content diff --git a/services/azure-storage/src/credential.rs b/services/azure-storage/src/credential.rs index abd1ec48..e824bc9e 100644 --- a/services/azure-storage/src/credential.rs +++ b/services/azure-storage/src/credential.rs @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -use reqsign_core::time::{now, DateTime}; +use reqsign_core::time::{now, Timestamp}; use reqsign_core::utils::Redact; use reqsign_core::SigningCredential; use std::fmt::{Debug, Formatter}; @@ -40,7 +40,7 @@ pub enum Credential { /// Bearer token. token: String, /// Expiration time for this credential. - expires_in: Option, + expires_in: Option, }, } @@ -82,7 +82,7 @@ impl SigningCredential for Credential { } // Check expiration for bearer tokens (take 20s as buffer to avoid edge cases) if let Some(expires) = expires_in { - *expires > now() + chrono::TimeDelta::try_seconds(20).expect("in bounds") + *expires > now() + jiff::SignedDuration::from_secs(20) } else { true } @@ -108,7 +108,7 @@ impl Credential { } /// Create a new credential with bearer token authentication. - pub fn with_bearer_token(bearer_token: &str, expires_in: Option) -> Self { + pub fn with_bearer_token(bearer_token: &str, expires_in: Option) -> Self { Self::BearerToken { token: bearer_token.to_string(), expires_in, diff --git a/services/azure-storage/src/provide_credential/azure_cli.rs b/services/azure-storage/src/provide_credential/azure_cli.rs index ce6dc825..69f112a7 100644 --- a/services/azure-storage/src/provide_credential/azure_cli.rs +++ b/services/azure-storage/src/provide_credential/azure_cli.rs @@ -15,12 +15,12 @@ // specific language governing permissions and limitations // under the License. +use crate::credential::Credential; use async_trait::async_trait; +use reqsign_core::time::Timestamp; use reqsign_core::{Context, ProvideCredential}; use serde::Deserialize; -use crate::credential::Credential; - /// AzureCliCredentialProvider provides credentials from Azure CLI /// /// This provider reads tokens from Azure CLI's local storage or invokes @@ -96,12 +96,13 @@ impl ProvideCredential for AzureCliCredentialProvider { // Calculate expiration time let expires_on = if let Some(timestamp) = token.expires_on_timestamp { - Some(chrono::DateTime::from_timestamp(timestamp, 0).unwrap()) + Some(Timestamp::from_second(timestamp).unwrap()) } else if let Some(expires_str) = token.expires_on { // Parse the string format "2023-10-31 21:59:10.000000" - chrono::NaiveDateTime::parse_from_str(&expires_str, "%Y-%m-%d %H:%M:%S%.f") + expires_str + .parse::() + .and_then(|dt| jiff::tz::TimeZone::UTC.to_timestamp(dt)) .ok() - .map(|dt| chrono::DateTime::from_naive_utc_and_offset(dt, chrono::Utc)) } else { None }; diff --git a/services/azure-storage/src/provide_credential/azure_pipelines.rs b/services/azure-storage/src/provide_credential/azure_pipelines.rs index 6544528f..a1849d00 100644 --- a/services/azure-storage/src/provide_credential/azure_pipelines.rs +++ b/services/azure-storage/src/provide_credential/azure_pipelines.rs @@ -17,12 +17,12 @@ use std::collections::HashMap; +use crate::credential::Credential; use async_trait::async_trait; +use reqsign_core::time::now; use reqsign_core::{Context, ProvideCredential}; use serde::Deserialize; -use crate::credential::Credential; - /// AzurePipelinesCredentialProvider provides credentials using Azure Pipelines workload identity /// /// This provider uses OIDC tokens from Azure Pipelines to authenticate with Azure AD @@ -233,13 +233,8 @@ impl ProvideCredential for AzurePipelinesCredentialProvider { .await?; // Calculate expiration time - let expires_on = std::time::SystemTime::now() - .checked_add(std::time::Duration::from_secs(token_response.expires_in)) - .and_then(|t| { - t.duration_since(std::time::UNIX_EPOCH) - .ok() - .map(|d| chrono::DateTime::from_timestamp(d.as_secs() as i64, 0).unwrap()) - }); + let expires_in = std::time::Duration::from_secs(token_response.expires_in); + let expires_on = now().checked_add(expires_in).ok(); Ok(Some(Credential::with_bearer_token( &token_response.access_token, @@ -271,7 +266,7 @@ mod tests { async fn test_provide_credential_no_environment() { // When not in Azure Pipelines environment, should return None let provider = AzurePipelinesCredentialProvider::new(); - let ctx = reqsign_core::Context::new() + let ctx = Context::new() .with_file_read(reqsign_file_read_tokio::TokioFileRead) .with_http_send(reqsign_http_send_reqwest::ReqwestHttpSend::default()); diff --git a/services/azure-storage/src/provide_credential/client_certificate.rs b/services/azure-storage/src/provide_credential/client_certificate.rs index 9e1d6a64..7268568a 100644 --- a/services/azure-storage/src/provide_credential/client_certificate.rs +++ b/services/azure-storage/src/provide_credential/client_certificate.rs @@ -18,23 +18,23 @@ use std::collections::HashMap; use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use crate::credential::Credential; use async_trait::async_trait; use base64::engine::general_purpose::URL_SAFE_NO_PAD; use base64::Engine; use jsonwebtoken::{Algorithm, EncodingKey, Header}; +use reqsign_core::time::now; use reqsign_core::{Context, ProvideCredential}; use rsa::pkcs8::DecodePrivateKey; use rsa::RsaPrivateKey; use serde::{Deserialize, Serialize}; use sha1::{Digest, Sha1}; -use crate::credential::Credential; - /// Generate a unique JWT ID using timestamp and a pseudo-random component fn generate_jti(now: u64) -> String { // Use timestamp in nanoseconds + a hash of the timestamp for uniqueness - let nano_time = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) + let nano_time = SystemTime::now() + .duration_since(UNIX_EPOCH) .unwrap_or_default() .as_nanos(); @@ -315,13 +315,8 @@ impl ProvideCredential for ClientCertificateCredentialProvider { .await?; // Calculate expiration time - let expires_on = SystemTime::now() - .checked_add(Duration::from_secs(token_response.expires_in)) - .and_then(|t| { - t.duration_since(UNIX_EPOCH) - .ok() - .map(|d| chrono::DateTime::from_timestamp(d.as_secs() as i64, 0).unwrap()) - }); + let expires_in = Duration::from_secs(token_response.expires_in); + let expires_on = now().checked_add(expires_in).ok(); Ok(Some(Credential::with_bearer_token( &token_response.access_token, diff --git a/services/azure-storage/src/provide_credential/client_secret.rs b/services/azure-storage/src/provide_credential/client_secret.rs index 7294f7cb..bde45cd2 100644 --- a/services/azure-storage/src/provide_credential/client_secret.rs +++ b/services/azure-storage/src/provide_credential/client_secret.rs @@ -91,8 +91,7 @@ impl ProvideCredential for ClientSecretCredentialProvider { match token { Some(token_response) => { let expires_on = reqsign_core::time::now() - + chrono::TimeDelta::try_seconds(token_response.expires_in as i64) - .unwrap_or_else(|| chrono::TimeDelta::try_minutes(10).expect("in bounds")); + + jiff::SignedDuration::from_secs(token_response.expires_in as i64); Ok(Some(Credential::with_bearer_token( &token_response.access_token, diff --git a/services/azure-storage/src/provide_credential/imds.rs b/services/azure-storage/src/provide_credential/imds.rs index a09a41f1..963b34f1 100644 --- a/services/azure-storage/src/provide_credential/imds.rs +++ b/services/azure-storage/src/provide_credential/imds.rs @@ -17,6 +17,7 @@ use crate::Credential; use async_trait::async_trait; +use reqsign_core::time::Timestamp; use reqsign_core::{Context, ProvideCredential, Result}; /// Load credential from Azure Instance Metadata Service (IMDS). @@ -51,15 +52,16 @@ impl ProvideCredential for ImdsCredentialProvider { let token = get_access_token("https://storage.azure.com/", ctx).await?; let expires_on = if token.expires_on.is_empty() { - reqsign_core::time::now() + chrono::TimeDelta::try_minutes(10).expect("in bounds") + reqsign_core::time::now() + jiff::SignedDuration::from_mins(10) } else { // Azure IMDS returns expires_on as Unix timestamp (seconds since epoch) let timestamp = token.expires_on.parse::().map_err(|e| { reqsign_core::Error::unexpected("failed to parse expires_on timestamp") .with_source(e) })?; - chrono::DateTime::from_timestamp(timestamp, 0) - .ok_or_else(|| reqsign_core::Error::unexpected("invalid expires_on timestamp"))? + Timestamp::from_second(timestamp).map_err(|e| { + reqsign_core::Error::unexpected(format!("invalid expires_on timestamp: {e}")) + })? }; Ok(Some(Credential::with_bearer_token( diff --git a/services/azure-storage/src/provide_credential/workload_identity.rs b/services/azure-storage/src/provide_credential/workload_identity.rs index 17d0328d..500186a6 100644 --- a/services/azure-storage/src/provide_credential/workload_identity.rs +++ b/services/azure-storage/src/provide_credential/workload_identity.rs @@ -91,10 +91,7 @@ impl ProvideCredential for WorkloadIdentityCredentialProvider { .with_source(e) })? } - None => { - reqsign_core::time::now() - + chrono::TimeDelta::try_minutes(10).expect("in bounds") - } + None => reqsign_core::time::now() + jiff::SignedDuration::from_mins(10), }; Ok(Some(Credential::with_bearer_token( diff --git a/services/azure-storage/src/sign_request.rs b/services/azure-storage/src/sign_request.rs index 43eb0717..c528a07e 100644 --- a/services/azure-storage/src/sign_request.rs +++ b/services/azure-storage/src/sign_request.rs @@ -23,7 +23,7 @@ use http::{header, HeaderValue}; use log::debug; use percent_encoding::percent_encode; use reqsign_core::hash::{base64_decode, base64_hmac_sha256}; -use reqsign_core::time::{format_http_date, now, DateTime}; +use reqsign_core::time::{format_http_date, now, Timestamp}; use reqsign_core::{Context, Result, SignRequest, SigningMethod, SigningRequest}; use std::fmt::Write; use std::time::Duration; @@ -33,7 +33,7 @@ use std::time::Duration; /// - [Authorize with Shared Key](https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key) #[derive(Debug)] pub struct RequestSigner { - time: Option, + time: Option, } impl RequestSigner { @@ -49,7 +49,7 @@ impl RequestSigner { /// We should always take current time to sign requests. /// Only use this function for testing. #[cfg(test)] - pub fn with_time(mut self, time: DateTime) -> Self { + pub fn with_time(mut self, time: Timestamp) -> Self { self.time = Some(time); self } @@ -134,7 +134,7 @@ impl SignRequest for RequestSigner { account_name.clone(), account_key.clone(), now() - + chrono::TimeDelta::from_std(d).map_err(|e| { + + jiff::SignedDuration::try_from(d).map_err(|e| { reqsign_core::Error::unexpected("failed to convert duration") .with_source(e) })?, @@ -214,7 +214,7 @@ impl SignRequest for RequestSigner { fn string_to_sign( ctx: &mut SigningRequest, account_name: &str, - now_time: DateTime, + now_time: Timestamp, ) -> Result { let mut s = String::with_capacity(128); @@ -374,7 +374,7 @@ fn string_to_sign( /// ## Reference /// /// - [Constructing the canonicalized headers string](https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key#constructing-the-canonicalized-headers-string) -fn canonicalize_header(ctx: &mut SigningRequest, now_time: DateTime) -> Result { +fn canonicalize_header(ctx: &mut SigningRequest, now_time: Timestamp) -> Result { ctx.headers.insert( X_MS_DATE, format_http_date(now_time).parse().map_err(|e| { @@ -455,7 +455,7 @@ mod tests { .with_env(OsEnv); let cred = Credential::with_bearer_token( "token", - Some(now() + chrono::TimeDelta::try_hours(1).unwrap()), + Some(now() + jiff::SignedDuration::from_hours(1)), ); let builder = RequestSigner::new(); diff --git a/services/google/Cargo.toml b/services/google/Cargo.toml index cfb86d27..58d38649 100644 --- a/services/google/Cargo.toml +++ b/services/google/Cargo.toml @@ -28,8 +28,8 @@ repository.workspace = true [dependencies] async-trait.workspace = true -chrono.workspace = true http.workspace = true +jiff.workspace = true jsonwebtoken = "9.2" log.workspace = true percent-encoding.workspace = true diff --git a/services/google/src/credential.rs b/services/google/src/credential.rs index 7167c13d..ace9f946 100644 --- a/services/google/src/credential.rs +++ b/services/google/src/credential.rs @@ -16,7 +16,7 @@ // under the License. use reqsign_core::{ - time::now, time::DateTime, utils::Redact, Result, SigningCredential as KeyTrait, + time::now, time::Timestamp, utils::Redact, Result, SigningCredential as KeyTrait, }; use std::fmt::{self, Debug}; @@ -183,7 +183,7 @@ pub struct Token { /// The access token. pub access_token: String, /// The expiration time of the token. - pub expires_at: Option, + pub expires_at: Option, } impl Debug for Token { @@ -204,7 +204,7 @@ impl KeyTrait for Token { match self.expires_at { Some(expires_at) => { // Consider token invalid if it expires within 2 minutes - let buffer = chrono::TimeDelta::try_seconds(2 * 60).expect("in bounds"); + let buffer = jiff::SignedDuration::from_mins(2); now() < expires_at - buffer } None => true, // No expiration means always valid @@ -337,15 +337,15 @@ mod tests { assert!(token.is_valid()); // Token with future expiration - token.expires_at = Some(now() + chrono::TimeDelta::try_hours(1).unwrap()); + token.expires_at = Some(now() + jiff::SignedDuration::from_hours(1)); assert!(token.is_valid()); // Token that expires within 2 minutes - token.expires_at = Some(now() + chrono::TimeDelta::try_seconds(30).unwrap()); + token.expires_at = Some(now() + jiff::SignedDuration::from_secs(30)); assert!(!token.is_valid()); // Expired token - token.expires_at = Some(now() - chrono::TimeDelta::try_hours(1).unwrap()); + token.expires_at = Some(now() - jiff::SignedDuration::from_hours(1)); assert!(!token.is_valid()); // Empty access token @@ -417,7 +417,7 @@ mod tests { // Valid token only let cred = Credential::with_token(Token { access_token: "test".to_string(), - expires_at: Some(now() + chrono::TimeDelta::try_hours(1).unwrap()), + expires_at: Some(now() + jiff::SignedDuration::from_hours(1)), }); assert!(cred.is_valid()); assert!(!cred.has_service_account()); @@ -439,7 +439,7 @@ mod tests { }); cred.token = Some(Token { access_token: "test".to_string(), - expires_at: Some(now() + chrono::TimeDelta::try_hours(1).unwrap()), + expires_at: Some(now() + jiff::SignedDuration::from_hours(1)), }); assert!(cred.is_valid()); assert!(cred.has_service_account()); @@ -452,7 +452,7 @@ mod tests { }); cred.token = Some(Token { access_token: "test".to_string(), - expires_at: Some(now() - chrono::TimeDelta::try_hours(1).unwrap()), + expires_at: Some(now() - jiff::SignedDuration::from_hours(1)), }); assert!(cred.is_valid()); // Still valid because of service account assert!(!cred.has_valid_token()); diff --git a/services/google/src/provide_credential/authorized_user.rs b/services/google/src/provide_credential/authorized_user.rs index 88b130aa..e510e5e7 100644 --- a/services/google/src/provide_credential/authorized_user.rs +++ b/services/google/src/provide_credential/authorized_user.rs @@ -94,9 +94,9 @@ impl ProvideCredential for AuthorizedUserCredentialProvider { reqsign_core::Error::unexpected("failed to parse token response").with_source(e) })?; - let expires_at = token_resp.expires_in.map(|expires_in| { - now() + chrono::TimeDelta::try_seconds(expires_in as i64).expect("in bounds") - }); + let expires_at = token_resp + .expires_in + .map(|expires_in| now() + jiff::SignedDuration::from_secs(expires_in as i64)); let token = Token { access_token: token_resp.access_token, diff --git a/services/google/src/provide_credential/external_account.rs b/services/google/src/provide_credential/external_account.rs index 150a9203..4cd7c2a4 100644 --- a/services/google/src/provide_credential/external_account.rs +++ b/services/google/src/provide_credential/external_account.rs @@ -21,9 +21,9 @@ use http::header::{ACCEPT, CONTENT_TYPE}; use log::{debug, error}; use serde::{Deserialize, Serialize}; -use reqsign_core::{time::now, Context, ProvideCredential, Result}; - use crate::credential::{external_account, Credential, ExternalAccount, Token}; +use reqsign_core::time::parse_rfc3339; +use reqsign_core::{time::now, Context, ProvideCredential, Result}; /// The maximum impersonated token lifetime allowed, 1 hour. const MAX_LIFETIME: Duration = Duration::from_secs(3600); @@ -176,9 +176,9 @@ impl ExternalAccountCredentialProvider { reqsign_core::Error::unexpected("failed to parse STS response").with_source(e) })?; - let expires_at = token_resp.expires_in.map(|expires_in| { - now() + chrono::TimeDelta::try_seconds(expires_in as i64).expect("in bounds") - }); + let expires_at = token_resp + .expires_in + .map(|expires_in| now() + jiff::SignedDuration::from_secs(expires_in as i64)); Ok(Token { access_token: token_resp.access_token, @@ -247,9 +247,7 @@ impl ExternalAccountCredentialProvider { })?; // Parse expire time from RFC3339 format - let expires_at = chrono::DateTime::parse_from_rfc3339(&token_resp.expire_time) - .ok() - .map(|dt| dt.with_timezone(&chrono::Utc)); + let expires_at = parse_rfc3339(&token_resp.expire_time).ok(); Ok(Some(Token { access_token: token_resp.access_token, diff --git a/services/google/src/provide_credential/impersonated_service_account.rs b/services/google/src/provide_credential/impersonated_service_account.rs index a1e53b60..5760488a 100644 --- a/services/google/src/provide_credential/impersonated_service_account.rs +++ b/services/google/src/provide_credential/impersonated_service_account.rs @@ -21,9 +21,9 @@ use http::header::CONTENT_TYPE; use log::{debug, error}; use serde::{Deserialize, Serialize}; -use reqsign_core::{time::now, Context, ProvideCredential, Result}; - use crate::credential::{Credential, ImpersonatedServiceAccount, Token}; +use reqsign_core::time::parse_rfc3339; +use reqsign_core::{time::now, Context, ProvideCredential, Result}; /// The maximum impersonated token lifetime allowed, 1 hour. const MAX_LIFETIME: Duration = Duration::from_secs(3600); @@ -135,9 +135,9 @@ impl ImpersonatedServiceAccountCredentialProvider { reqsign_core::Error::unexpected("failed to parse token response").with_source(e) })?; - let expires_at = token_resp.expires_in.map(|expires_in| { - now() + chrono::TimeDelta::try_seconds(expires_in as i64).expect("in bounds") - }); + let expires_at = token_resp + .expires_in + .map(|expires_in| now() + jiff::SignedDuration::from_secs(expires_in as i64)); Ok(Token { access_token: token_resp.access_token, @@ -200,9 +200,7 @@ impl ImpersonatedServiceAccountCredentialProvider { })?; // Parse expire time from RFC3339 format - let expires_at = chrono::DateTime::parse_from_rfc3339(&token_resp.expire_time) - .ok() - .map(|dt| dt.with_timezone(&chrono::Utc)); + let expires_at = parse_rfc3339(&token_resp.expire_time).ok(); Ok(Token { access_token: token_resp.access_token, diff --git a/services/google/src/provide_credential/vm_metadata.rs b/services/google/src/provide_credential/vm_metadata.rs index bd09ccd0..48b24212 100644 --- a/services/google/src/provide_credential/vm_metadata.rs +++ b/services/google/src/provide_credential/vm_metadata.rs @@ -106,8 +106,7 @@ impl ProvideCredential for VmMetadataCredentialProvider { .with_source(e) })?; - let expires_at = now() - + chrono::TimeDelta::try_seconds(token_resp.expires_in as i64).expect("in bounds"); + let expires_at = now() + jiff::SignedDuration::from_secs(token_resp.expires_in as i64); let token = Token { access_token: token_resp.access_token, diff --git a/services/google/src/sign_request.rs b/services/google/src/sign_request.rs index fa4e2f95..b2d58a49 100644 --- a/services/google/src/sign_request.rs +++ b/services/google/src/sign_request.rs @@ -47,7 +47,7 @@ struct Claims { impl Claims { fn new(client_email: &str, scope: &str) -> Self { - let current = now().timestamp() as u64; + let current = now().as_second() as u64; Claims { iss: client_email.to_string(), @@ -155,9 +155,9 @@ impl RequestSigner { reqsign_core::Error::unexpected("failed to parse token response").with_source(e) })?; - let expires_at = token_resp.expires_in.map(|expires_in| { - now() + chrono::TimeDelta::try_seconds(expires_in as i64).expect("in bounds") - }); + let expires_at = token_resp + .expires_in + .map(|expires_in| now() + jiff::SignedDuration::from_secs(expires_in as i64)); Ok(Token { access_token: token_resp.access_token, @@ -373,7 +373,7 @@ fn canonicalize_query( req: &mut SigningRequest, method: SigningMethod, cred: &ServiceAccount, - now: DateTime, + now: Timestamp, service: &str, region: &str, ) -> Result<()> { diff --git a/services/huaweicloud-obs/Cargo.toml b/services/huaweicloud-obs/Cargo.toml index 6842eb28..3f0c464c 100644 --- a/services/huaweicloud-obs/Cargo.toml +++ b/services/huaweicloud-obs/Cargo.toml @@ -29,8 +29,8 @@ repository.workspace = true [dependencies] anyhow.workspace = true async-trait.workspace = true -chrono.workspace = true http.workspace = true +jiff.workspace = true log.workspace = true once_cell.workspace = true percent-encoding.workspace = true diff --git a/services/huaweicloud-obs/src/sign_request.rs b/services/huaweicloud-obs/src/sign_request.rs index 61769561..893c0a1d 100644 --- a/services/huaweicloud-obs/src/sign_request.rs +++ b/services/huaweicloud-obs/src/sign_request.rs @@ -34,7 +34,7 @@ use super::credential::Credential; use reqsign_core::hash::base64_hmac_sha1; use reqsign_core::time::format_http_date; use reqsign_core::time::now; -use reqsign_core::time::DateTime; +use reqsign_core::time::Timestamp; use reqsign_core::{SignRequest, SigningMethod, SigningRequest}; /// RequestSigner that implement Huawei Cloud Object Storage Service Authorization. @@ -43,7 +43,7 @@ use reqsign_core::{SignRequest, SigningMethod, SigningRequest}; #[derive(Debug)] pub struct RequestSigner { bucket: String, - time: Option, + time: Option, } impl RequestSigner { @@ -62,7 +62,7 @@ impl RequestSigner { /// We should always take current time to sign requests. /// Only use this function for testing. #[cfg(test)] - pub fn with_time(mut self, time: DateTime) -> Self { + pub fn with_time(mut self, time: Timestamp) -> Self { self.time = Some(time); self } @@ -110,8 +110,8 @@ impl SignRequest for RequestSigner { ctx.query_push("AccessKeyId", &k.access_key_id); ctx.query_push( "Expires", - (now + chrono::TimeDelta::from_std(expire).unwrap()) - .timestamp() + (now + jiff::SignedDuration::try_from(expire).unwrap()) + .as_second() .to_string(), ); ctx.query_push( @@ -144,7 +144,7 @@ impl SignRequest for RequestSigner { fn string_to_sign( ctx: &mut SigningRequest, cred: &Credential, - now: DateTime, + now: Timestamp, method: SigningMethod, bucket: &str, ) -> Result { @@ -169,7 +169,7 @@ fn string_to_sign( writeln!( &mut s, "{}", - (now + chrono::TimeDelta::from_std(expires).unwrap()).timestamp() + (now + jiff::SignedDuration::try_from(expires).unwrap()).as_second() )?; } } @@ -303,9 +303,9 @@ static SUBRESOURCES: Lazy> = Lazy::new(|| { mod tests { use std::str::FromStr; - use chrono::Utc; use http::header::HeaderName; use http::Uri; + use reqsign_core::time::parse_rfc2822; use reqsign_core::Result; use reqsign_core::{Context, OsEnv, Signer}; use reqsign_file_read_tokio::TokioFileRead; @@ -317,11 +317,8 @@ mod tests { #[tokio::test] async fn test_sign() -> Result<()> { let loader = StaticCredentialProvider::new("access_key", "123456"); - let builder = RequestSigner::new("bucket").with_time( - chrono::DateTime::parse_from_rfc2822("Mon, 15 Aug 2022 16:50:12 GMT") - .unwrap() - .with_timezone(&Utc), - ); + let builder = + RequestSigner::new("bucket").with_time(parse_rfc2822("Mon, 15 Aug 2022 16:50:12 GMT")?); let ctx = Context::new() .with_file_read(TokioFileRead) @@ -359,11 +356,8 @@ mod tests { #[tokio::test] async fn test_sign_with_subresource() -> Result<()> { let loader = StaticCredentialProvider::new("access_key", "123456"); - let builder = RequestSigner::new("bucket").with_time( - chrono::DateTime::parse_from_rfc2822("Mon, 15 Aug 2022 16:50:12 GMT") - .unwrap() - .with_timezone(&Utc), - ); + let builder = + RequestSigner::new("bucket").with_time(parse_rfc2822("Mon, 15 Aug 2022 16:50:12 GMT")?); let ctx = Context::new() .with_file_read(TokioFileRead) @@ -403,11 +397,8 @@ mod tests { #[tokio::test] async fn test_sign_list_objects() -> Result<()> { let loader = StaticCredentialProvider::new("access_key", "123456"); - let builder = RequestSigner::new("bucket").with_time( - chrono::DateTime::parse_from_rfc2822("Mon, 15 Aug 2022 16:50:12 GMT") - .unwrap() - .with_timezone(&Utc), - ); + let builder = + RequestSigner::new("bucket").with_time(parse_rfc2822("Mon, 15 Aug 2022 16:50:12 GMT")?); let ctx = Context::new() .with_file_read(TokioFileRead) diff --git a/services/oracle/Cargo.toml b/services/oracle/Cargo.toml index 651bb286..8531ceab 100644 --- a/services/oracle/Cargo.toml +++ b/services/oracle/Cargo.toml @@ -30,14 +30,12 @@ repository.workspace = true anyhow.workspace = true async-trait.workspace = true base64.workspace = true -chrono.workspace = true http.workspace = true +jiff.workspace = true log.workspace = true reqsign-core.workspace = true rsa.workspace = true rust-ini.workspace = true -serde.workspace = true - [dev-dependencies] env_logger = "0.11" diff --git a/services/oracle/src/credential.rs b/services/oracle/src/credential.rs index 64d95c6a..eb9731c8 100644 --- a/services/oracle/src/credential.rs +++ b/services/oracle/src/credential.rs @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -use reqsign_core::time::{now, DateTime}; +use reqsign_core::time::{now, Timestamp}; use reqsign_core::utils::Redact; use reqsign_core::SigningCredential; use std::fmt::{Debug, Formatter}; @@ -32,7 +32,7 @@ pub struct Credential { /// Fingerprint of the API Key. pub fingerprint: String, /// Expiration time for this credential. - pub expires_in: Option, + pub expires_in: Option, } impl Debug for Credential { @@ -59,7 +59,7 @@ impl SigningCredential for Credential { // Take 120s as buffer to avoid edge cases. if let Some(valid) = self .expires_in - .map(|v| v > now() + chrono::TimeDelta::try_minutes(2).expect("in bounds")) + .map(|v| v > now() + jiff::SignedDuration::from_mins(2)) { return valid; } diff --git a/services/oracle/src/provide_credential/config.rs b/services/oracle/src/provide_credential/config.rs index cec03da2..71d8c5f4 100644 --- a/services/oracle/src/provide_credential/config.rs +++ b/services/oracle/src/provide_credential/config.rs @@ -63,8 +63,7 @@ impl ProvideCredential for ConfigCredentialProvider { fingerprint: fingerprint.clone(), // Set expires_in to 10 minutes to enforce re-read expires_in: Some( - reqsign_core::time::now() - + chrono::TimeDelta::try_minutes(10).expect("in bounds"), + reqsign_core::time::now() + jiff::SignedDuration::from_mins(10), ), })) } diff --git a/services/oracle/src/provide_credential/config_file.rs b/services/oracle/src/provide_credential/config_file.rs index 2ea4f2f0..8b7ebeab 100644 --- a/services/oracle/src/provide_credential/config_file.rs +++ b/services/oracle/src/provide_credential/config_file.rs @@ -109,8 +109,7 @@ impl ProvideCredential for ConfigFileCredentialProvider { key_file: expanded_key_file, fingerprint: fingerprint.to_string(), expires_in: Some( - reqsign_core::time::now() - + chrono::TimeDelta::try_minutes(10).expect("in bounds"), + reqsign_core::time::now() + jiff::SignedDuration::from_mins(10), ), })) } diff --git a/services/oracle/src/provide_credential/env.rs b/services/oracle/src/provide_credential/env.rs index f92dbd8e..582828da 100644 --- a/services/oracle/src/provide_credential/env.rs +++ b/services/oracle/src/provide_credential/env.rs @@ -65,8 +65,7 @@ impl ProvideCredential for EnvCredentialProvider { key_file: expanded_key_file, fingerprint: fingerprint.clone(), expires_in: Some( - reqsign_core::time::now() - + chrono::TimeDelta::try_minutes(10).expect("in bounds"), + reqsign_core::time::now() + jiff::SignedDuration::from_mins(10), ), })) } diff --git a/services/tencent-cos/Cargo.toml b/services/tencent-cos/Cargo.toml index 298fd388..f513dcc7 100644 --- a/services/tencent-cos/Cargo.toml +++ b/services/tencent-cos/Cargo.toml @@ -30,8 +30,8 @@ repository.workspace = true [dependencies] anyhow.workspace = true async-trait.workspace = true -chrono.workspace = true http.workspace = true +jiff.workspace = true log.workspace = true percent-encoding.workspace = true reqsign-core.workspace = true diff --git a/services/tencent-cos/src/credential.rs b/services/tencent-cos/src/credential.rs index cb5a63a4..cb317b02 100644 --- a/services/tencent-cos/src/credential.rs +++ b/services/tencent-cos/src/credential.rs @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -use reqsign_core::time::{now, DateTime}; +use reqsign_core::time::{now, Timestamp}; use reqsign_core::utils::Redact; use reqsign_core::SigningCredential; use std::fmt::{Debug, Formatter}; @@ -30,7 +30,7 @@ pub struct Credential { /// Security token for temporary credentials pub security_token: Option, /// Expiration time for this credential - pub expires_in: Option, + pub expires_in: Option, } impl Debug for Credential { @@ -52,7 +52,7 @@ impl SigningCredential for Credential { // Take 120s as buffer to avoid edge cases. if let Some(valid) = self .expires_in - .map(|v| v > now() + chrono::TimeDelta::try_minutes(2).expect("in bounds")) + .map(|v| v > now() + jiff::SignedDuration::from_mins(2)) { return valid; } diff --git a/services/tencent-cos/src/provide_credential/assume_role_with_web_identity.rs b/services/tencent-cos/src/provide_credential/assume_role_with_web_identity.rs index c0ee0dc8..fb004871 100644 --- a/services/tencent-cos/src/provide_credential/assume_role_with_web_identity.rs +++ b/services/tencent-cos/src/provide_credential/assume_role_with_web_identity.rs @@ -106,7 +106,7 @@ impl ProvideCredential for AssumeRoleWithWebIdentityCredentialProvider { .header(CONTENT_LENGTH, bs.len()) .header("X-TC-Action", "AssumeRoleWithWebIdentity") .header("X-TC-Region", ®ion) - .header("X-TC-Timestamp", now().timestamp()) + .header("X-TC-Timestamp", now().as_second()) .header("X-TC-Version", "2018-08-13") .body(bs.into())?; diff --git a/services/tencent-cos/src/sign_request.rs b/services/tencent-cos/src/sign_request.rs index 89c90346..779a1c75 100644 --- a/services/tencent-cos/src/sign_request.rs +++ b/services/tencent-cos/src/sign_request.rs @@ -23,7 +23,7 @@ use http::request::Parts; use log::debug; use percent_encoding::{percent_decode_str, utf8_percent_encode}; use reqsign_core::hash::{hex_hmac_sha1, hex_sha1}; -use reqsign_core::time::{format_http_date, now, DateTime}; +use reqsign_core::time::{format_http_date, now, Timestamp}; use reqsign_core::{Context, Result, SignRequest, SigningRequest}; use std::time::Duration; @@ -32,7 +32,7 @@ use std::time::Duration; /// - [Tencent COS Signature](https://cloud.tencent.com/document/product/436/7778) #[derive(Debug, Default)] pub struct RequestSigner { - time: Option, + time: Option, } impl RequestSigner { @@ -48,7 +48,7 @@ impl RequestSigner { /// We should always take current time to sign requests. /// Only use this function for testing. #[cfg(test)] - pub fn with_time(mut self, time: DateTime) -> Self { + pub fn with_time(mut self, time: Timestamp) -> Self { self.time = Some(time); self } @@ -116,13 +116,13 @@ impl SignRequest for RequestSigner { fn build_signature( ctx: &mut SigningRequest, cred: &Credential, - now: DateTime, + now: Timestamp, expires: Duration, ) -> String { let key_time = format!( "{};{}", - now.timestamp(), - (now + chrono::TimeDelta::from_std(expires).unwrap()).timestamp() + now.as_second(), + (now + jiff::SignedDuration::try_from(expires).unwrap()).as_second() ); let sign_key = hex_hmac_sha1(cred.secret_key.as_bytes(), key_time.as_bytes()); diff --git a/services/tencent-cos/tests/credential_chain.rs b/services/tencent-cos/tests/credential_chain.rs index e381ae27..66457149 100644 --- a/services/tencent-cos/tests/credential_chain.rs +++ b/services/tencent-cos/tests/credential_chain.rs @@ -199,9 +199,7 @@ impl ProvideCredential for SecurityTokenProvider { secret_id: "temp_id".to_string(), secret_key: "temp_key".to_string(), security_token: Some("security_token".to_string()), - expires_in: Some( - reqsign_core::time::now() + chrono::TimeDelta::try_hours(1).expect("in bounds"), - ), + expires_in: Some(reqsign_core::time::now() + jiff::SignedDuration::from_hours(1)), })) } }