diff --git a/src/api/common/signature/checksum.rs b/src/api/common/signature/checksum.rs index 0fb66ce5..2d7a1389 100644 --- a/src/api/common/signature/checksum.rs +++ b/src/api/common/signature/checksum.rs @@ -22,6 +22,7 @@ pub const CONTENT_MD5: HeaderName = HeaderName::from_static("content-md5"); pub const X_AMZ_CHECKSUM_ALGORITHM: HeaderName = HeaderName::from_static("x-amz-checksum-algorithm"); pub const X_AMZ_CHECKSUM_MODE: HeaderName = HeaderName::from_static("x-amz-checksum-mode"); +pub const X_AMZ_CHECKSUM_TYPE: HeaderName = HeaderName::from_static("x-amz-checksum-type"); pub const X_AMZ_CHECKSUM_CRC32: HeaderName = HeaderName::from_static("x-amz-checksum-crc32"); pub const X_AMZ_CHECKSUM_CRC32C: HeaderName = HeaderName::from_static("x-amz-checksum-crc32c"); pub const X_AMZ_CHECKSUM_CRC64NVME: HeaderName = @@ -29,6 +30,10 @@ pub const X_AMZ_CHECKSUM_CRC64NVME: HeaderName = pub const X_AMZ_CHECKSUM_SHA1: HeaderName = HeaderName::from_static("x-amz-checksum-sha1"); pub const X_AMZ_CHECKSUM_SHA256: HeaderName = HeaderName::from_static("x-amz-checksum-sha256"); +// Values for x-amz-checksum-type +pub const COMPOSITE: &[u8] = b"COMPOSITE"; +pub const FULL_OBJECT: &[u8] = b"FULL_OBJECT"; + pub type Crc32Checksum = [u8; 4]; pub type Crc32cChecksum = [u8; 4]; pub type Crc64NvmeChecksum = [u8; 8]; diff --git a/src/api/s3/multipart.rs b/src/api/s3/multipart.rs index dea87b98..b946fd3d 100644 --- a/src/api/s3/multipart.rs +++ b/src/api/s3/multipart.rs @@ -8,7 +8,7 @@ use crc32c::Crc32cHasher as Crc32c; use crc32fast::Hasher as Crc32; use crc64fast_nvme::Digest as Crc64Nvme; use futures::prelude::*; -use hyper::{Request, Response}; +use hyper::{header::HeaderValue, HeaderMap, Request, Response}; use md5::{Digest, Md5}; use sha1::Sha1; use sha2::Sha256; @@ -70,6 +70,7 @@ pub async fn handle_create_multipart_upload( let object_encryption = encryption.encrypt_meta(meta)?; let checksum_algorithm = request_checksum_algorithm(req.headers())?; + let checksum_type = request_checksum_type(req.headers(), &checksum_algorithm)?; // Create object in object table let object_version = ObjectVersion { @@ -79,8 +80,7 @@ pub async fn handle_create_multipart_upload( multipart: true, encryption: object_encryption, checksum_algorithm, - // TODO: add support for full-object checksums - checksum_type: checksum_algorithm.map(|_| ChecksumType::Composite), + checksum_type, }, }; let object = Object::new(*bucket_id, key.to_string(), vec![object_version]); @@ -292,6 +292,8 @@ pub async fn handle_complete_multipart_upload( let (req_head, req_body) = req.into_parts(); let expected_checksum = request_checksum_value(&req_head.headers)?; + let checksum_algorithm = expected_checksum.map(|x| x.algorithm()); + let checksum_type = request_checksum_type(&req_head.headers, &checksum_algorithm)?; let body = req_body.collect().await?; @@ -347,8 +349,9 @@ pub async fn handle_complete_multipart_upload( for req_part in body_list_of_parts.iter() { match have_parts.get(&req_part.part_number) { Some(part) if part.etag.as_ref() == Some(&req_part.etag) && part.size.is_some() => { - // alternative version: if req_part.checksum.is_some() && part.checksum != req_part.checksum { - if part.checksum != req_part.checksum { + if checksum_type == Some(ChecksumType::Composite) + && part.checksum != req_part.checksum + { return Err(Error::InvalidDigest(format!( "Invalid checksum for part {}: in request = {:?}, uploaded part = {:?}", req_part.part_number, req_part.checksum, part.checksum @@ -405,7 +408,7 @@ pub async fn handle_complete_multipart_upload( // To understand how etags are calculated, read more here: // https://docs.aws.amazon.com/AmazonS3/latest/userguide/checking-object-integrity.html // https://teppen.io/2018/06/23/aws_s3_etags/ - let mut checksummer = MultipartChecksummer::init(checksum_algorithm); + let mut checksummer = MultipartChecksummer::init(checksum_algorithm, checksum_type); for part in parts.iter() { checksummer.update(part.etag.as_ref().unwrap(), part.checksum)?; } @@ -654,6 +657,36 @@ fn parse_complete_multipart_upload_body( // ====== checksummer ==== +pub fn request_checksum_type( + headers: &HeaderMap, + algo: &Option, +) -> Result, Error> { + match (headers.get(X_AMZ_CHECKSUM_TYPE), algo) { + (None, None) => Ok(None), + (None, Some(ChecksumAlgorithm::Crc64Nvme)) => Ok(Some(ChecksumType::FullObject)), + (None, Some(_)) => Ok(Some(ChecksumType::Composite)), + (Some(_), None) => Err(Error::bad_request( + "Cannot specify x-amz-checksum-type when no checksum algorithm is in use.", + )), + (Some(x), Some(algo)) => { + let checksum_type = match x.as_bytes() { + COMPOSITE => ChecksumType::Composite, + FULL_OBJECT => ChecksumType::FullObject, + _ => return Err(Error::bad_request("Invalid x-amz-checksum-type value")), + }; + match (checksum_type, algo) { + (ChecksumType::Composite, ChecksumAlgorithm::Crc64Nvme) + | (ChecksumType::FullObject, ChecksumAlgorithm::Sha1) + | (ChecksumType::FullObject, ChecksumAlgorithm::Sha256) => Err(Error::bad_request(format!( + "checksum type {:?} is not supported for algorithm {:?}", + checksum_type, algo + ))), + _ => Ok(Some(checksum_type)), + } + } + } +} + #[derive(Default)] pub(crate) struct MultipartChecksummer { pub md5: Md5, @@ -669,7 +702,10 @@ pub(crate) enum MultipartExtraChecksummer { } impl MultipartChecksummer { - pub(crate) fn init(algo: Option) -> Self { + pub(crate) fn init(algo: Option, cktype: Option) -> Self { + if cktype == Some(ChecksumType::FullObject) { + todo!() + } Self { md5: Md5::new(), extra: match algo {