From c7d3c9887f433fefece38c0a7f7774a4193ba869 Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Fri, 23 Feb 2024 18:10:38 +0100 Subject: [sse-c] hook sse-c decryption into GetObject --- src/api/s3/encryption.rs | 98 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 79 insertions(+), 19 deletions(-) (limited to 'src/api/s3/encryption.rs') diff --git a/src/api/s3/encryption.rs b/src/api/s3/encryption.rs index 4055a67f..2d403ff3 100644 --- a/src/api/s3/encryption.rs +++ b/src/api/s3/encryption.rs @@ -4,10 +4,15 @@ use aes_gcm::{ aead::{Aead, AeadCore, KeyInit, OsRng}, Aes256Gcm, Key, Nonce, }; +use base64::prelude::*; use http::header::{HeaderName, HeaderValue}; use hyper::{body::Body, Request}; +use garage_net::stream::{ByteStream, ByteStreamReader}; +use garage_rpc::rpc_helper::OrderTag; +use garage_util::data::Hash; + use garage_model::garage::Garage; use garage_model::s3::object_table::{ObjectVersionEncryption, ObjectVersionHeaders}; @@ -29,6 +34,9 @@ const X_AMZ_COPY_SOURCE_SERVER_SIDE_ENCRYPTION_CUSTOMER_KEY_MD5: HeaderName = const CUSTOMER_ALGORITHM_AES256: HeaderValue = HeaderValue::from_static("AES256"); +const STREAM_ENC_CHUNK_SIZE: usize = 0x1000; // 4096 bytes + +#[derive(Clone, Copy)] pub enum EncryptionParams { Plaintext, SseC { @@ -98,16 +106,6 @@ impl EncryptionParams { compressed, }, ) => { - let cipher = Aes256Gcm::new(&client_key); - let nonce: Nonce = headers - .get(..12) - .ok_or_internal_error("invalid encrypted data")? - .try_into() - .unwrap(); - let plaintext = cipher.decrypt(&nonce, &headers[12..]).ok_or_bad_request( - "Invalid encryption key, could not decrypt object metadata.", - )?; - let headers = ObjectVersionHeaders::decode(&plaintext)?; let enc = Self::SseC { client_key, compression_level: if compressed { @@ -116,6 +114,8 @@ impl EncryptionParams { None }, }; + let plaintext = enc.decrypt_blob(&headers)?; + let headers = ObjectVersionHeaders::decode(&plaintext)?; Ok((enc, headers.into())) } (None, ObjectVersionEncryption::Plaintext { headers }) => { @@ -140,21 +140,79 @@ impl EncryptionParams { client_key, compression_level, } => { - let cipher = Aes256Gcm::new(&client_key); - let nonce = Aes256Gcm::generate_nonce(&mut OsRng); let plaintext = h.encode()?; - let ciphertext = cipher - .encrypt(&nonce, &plaintext) - .ok_or_internal_error("Encryption failed")?; - let headers_enc = [nonce.to_vec(), ciphertext].concat(); + let ciphertext = self.encrypt_blob(plaintext)?; Ok(ObjectVersionEncryption::SseC { - headers: headers_enc, + headers: ciphertext, compressed: compression_level.is_some(), }) } Self::Plaintext => Ok(ObjectVersionEncryption::Plaintext { headers: h }), } } + + // ---- generic function for encrypting / decrypting blobs ---- + // prepends a randomly-generated nonce to the encrypted value + + pub fn encrypt_blob<'a>(&self, blob: &'a [u8]) -> Result, Error> { + match self { + Self::SseC { client_key, .. } => { + let cipher = Aes256Gcm::new(&client_key); + let nonce = Aes256Gcm::generate_nonce(&mut OsRng); + let ciphertext = cipher + .encrypt(&nonce, &blob) + .ok_or_internal_error("Encryption failed")?; + Ok([nonce.to_vec(), ciphertext].concat().into()) + } + Self::Plaintext => Ok(blob.into()), + } + } + + pub fn decrypt_blob<'a>(&self, blob: &'a [u8]) -> Result, Error> { + match self { + Self::SseC { client_key, .. } => { + let cipher = Aes256Gcm::new(&client_key); + let nonce_size = Aes256Gcm::NonceSize::to_usize(); + let nonce: Nonce = blob + .get(..nonce_size) + .ok_or_internal_error("invalid encrypted data")? + .try_into() + .unwrap(); + let plaintext = cipher + .decrypt(&nonce, &blob[nonce_size..]) + .ok_or_bad_request( + "Invalid encryption key, could not decrypt object metadata.", + )?; + Ok(plaintext.into()) + } + Self::Plaintext => Ok(blob.into()), + } + } + + // ---- function for encrypting / decrypting byte streams ---- + + /// Get a data block from the storage node, and decrypt+decompress it + /// if necessary. If object is plaintext, just get it without any processing. + pub async fn get_and_decrypt_block( + &self, + garage: &Garage, + hash: &Hash, + order: Option, + ) -> Result { + let raw_block = garage + .block_manager + .rpc_get_block_streaming(hash, order) + .await?; + match self { + Self::Plaintext => Ok(raw_block), + Self::SseC { + client_key, + compression_level, + } => { + todo!() + } + } + } } fn parse_request_headers( @@ -171,7 +229,8 @@ fn parse_request_headers( .headers() .get(key_header) .ok_or_bad_request(format!("Missing {} header", key_header))?; - let key_bytes: [u8; 32] = base64::decode(&key_b64) + let key_bytes: [u8; 32] = BASE64_STANDARD + .decode(&key_b64) .ok_or_bad_request(format!("Invalid {} header", key_header))? .try_into() .ok_or_bad_request(format!("Invalid {} header", key_header))?; @@ -180,7 +239,8 @@ fn parse_request_headers( .headers() .get(md5_header) .ok_or_bad_request(format!("Missing {} header", md5_header))?; - let md5_bytes = base64::decode(&md5_b64) + let md5_bytes = BASE64_STANDARD + .decode(&md5_b64) .ok_or_bad_request(format!("Invalid {} header", md5_header))?; let mut hasher = Md5::new(); -- cgit v1.2.3