use chrono::{DateTime, Utc}; use hmac::{Hmac, Mac}; use sha2::Sha256; use hyper::header::HeaderName; use hyper::{body::Incoming as IncomingBody, Request}; use garage_model::garage::Garage; use garage_model::key_table::Key; use garage_util::data::{sha256sum, Hash}; use error::*; pub mod checksum; pub mod error; pub mod payload; pub mod streaming; pub const SHORT_DATE: &str = "%Y%m%d"; pub const LONG_DATETIME: &str = "%Y%m%dT%H%M%SZ"; // ---- Constants used in AWSv4 signatures ---- pub const X_AMZ_ALGORITHM: HeaderName = HeaderName::from_static("x-amz-algorithm"); pub const X_AMZ_CREDENTIAL: HeaderName = HeaderName::from_static("x-amz-credential"); pub const X_AMZ_DATE: HeaderName = HeaderName::from_static("x-amz-date"); pub const X_AMZ_EXPIRES: HeaderName = HeaderName::from_static("x-amz-expires"); pub const X_AMZ_SIGNEDHEADERS: HeaderName = HeaderName::from_static("x-amz-signedheaders"); pub const X_AMZ_SIGNATURE: HeaderName = HeaderName::from_static("x-amz-signature"); pub const X_AMZ_CONTENT_SH256: HeaderName = HeaderName::from_static("x-amz-content-sha256"); pub const X_AMZ_TRAILER: HeaderName = HeaderName::from_static("x-amz-trailer"); /// Result of `sha256("")` pub(crate) const EMPTY_STRING_HEX_DIGEST: &str = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; // Signature calculation algorithm pub const AWS4_HMAC_SHA256: &str = "AWS4-HMAC-SHA256"; type HmacSha256 = Hmac; // Possible values for x-amz-content-sha256, in addition to the actual sha256 pub const UNSIGNED_PAYLOAD: &str = "UNSIGNED-PAYLOAD"; pub const STREAMING_AWS4_HMAC_SHA256_PAYLOAD: &str = "STREAMING-AWS4-HMAC-SHA256-PAYLOAD"; // Used in the computation of StringToSign pub const AWS4_HMAC_SHA256_PAYLOAD: &str = "AWS4-HMAC-SHA256-PAYLOAD"; // ---- enums to describe stuff going on in signature calculation ---- pub enum ContentSha256Header { UnsignedPayload, Sha256Hash(String), StreamingPayload { trailer: Option, algorithm: Option, }, } pub enum SigningAlgorithm { AwsHmacSha256, } pub enum TrailerHeader { XAmzChecksumCrc32, XAmzChecksumCrc32c, XAmzChecksumCrc64Nvme, } // ---- top-level functions ---- pub async fn verify_request( garage: &Garage, mut req: Request, service: &'static str, ) -> Result<(Request, Key, Option), Error> { let (api_key, mut content_sha256) = payload::check_payload_signature(&garage, &mut req, service).await?; let api_key = api_key.ok_or_else(|| Error::forbidden("Garage does not support anonymous access yet"))?; let req = streaming::parse_streaming_body( &api_key, req, &mut content_sha256, &garage.config.s3_api.s3_region, service, )?; Ok((req, api_key, content_sha256)) } pub fn verify_signed_content(expected_sha256: Hash, body: &[u8]) -> Result<(), Error> { if expected_sha256 != sha256sum(body) { return Err(Error::bad_request( "Request content hash does not match signed hash".to_string(), )); } Ok(()) } pub fn signing_hmac( datetime: &DateTime, secret_key: &str, region: &str, service: &str, ) -> Result { let secret = String::from("AWS4") + secret_key; let mut date_hmac = HmacSha256::new_from_slice(secret.as_bytes())?; date_hmac.update(datetime.format(SHORT_DATE).to_string().as_bytes()); let mut region_hmac = HmacSha256::new_from_slice(&date_hmac.finalize().into_bytes())?; region_hmac.update(region.as_bytes()); let mut service_hmac = HmacSha256::new_from_slice(®ion_hmac.finalize().into_bytes())?; service_hmac.update(service.as_bytes()); let mut signing_hmac = HmacSha256::new_from_slice(&service_hmac.finalize().into_bytes())?; signing_hmac.update(b"aws4_request"); let hmac = HmacSha256::new_from_slice(&signing_hmac.finalize().into_bytes())?; Ok(hmac) } pub fn compute_scope(datetime: &DateTime, region: &str, service: &str) -> String { format!( "{}/{}/{}/aws4_request", datetime.format(SHORT_DATE), region, service ) }