aboutsummaryrefslogtreecommitdiff
path: root/src/api/s3/encryption.rs
diff options
context:
space:
mode:
authorAlex Auvolat <alex@adnab.me>2024-02-23 18:10:38 +0100
committerAlex Auvolat <alex@adnab.me>2024-02-27 10:15:17 +0100
commitc7d3c9887f433fefece38c0a7f7774a4193ba869 (patch)
tree5808f21fdf1edacb70089f7951acad5352074588 /src/api/s3/encryption.rs
parenta12153ad282b6d939b205191b2ee19671894428c (diff)
downloadgarage-c7d3c9887f433fefece38c0a7f7774a4193ba869.tar.gz
garage-c7d3c9887f433fefece38c0a7f7774a4193ba869.zip
[sse-c] hook sse-c decryption into GetObject
Diffstat (limited to 'src/api/s3/encryption.rs')
-rw-r--r--src/api/s3/encryption.rs98
1 files changed, 79 insertions, 19 deletions
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<Aes256Gcm::NonceSize> = 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<Cow<'a, [u8]>, 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<Cow<'a, [u8]>, Error> {
+ match self {
+ Self::SseC { client_key, .. } => {
+ let cipher = Aes256Gcm::new(&client_key);
+ let nonce_size = Aes256Gcm::NonceSize::to_usize();
+ let nonce: Nonce<Aes256Gcm::NonceSize> = 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<OrderTag>,
+ ) -> Result<ByteStream, Error> {
+ 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();