Skip to content

Commit

Permalink
CSE Encryption V2.1 with Configurable Region Length (Azure#41872)
Browse files Browse the repository at this point in the history
  • Loading branch information
ibrahimrabab authored Sep 17, 2024
1 parent 932389b commit a62a2f0
Show file tree
Hide file tree
Showing 13 changed files with 166 additions and 18 deletions.
3 changes: 2 additions & 1 deletion sdk/storage/azure-storage-blob-cryptography/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
## 12.27.0-beta.2 (Unreleased)

### Features Added
- Added a new `EncryptionVersion.V2_1` that allows encrypted blobs to be uploaded using a configurable authenticated region length.
- Added configuration to allow encrypted blobs to be uploaded using a configurable authenticated region length via
`BlobClientSideEncryptionOptions`. The region length can be configured to range between 16 bytes to 1GB. The region
length can be set via `BlobClientSideEncryptionOptions.setAuthenticatedRegionDataLengthInBytes(long authenticatedRegionDataLength)`.
Note: This change only applies `EncryptionVersion.V2`. Also, only applies to upload operations, this does not directly
Note: This change only applies to `EncryptionVersion.V2_1`. Also, only applies to upload operations, this does not directly
change the authenticated region length used to download and decrypt blobs.
### Breaking Changes

Expand Down
2 changes: 1 addition & 1 deletion sdk/storage/azure-storage-blob-cryptography/assets.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
"AssetsRepo": "Azure/azure-sdk-assets",
"AssetsRepoPrefixPath": "java",
"TagPrefix": "java/storage/azure-storage-blob-cryptography",
"Tag": "java/storage/azure-storage-blob-cryptography_c980727e40"
"Tag": "java/storage/azure-storage-blob-cryptography_e245db55c0"
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ final class CryptographyConstants {

static final String ENCRYPTION_PROTOCOL_V2 = "2.0";

static final String ENCRYPTION_PROTOCOL_V2_1 = "2.1";

static final String AGENT_METADATA_KEY = "EncryptionLibrary";

static final String AES_CBC_PKCS5PADDING = "AES/CBC/PKCS5Padding";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import static com.azure.storage.blob.specialized.cryptography.CryptographyConstants.ENCRYPTION_PROTOCOL_V1;
import static com.azure.storage.blob.specialized.cryptography.CryptographyConstants.ENCRYPTION_PROTOCOL_V2;
import static com.azure.storage.blob.specialized.cryptography.CryptographyConstants.ENCRYPTION_PROTOCOL_V2_1;

abstract class Decryptor {
private static final ClientLogger LOGGER = new ClientLogger(Decryptor.class);
Expand Down Expand Up @@ -97,6 +98,7 @@ static Decryptor getDecryptor(AsyncKeyEncryptionKeyResolver keyResolver,
case ENCRYPTION_PROTOCOL_V1:
return new DecryptorV1(keyResolver, keyWrapper, encryptionData);
case ENCRYPTION_PROTOCOL_V2:
case ENCRYPTION_PROTOCOL_V2_1:
return new DecryptorV2(keyResolver, keyWrapper, encryptionData);
default:
throw LOGGER.logExceptionAsError(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
import static com.azure.storage.blob.specialized.cryptography.CryptographyConstants.AES_GCM_NO_PADDING;
import static com.azure.storage.blob.specialized.cryptography.CryptographyConstants.AES_KEY_SIZE_BITS;
import static com.azure.storage.blob.specialized.cryptography.CryptographyConstants.EMPTY_BUFFER;
import static com.azure.storage.blob.specialized.cryptography.CryptographyConstants.ENCRYPTION_PROTOCOL_V2;
import static com.azure.storage.blob.specialized.cryptography.CryptographyConstants.TAG_LENGTH;

class DecryptorV2 extends Decryptor {
Expand Down Expand Up @@ -97,7 +96,7 @@ protected Mono<byte[]> getKeyEncryptionKey() {
byte[] protocolBytes = new byte[3];
try {
keyStream.read(protocolBytes);
if (ByteBuffer.wrap(ENCRYPTION_PROTOCOL_V2.getBytes(StandardCharsets.UTF_8))
if (ByteBuffer.wrap(encryptionData.getEncryptionAgent().getProtocol().getBytes(StandardCharsets.UTF_8))
.compareTo(ByteBuffer.wrap(protocolBytes)) != 0) {
return Mono.error(LOGGER.logExceptionAsError(
new IllegalStateException("Padded wrapped key did not match protocol version")));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -275,12 +275,23 @@ private HttpPipeline addBlobUserAgentModificationPolicy(HttpPipeline pipeline) {
.build();
}

private String getVersionString(EncryptionVersion encryptionVersion) {
switch (encryptionVersion) {
case V2:
return "2.0";
case V2_1:
return "2.1";
default:
return "1.0";
}
}

private String modifyUserAgentString(String applicationId, Configuration userAgentConfiguration) {
Pattern pattern = Pattern.compile(USER_AGENT_MODIFICATION_REGEX);
String userAgent = UserAgentUtil.toUserAgentString(applicationId, BLOB_CLIENT_NAME, BLOB_CLIENT_VERSION,
userAgentConfiguration);
Matcher matcher = pattern.matcher(userAgent);
String version = encryptionVersion == EncryptionVersion.V2 ? "2.0" : "1.0";
String version = getVersionString(encryptionVersion);
String stringToAppend = "azstorage-clientsideencryption/" + version;
if (matcher.matches() && !userAgent.contains(stringToAppend)) {
String segment1 = matcher.group(1) == null ? "" : matcher.group(1);
Expand Down Expand Up @@ -924,9 +935,13 @@ public EncryptedBlobClientBuilder requiresEncryption(boolean requiresEncryption)
*
* @param clientSideEncryptionOptions The {@link BlobClientSideEncryptionOptions} for the blob.
* @return the updated EncryptedBlobClientBuilder object
* @throws IllegalArgumentException If {@link EncryptionVersion} is not V2_1.
*/
public EncryptedBlobClientBuilder clientSideEncryptionOptions(
BlobClientSideEncryptionOptions clientSideEncryptionOptions) {
if (this.encryptionVersion != EncryptionVersion.V2_1) {
throw LOGGER.logExceptionAsError(new IllegalArgumentException("ClientSideEncryptionOptions can only be set if encryption version is V2_1."));
}
this.clientSideEncryptionOptions = clientSideEncryptionOptions;
return this;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import static com.azure.storage.blob.specialized.cryptography.CryptographyConstants.ENCRYPTION_BLOCK_SIZE;
import static com.azure.storage.blob.specialized.cryptography.CryptographyConstants.ENCRYPTION_PROTOCOL_V1;
import static com.azure.storage.blob.specialized.cryptography.CryptographyConstants.ENCRYPTION_PROTOCOL_V2;
import static com.azure.storage.blob.specialized.cryptography.CryptographyConstants.ENCRYPTION_PROTOCOL_V2_1;
import static com.azure.storage.blob.specialized.cryptography.CryptographyConstants.NONCE_LENGTH;
import static com.azure.storage.blob.specialized.cryptography.CryptographyConstants.TAG_LENGTH;

Expand Down Expand Up @@ -125,6 +126,7 @@ static EncryptedBlobRange getEncryptedBlobRangeFromHeader(String stringRange, En
this.amountPlaintextToSkip = offsetAdjustment;
break;
case ENCRYPTION_PROTOCOL_V2:
case ENCRYPTION_PROTOCOL_V2_1:
// Calculate offsetAdjustment.
// Get the start of the encryption region for the original offset
long authenticatedRegionDataLength = encryptionData.getEncryptedRegionInfo().getDataLength();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import static com.azure.storage.blob.specialized.cryptography.CryptographyConstants.ENCRYPTION_PROTOCOL_V1;
import static com.azure.storage.blob.specialized.cryptography.CryptographyConstants.ENCRYPTION_PROTOCOL_V2;
import static com.azure.storage.blob.specialized.cryptography.CryptographyConstants.ENCRYPTION_PROTOCOL_V2_1;
import static com.azure.storage.blob.specialized.cryptography.EncryptionAlgorithm.AES_CBC_256;
import static com.azure.storage.blob.specialized.cryptography.EncryptionAlgorithm.AES_GCM_256;

Expand Down Expand Up @@ -226,7 +227,8 @@ static EncryptionData getAndValidateEncryptionData(String encryptionDataString,

try (JsonReader jsonReader = JsonProviders.createReader(encryptionDataString)) {
EncryptionData encryptionData = EncryptionData.fromJson(jsonReader);
if (encryptionData.getEncryptionAgent().getProtocol().equals(ENCRYPTION_PROTOCOL_V1)) {
String encryptionProtocol = encryptionData.getEncryptionAgent().getProtocol();
if (encryptionProtocol.equals(ENCRYPTION_PROTOCOL_V1)) {
Objects.requireNonNull(encryptionData.getContentEncryptionIV(),
"contentEncryptionIV in encryptionData cannot be null");
Objects.requireNonNull(encryptionData.getWrappedContentKey().getEncryptedKey(), "encryptedKey in "
Expand All @@ -236,7 +238,7 @@ static EncryptionData getAndValidateEncryptionData(String encryptionDataString,
"Encryption algorithm does not match v1 protocol: "
+ encryptionData.getEncryptionAgent().getAlgorithm()));
}
} else if (encryptionData.getEncryptionAgent().getProtocol().equals(ENCRYPTION_PROTOCOL_V2)) {
} else if (encryptionProtocol.equals(ENCRYPTION_PROTOCOL_V2) || encryptionProtocol.equals(ENCRYPTION_PROTOCOL_V2_1)) {
Objects.requireNonNull(encryptionData.getWrappedContentKey().getEncryptedKey(), "encryptedKey in "
+ "encryptionData.wrappedContentKey cannot be null");
if (!encryptionData.getEncryptionAgent().getAlgorithm().equals(AES_GCM_256)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,12 @@ public enum EncryptionVersion {
* Version 2 of the client side encryption protocol.
* Uses AES/GCM/NoPadding
*/
V2
V2,

/**
* Version 2.1 of the client side encryption protocol. Use this version when configuring {@link BlobClientSideEncryptionOptions}
* authenticatedRegionDataLength for encryption.
* Uses AES/GCM/NoPadding
*/
V2_1
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import java.util.Map;

import static com.azure.storage.blob.specialized.cryptography.CryptographyConstants.ENCRYPTION_MODE;
import static com.azure.storage.blob.specialized.cryptography.CryptographyConstants.ENCRYPTION_PROTOCOL_V2;
import static com.azure.storage.blob.specialized.cryptography.CryptographyConstants.ENCRYPTION_PROTOCOL_V2_1;

abstract class Encryptor {
private static final ClientLogger LOGGER = new ClientLogger(Encryptor.class);
Expand Down Expand Up @@ -40,7 +42,9 @@ static Encryptor getEncryptor(EncryptionVersion version, SecretKey aesKey, BlobC
case V1:
return new EncryptorV1(aesKey);
case V2:
return new EncryptorV2(aesKey, encryptionOptions);
return new EncryptorV2(aesKey, encryptionOptions, ENCRYPTION_PROTOCOL_V2);
case V2_1:
return new EncryptorV2(aesKey, encryptionOptions, ENCRYPTION_PROTOCOL_V2_1);
default:
throw LOGGER.logExceptionAsError(new IllegalArgumentException("Invalid encryption version: "
+ version));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,18 @@
import static com.azure.storage.blob.specialized.cryptography.CryptographyConstants.AES_GCM_NO_PADDING;
import static com.azure.storage.blob.specialized.cryptography.CryptographyConstants.AES_KEY_SIZE_BITS;
import static com.azure.storage.blob.specialized.cryptography.CryptographyConstants.EMPTY_BUFFER;
import static com.azure.storage.blob.specialized.cryptography.CryptographyConstants.ENCRYPTION_PROTOCOL_V2;
import static com.azure.storage.blob.specialized.cryptography.CryptographyConstants.NONCE_LENGTH;
import static com.azure.storage.blob.specialized.cryptography.CryptographyConstants.TAG_LENGTH;

class EncryptorV2 extends Encryptor {
private static final ClientLogger LOGGER = new ClientLogger(EncryptorV2.class);
private final BlobClientSideEncryptionOptions encryptionOptions;
private final String encryptionProtocol;

protected EncryptorV2(SecretKey aesKey, BlobClientSideEncryptionOptions encryptionOptions) {
protected EncryptorV2(SecretKey aesKey, BlobClientSideEncryptionOptions encryptionOptions, String encryptionProtocol) {
super(aesKey);
this.encryptionOptions = encryptionOptions;
this.encryptionProtocol = encryptionProtocol;
}

@Override
Expand All @@ -46,7 +47,7 @@ byte[] getKeyToWrap() {
*/
ByteArrayOutputStream keyStream = new ByteArrayOutputStream((AES_KEY_SIZE_BITS / 8) + 8);
// This will always be three bytes
keyStream.write(ENCRYPTION_PROTOCOL_V2.getBytes(StandardCharsets.UTF_8));
keyStream.write(encryptionProtocol.getBytes(StandardCharsets.UTF_8));
// Key wrapping requires 8-byte alignment. Pad will 0s
for (int i = 0; i < 5; i++) {
keyStream.write(0);
Expand All @@ -61,7 +62,7 @@ byte[] getKeyToWrap() {
@Override
protected EncryptionData buildEncryptionData(Map<String, String> keyWrappingMetadata, WrappedKey wrappedKey) {
return super.buildEncryptionData(keyWrappingMetadata, wrappedKey)
.setEncryptionAgent(new EncryptionAgent(ENCRYPTION_PROTOCOL_V2,
.setEncryptionAgent(new EncryptionAgent(encryptionProtocol,
EncryptionAlgorithm.AES_GCM_256))
.setEncryptedRegionInfo(new EncryptedRegionInfo(encryptionOptions.getAuthenticatedRegionDataLengthInBytes(), NONCE_LENGTH));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ public void illegalRegionLength(long regionLength) {
@ParameterizedTest
@ValueSource(longs = { 16, 4 * Constants.KB, 4 * Constants.MB, Constants.GB })
public void encryptedRegionLength(long regionLength) {
EncryptedBlobClient encryptedBlobClient = new EncryptedBlobClientBuilder(EncryptionVersion.V2)
EncryptedBlobClient encryptedBlobClient = new EncryptedBlobClientBuilder(EncryptionVersion.V2_1)
.blobName("foo")
.containerName("container")
.key(new FakeKey("keyId", randomData), "keyWrapAlgorithm")
Expand All @@ -311,6 +311,25 @@ public void encryptedRegionLengthDefault() {
assertEquals(4 * Constants.MB, encryptedBlobClient.getClientSideEncryptionOptions().getAuthenticatedRegionDataLengthInBytes());
}

@ParameterizedTest
@MethodSource("encryptedRegionLengthWithIllegalVersionSupplier")
public void encryptedRegionLengthWithIllegalVersion(EncryptionVersion version) {
assertThrows(IllegalArgumentException.class, () -> new EncryptedBlobClientBuilder(version)
.blobName("foo")
.containerName("container")
.key(new FakeKey("keyId", randomData), "keyWrapAlgorithm")
.clientSideEncryptionOptions(new BlobClientSideEncryptionOptions()
.setAuthenticatedRegionDataLengthInBytes(Constants.KB))
.buildEncryptedBlobClient());
}

private static Stream<Arguments> encryptedRegionLengthWithIllegalVersionSupplier() {
return Stream.of(
Arguments.of(EncryptionVersion.V1),
Arguments.of(EncryptionVersion.V2)
);
}


private static void sendAndValidateUserAgentHeader(HttpPipeline pipeline, String url) {
boolean foundPolicy = false;
Expand Down
Loading

0 comments on commit a62a2f0

Please sign in to comment.