kevinjqliu commented on code in PR #742:
URL: 
https://github.com/apache/arrow-rs-object-store/pull/742#discussion_r3382706299


##########
src/azure/client.rs:
##########
@@ -190,6 +197,57 @@ impl AzureConfig {
     }
 }
 
+#[derive(Default, Clone, Debug)]
+pub(crate) struct AzureEncryptionHeaders {
+    pub encryption_key: Option<String>,
+    pub encryption_key_sha256: Option<String>,
+}
+
+impl AzureEncryptionHeaders {
+    pub(crate) fn try_new(encryption_key: Option<String>) -> Result<Self> {
+        let Some(encryption_key) = encryption_key else {
+            return Ok(Self::default());
+        };
+
+        let decoded_key = BASE64_STANDARD
+            .decode(encryption_key.as_bytes())
+            .map_err(|source| crate::Error::Generic {
+                store: STORE,
+                source: Box::new(source),
+            })?;
+
+        if decoded_key.len() != 32 {
+            return Err(crate::Error::Generic {
+                store: STORE,
+                source: format!(
+                    "Azure customer-provided encryption key must decode to 32 
bytes, got {}",
+                    decoded_key.len()
+                )
+                .into(),
+            });
+        }
+
+        let encryption_key_sha256 =
+            BASE64_STANDARD.encode(digest::digest(&digest::SHA256, 
&decoded_key));
+
+        Ok(Self {
+            encryption_key: Some(encryption_key),
+            encryption_key_sha256: Some(encryption_key_sha256),
+        })
+    }
+
+    fn apply(&self, builder: HttpRequestBuilder) -> HttpRequestBuilder {
+        match (&self.encryption_key, &self.encryption_key_sha256) {
+            (Some(encryption_key), Some(encryption_key_sha256)) => builder
+                .header(&ENCRYPTION_KEY_HEADER, encryption_key)
+                .header(&ENCRYPTION_KEY_SHA256_HEADER, encryption_key_sha256)
+                // Azure only supports this algorithm header
+                .header(&ENCRYPTION_ALGORITHM_HEADER, "AES256"),

Review Comment:
   nit: move this string up to the class 



##########
src/azure/client.rs:
##########
@@ -190,6 +197,57 @@ impl AzureConfig {
     }
 }
 
+#[derive(Default, Clone, Debug)]
+pub(crate) struct AzureEncryptionHeaders {
+    pub encryption_key: Option<String>,
+    pub encryption_key_sha256: Option<String>,
+}
+
+impl AzureEncryptionHeaders {
+    pub(crate) fn try_new(encryption_key: Option<String>) -> Result<Self> {
+        let Some(encryption_key) = encryption_key else {
+            return Ok(Self::default());
+        };
+
+        let decoded_key = BASE64_STANDARD
+            .decode(encryption_key.as_bytes())
+            .map_err(|source| crate::Error::Generic {
+                store: STORE,
+                source: Box::new(source),
+            })?;
+
+        if decoded_key.len() != 32 {

Review Comment:
   nit: why 32 here? 
   
   is it because
   > An AES-256 key is 256 bits = 32 bytes raw.



##########
src/azure/builder.rs:
##########
@@ -85,6 +85,11 @@ enum Error {
     #[error("Missing component in SAS query pair")]
     MissingSasComponent {},
 
+    #[error("Invalid encryption key: {source}")]

Review Comment:
   curious, why this is a breaking change?



##########
src/azure/client.rs:
##########
@@ -264,8 +322,8 @@ impl PutRequest<'_> {
             .as_deref()
             .map(|c| c.sensitive_request())
             .unwrap_or_default();
-        let response = self
-            .builder
+        let builder = self.config.encryption_headers.apply(self.builder);
+        let response = builder
             .header(CONTENT_LENGTH, self.payload.content_length())
             .with_azure_authorization(&credential, &self.config.account)

Review Comment:
   nit: this call feels awkward, could we move this into the request-builder 
extension layer, similar to `with_azure_authorization`?
   
   Right now `AzureEncryptionHeaders::apply(builder)` feels a little detached 
from the rest of the request construction, and it also makes it easy to apply 
the CPK headers without remembering that the request now contains secret 
material. A fluent helper like 
`with_azure_encryption_headers(&self.config.encryption_headers)` would match 
the existing style better.
   
   Ideally that helper would also mark the request as sensitive when 
`x-ms-encryption-key` is present, so callers do not need to separately remember 
to combine credential sensitivity with CPK sensitivity.



##########
src/azure/client.rs:
##########
@@ -190,6 +197,57 @@ impl AzureConfig {
     }
 }
 
+#[derive(Default, Clone, Debug)]

Review Comment:
   nit: Can we avoid deriving Debug here? This struct contains the raw 
customer-provided encryption key, so debug output could leak key material. I’d 
prefer a custom Debug impl that only reports whether a key is configured, and 
maybe the SHA-256 value if we consider that safe enough, but not the raw key.



##########
src/azure/builder.rs:
##########
@@ -1235,6 +1270,17 @@ mod tests {
         }
     }
 
+    #[test]
+    fn azure_encryption_key_roundtrip() {
+        let key = BASE64_STANDARD.encode([7_u8; 32]);
+        let builder = MicrosoftAzureBuilder::new().with_encryption_key(&key);
+
+        assert_eq!(
+            
builder.get_config_value(&AzureConfigKey::EncryptionKey).as_deref(),
+            Some(key.as_str())
+        );

Review Comment:
   could we also add tests for invalid values
   - malformed base64
   - base64 that decodes to fewer than 32 bytes
   - base64 that decodes to more than 32 bytes
   
   The happy-path roundtrip test is useful, but the main correctness behavior 
here is that invalid keys fail at build time rather than producing bad Azure 
requests.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to