Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public sealed class AzblobServiceConfig : IServiceConfig
/// </summary>
public string? EncryptionAlgorithm { get; init; }
/// <summary>
/// The encryption key of Azblob service backend.
/// The Base64-encoded AES-256 customer-provided encryption key. The key must decode to exactly 32 bytes. Building an operator returns `ConfigInvalid` for invalid Base64 or a different decoded length.
/// </summary>
public string? EncryptionKey { get; init; }
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,9 @@ class Azblob implements ServiceConfig {
*/
public final String encryptionAlgorithm;
/**
* <p>The encryption key of Azblob service backend.</p>
* <p>The Base64-encoded AES-256 customer-provided encryption key.</p>
* <p>The key must decode to exactly 32 bytes. Building an operator returns
* <code>ConfigInvalid</code> for invalid Base64 or a different decoded length.</p>
*/
public final String encryptionKey;
/**
Expand Down
2 changes: 1 addition & 1 deletion bindings/python/python/opendal/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ class AzblobConfig(TypedDict):
encryption_algorithm: NotRequired[str]
"""The encryption algorithm of Azblob service backend."""
encryption_key: NotRequired[str]
"""The encryption key of Azblob service backend."""
"""The Base64-encoded AES-256 customer-provided encryption key. The key must decode to exactly 32 bytes. Building an operator returns `ConfigInvalid` for invalid Base64 or a different decoded length."""
encryption_key_sha256: NotRequired[str]
"""The encryption key sha256 of Azblob service backend."""
endpoint: NotRequired[str]
Expand Down
102 changes: 94 additions & 8 deletions core/services/azblob/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,9 @@ impl AzblobBuilder {
///
/// # Args
///
/// `v`: Base64-encoded key that matches algorithm specified in `encryption_algorithm`.
/// `v`: Base64-encoded AES-256 key that decodes to exactly 32 bytes.
/// Building an operator returns [`ErrorKind::ConfigInvalid`] if the key
/// is not valid Base64 or has a different decoded length.
///
/// # Note
///
Expand Down Expand Up @@ -212,7 +214,9 @@ impl AzblobBuilder {
///
/// # Args
///
/// `key`: Base64-encoded SHA256 digest of the key specified in encryption_key.
/// `key`: Raw AES-256 key containing exactly 32 bytes.
/// Building an operator returns [`ErrorKind::ConfigInvalid`] if the key
/// has a different length.
///
/// # Note
///
Expand Down Expand Up @@ -347,13 +351,32 @@ impl Builder for AzblobBuilder {
}
}

let encryption_key =
match &self.config.encryption_key {
None => None,
Some(v) => Some(build_header_value(v).map_err(|err| {
let encryption_key = match &self.config.encryption_key {
None => None,
Some(v) => {
let key = BASE64_STANDARD.decode(v).map_err(|_| {
Error::new(
ErrorKind::ConfigInvalid,
"encryption_key must be valid Base64",
)
.with_operation("Builder::build")
.with_context("service", AZBLOB_SCHEME)
.with_context("key", "encryption_key")
})?;
if key.len() != 32 {
return Err(Error::new(
ErrorKind::ConfigInvalid,
"encryption_key must decode to exactly 32 bytes",
)
.with_operation("Builder::build")
.with_context("service", AZBLOB_SCHEME)
.with_context("key", "encryption_key"));
}
Some(build_header_value(v).map_err(|err| {
err.with_context("key", "server_side_encryption_customer_key")
})?),
};
})?)
}
};

let encryption_key_sha256 = match &self.config.encryption_key_sha256 {
None => None,
Expand Down Expand Up @@ -663,3 +686,66 @@ impl Service for AzblobBackend {
)))
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn invalid_encoded_customer_keys_are_rejected() {
for key in [
String::new(),
"not-base64!".to_string(),
BASE64_STANDARD.encode([42; 16]),
BASE64_STANDARD.encode([42; 31]),
BASE64_STANDARD.encode([42; 33]),
BASE64_STANDARD.encode([42; 64]),
] {
let config = AzblobConfig {
container: "test".to_string(),
endpoint: Some("https://test.blob.core.windows.net".to_string()),
encryption_key: Some(key.clone()),
..Default::default()
};
let err = Operator::from_config(config).expect_err("invalid key must fail to build");
assert_eq!(err.kind(), ErrorKind::ConfigInvalid);
let message = format!("{err:?}");
assert!(message.contains("encryption_key"));
if !key.is_empty() {
assert!(!message.contains(&key));
}
}
}

#[test]
fn invalid_raw_customer_keys_are_rejected() {
for len in [0, 16, 31, 33, 64] {
let builder = AzblobBuilder::default()
.container("test")
.endpoint("https://test.blob.core.windows.net")
.server_side_encryption_with_customer_key(&vec![42; len]);
let err = Operator::new(builder).expect_err("invalid key must fail to build");
assert_eq!(err.kind(), ErrorKind::ConfigInvalid);
}
}

#[test]
fn valid_customer_keys_build() {
let key = [42; 32];
let builder = AzblobBuilder::default()
.container("test")
.endpoint("https://test.blob.core.windows.net")
.server_side_encryption_with_customer_key(&key);
Operator::new(builder).expect("32-byte raw key must build");

let config = AzblobConfig {
container: "test".to_string(),
endpoint: Some("https://test.blob.core.windows.net".to_string()),
encryption_key: Some(BASE64_STANDARD.encode(key)),
encryption_key_sha256: Some(BASE64_STANDARD.encode(Sha256::digest(key))),
encryption_algorithm: Some("AES256".to_string()),
..Default::default()
};
Operator::from_config(config).expect("Base64-encoded 32-byte key must build");
}
}
5 changes: 4 additions & 1 deletion core/services/azblob/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@ pub struct AzblobConfig {
)]
pub account_key: Option<String>,

/// The encryption key of Azblob service backend.
/// The Base64-encoded AES-256 customer-provided encryption key.
///
/// The key must decode to exactly 32 bytes. Building an operator returns
/// `ConfigInvalid` for invalid Base64 or a different decoded length.
pub encryption_key: Option<String>,

/// The encryption key sha256 of Azblob service backend.
Expand Down
Loading
Loading