Skip to content

Commit

Permalink
Migrate from chrono to time to solve RUSTSEC-2020-0159 (#250)
Browse files Browse the repository at this point in the history
`chrono` still hasn't found a solution to [RUSTSEC-2020-0159] whereas
`time` already solved its vulnerability to [RUSTSEC-2020-0071] by hiding
the affected functionality behind a cfg flag (not to be confused with a
`feature`, such `cfg`s can only be enabled through `RUSTFLAGS`).  At the
same time `chrono` is a superset of `time` even though this crate hardly
uses any functionality of it: only UTC time is needed which does not
suffer from aforementioned local time vulnerabilities.

[RUSTSEC-2020-0071]: https://rustsec.org/advisories/RUSTSEC-2020-0071
[RUSTSEC-2020-0159]: https://rustsec.org/advisories/RUSTSEC-2020-0159

Co-authored-by: Drazen Urch <drazen@urch.eu>
  • Loading branch information
MarijnS95 and durch authored Feb 12, 2022
1 parent 10cf754 commit a917f78
Show file tree
Hide file tree
Showing 9 changed files with 51 additions and 36 deletions.
2 changes: 1 addition & 1 deletion aws-creds/src/credentials.rs
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ impl Credentials {
access_key_id: String,
secret_access_key: String,
token: String,
//expiration: chrono::DateTime<chrono::Utc>, // TODO fix #163
//expiration: time::OffsetDateTime, // TODO fix #163
}

let resp: Response = match env::var("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI") {
Expand Down
2 changes: 1 addition & 1 deletion s3/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ aws-creds = { version = "0.27", default-features = false }
aws-region = "0.23"
base64 = "0.13"
cfg-if = "1"
chrono = "0.4"
time = { version = "0.3", features = ["formatting", "macros"] }
futures-io = { version = "0.3", optional = true }
futures-util = { version = "0.3", optional = true, features = ["io"] }
hex = "0.4"
Expand Down
8 changes: 4 additions & 4 deletions s3/src/blocking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use attohttpc::header::HeaderName;

use super::bucket::Bucket;
use super::command::Command;
use chrono::{DateTime, Utc};
use time::OffsetDateTime;

use crate::command::HttpMethod;
use crate::request_trait::Request;
Expand All @@ -30,15 +30,15 @@ pub struct AttoRequest<'a> {
pub bucket: &'a Bucket,
pub path: &'a str,
pub command: Command<'a>,
pub datetime: DateTime<Utc>,
pub datetime: OffsetDateTime,
pub sync: bool,
}

impl<'a> Request for AttoRequest<'a> {
type Response = attohttpc::Response;
type HeaderMap = attohttpc::header::HeaderMap;

fn datetime(&self) -> DateTime<Utc> {
fn datetime(&self) -> OffsetDateTime {
self.datetime
}

Expand Down Expand Up @@ -133,7 +133,7 @@ impl<'a> AttoRequest<'a> {
bucket,
path,
command,
datetime: Utc::now(),
datetime: OffsetDateTime::now_utc(),
sync: false,
}
}
Expand Down
2 changes: 1 addition & 1 deletion s3/src/bucket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1799,7 +1799,7 @@ mod test {
)
.unwrap();
bucket.set_listobjects_v1();
return bucket;
bucket
}

fn test_minio_bucket() -> Bucket {
Expand Down
3 changes: 2 additions & 1 deletion s3/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,6 @@ pub mod surf_request;
pub mod request_trait;
pub mod utils;

const LONG_DATE: &str = "%Y%m%dT%H%M%SZ";
const LONG_DATETIME: &[time::format_description::FormatItem<'static>] =
time::macros::format_description!("[year][month][day]T[hour][minute][second]Z");
const EMPTY_PAYLOAD_SHA: &str = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
8 changes: 4 additions & 4 deletions s3/src/request.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
extern crate base64;
extern crate md5;

use chrono::{DateTime, Utc};
use maybe_async::maybe_async;
use reqwest::{Client, Response};
use time::OffsetDateTime;

use crate::bucket::Bucket;
use crate::command::Command;
Expand All @@ -19,7 +19,7 @@ pub struct Reqwest<'a> {
pub bucket: &'a Bucket,
pub path: &'a str,
pub command: Command<'a>,
pub datetime: DateTime<Utc>,
pub datetime: OffsetDateTime,
pub sync: bool,
}

Expand All @@ -36,7 +36,7 @@ impl<'a> Request for Reqwest<'a> {
self.path.to_string()
}

fn datetime(&self) -> DateTime<Utc> {
fn datetime(&self) -> OffsetDateTime {
self.datetime
}

Expand Down Expand Up @@ -147,7 +147,7 @@ impl<'a> Reqwest<'a> {
bucket,
path,
command,
datetime: Utc::now(),
datetime: OffsetDateTime::now_utc(),
sync: false,
}
}
Expand Down
14 changes: 9 additions & 5 deletions s3/src/request_trait.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use chrono::{DateTime, Utc};
use hmac::Mac;
use hmac::NewMac;
use time::format_description::well_known::Rfc2822;
use time::OffsetDateTime;
use url::Url;

use crate::bucket::Bucket;
use crate::command::Command;
use crate::signing;
use crate::LONG_DATE;
use crate::LONG_DATETIME;
use anyhow::anyhow;
use anyhow::Result;
use http::header::{
Expand Down Expand Up @@ -34,7 +35,7 @@ pub trait Request {
#[cfg(feature = "sync")]
fn response_data_to_writer<T: std::io::Write + Send>(&self, writer: &mut T) -> Result<u16>;
async fn response_header(&self) -> Result<(Self::HeaderMap, u16)>;
fn datetime(&self) -> DateTime<Utc>;
fn datetime(&self) -> OffsetDateTime;
fn bucket(&self) -> Bucket;
fn command(&self) -> Command;
fn path(&self) -> String;
Expand Down Expand Up @@ -74,7 +75,7 @@ pub trait Request {
}

fn long_date(&self) -> String {
self.datetime().format(LONG_DATE).to_string()
self.datetime().format(LONG_DATETIME).unwrap()
}

fn string_to_sign(&self, request: &str) -> String {
Expand Down Expand Up @@ -443,7 +444,10 @@ pub trait Request {
// range and can't be used again e.g. reply attacks. Adding this header
// after the generation of the Authorization header leaves it out of
// the signed headers.
headers.insert(DATE, self.datetime().to_rfc2822().parse().unwrap());
headers.insert(
DATE,
self.datetime().format(&Rfc2822).unwrap().parse().unwrap(),
);

Ok(headers)
}
Expand Down
40 changes: 25 additions & 15 deletions s3/src/signing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,20 @@
use std::str;

use chrono::{DateTime, Utc};
use hmac::{Hmac, Mac, NewMac};
use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS};
use sha2::{Digest, Sha256};
use time::{macros::format_description, OffsetDateTime};
use url::Url;

use crate::region::Region;
use crate::LONG_DATETIME;
use anyhow::anyhow;
use anyhow::Result;
use http::HeaderMap;

const SHORT_DATE: &str = "%Y%m%d";
const LONG_DATETIME: &str = "%Y%m%dT%H%M%SZ";
const SHORT_DATE: &[time::format_description::FormatItem<'static>] =
format_description!("[year][month][day]");

pub type HmacSha256 = Hmac<Sha256>;

Expand Down Expand Up @@ -130,22 +131,22 @@ pub fn canonical_request(method: &str, url: &Url, headers: &HeaderMap, sha256: &
}

/// Generate an AWS scope string.
pub fn scope_string(datetime: &DateTime<Utc>, region: &Region) -> String {
pub fn scope_string(datetime: &OffsetDateTime, region: &Region) -> String {
format!(
"{date}/{region}/s3/aws4_request",
date = datetime.format(SHORT_DATE),
date = datetime.format(SHORT_DATE).unwrap(),
region = region
)
}

/// Generate the "string to sign" - the value to which the HMAC signing is
/// applied to sign requests.
pub fn string_to_sign(datetime: &DateTime<Utc>, region: &Region, canonical_req: &str) -> String {
pub fn string_to_sign(datetime: &OffsetDateTime, region: &Region, canonical_req: &str) -> String {
let mut hasher = Sha256::default();
hasher.update(canonical_req.as_bytes());
let string_to = format!(
"AWS4-HMAC-SHA256\n{timestamp}\n{scope}\n{hash}",
timestamp = datetime.format(LONG_DATETIME),
timestamp = datetime.format(LONG_DATETIME).unwrap(),
scope = scope_string(datetime, region),
hash = hex::encode(hasher.finalize().as_slice())
);
Expand All @@ -155,15 +156,15 @@ pub fn string_to_sign(datetime: &DateTime<Utc>, region: &Region, canonical_req:
/// Generate the AWS signing key, derived from the secret key, date, region,
/// and service name.
pub fn signing_key(
datetime: &DateTime<Utc>,
datetime: &OffsetDateTime,
secret_key: &str,
region: &Region,
service: &str,
) -> Result<Vec<u8>> {
let secret = format!("AWS4{}", secret_key);
let mut date_hmac =
HmacSha256::new_from_slice(secret.as_bytes()).map_err(|e| anyhow! {"{}",e})?;
date_hmac.update(datetime.format(SHORT_DATE).to_string().as_bytes());
date_hmac.update(datetime.format(SHORT_DATE).unwrap().as_bytes());
let mut region_hmac = HmacSha256::new_from_slice(&date_hmac.finalize().into_bytes())
.map_err(|e| anyhow! {"{}",e})?;
region_hmac.update(region.to_string().as_bytes());
Expand All @@ -179,7 +180,7 @@ pub fn signing_key(
/// Generate the AWS authorization header.
pub fn authorization_header(
access_key: &str,
datetime: &DateTime<Utc>,
datetime: &OffsetDateTime,
region: &Region,
signed_headers: &str,
signature: &str,
Expand All @@ -196,7 +197,7 @@ pub fn authorization_header(

pub fn authorization_query_params_no_sig(
access_key: &str,
datetime: &DateTime<Utc>,
datetime: &OffsetDateTime,
region: &Region,
expires: u32,
custom_headers: Option<&HeaderMap>,
Expand Down Expand Up @@ -224,7 +225,7 @@ pub fn authorization_query_params_no_sig(
&X-Amz-Expires={expires}\
&X-Amz-SignedHeaders={signed_headers}",
credentials = credentials,
long_date = datetime.format(LONG_DATETIME),
long_date = datetime.format(LONG_DATETIME).unwrap(),
expires = expires,
signed_headers = signed_headers_string
);
Expand All @@ -241,8 +242,9 @@ pub fn authorization_query_params_no_sig(

#[cfg(test)]
mod tests {
use chrono::{TimeZone, Utc};
use std::convert::TryInto;
use std::str;
use time::Date;
use url::Url;

use super::*;
Expand Down Expand Up @@ -333,7 +335,11 @@ mod tests {
fn test_aws_signing_key() {
let key = "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY";
let expected = "c4afb1cc5771d871763a393e44b703571b55cc28424d1a5e86da6ed3c154a4b9";
let datetime = Utc.ymd(2015, 8, 30).and_hms(0, 0, 0);
let datetime = Date::from_calendar_date(2015, 8.try_into().unwrap(), 30)
.unwrap()
.with_hms(0, 0, 0)
.unwrap()
.assume_utc();
let signature = signing_key(&datetime, key, &"us-east-1".parse().unwrap(), "iam").unwrap();
assert_eq!(expected, hex::encode(signature));
}
Expand Down Expand Up @@ -378,7 +384,11 @@ mod tests {
let canonical = canonical_request("GET", &url, &headers, EXPECTED_SHA);
assert_eq!(EXPECTED_CANONICAL_REQUEST, canonical);

let datetime = Utc.ymd(2013, 5, 24).and_hms(0, 0, 0);
let datetime = Date::from_calendar_date(2013, 5.try_into().unwrap(), 24)
.unwrap()
.with_hms(0, 0, 0)
.unwrap()
.assume_utc();
let string_to_sign = string_to_sign(&datetime, &"us-east-1".parse().unwrap(), &canonical);
assert_eq!(EXPECTED_STRING_TO_SIGN, string_to_sign);

Expand Down
8 changes: 4 additions & 4 deletions s3/src/surf_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use futures_io::AsyncWrite;

use super::bucket::Bucket;
use super::command::Command;
use chrono::{DateTime, Utc};
use time::OffsetDateTime;

use crate::command::HttpMethod;
use crate::request_trait::Request;
Expand All @@ -19,7 +19,7 @@ pub struct SurfRequest<'a> {
pub bucket: &'a Bucket,
pub path: &'a str,
pub command: Command<'a>,
pub datetime: DateTime<Utc>,
pub datetime: OffsetDateTime,
pub sync: bool,
}

Expand All @@ -28,7 +28,7 @@ impl<'a> Request for SurfRequest<'a> {
type Response = surf::Response;
type HeaderMap = HeaderMap;

fn datetime(&self) -> DateTime<Utc> {
fn datetime(&self) -> OffsetDateTime {
self.datetime
}

Expand Down Expand Up @@ -136,7 +136,7 @@ impl<'a> SurfRequest<'a> {
bucket,
path,
command,
datetime: Utc::now(),
datetime: OffsetDateTime::now_utc(),
sync: false,
}
}
Expand Down

0 comments on commit a917f78

Please sign in to comment.