Skip to content
This repository was archived by the owner on Jul 19, 2024. It is now read-only.

Commit 8bc11d5

Browse files
authored
Merge pull request #117 from jofriedm-msft/master
Azure Storage Client Library 4.4.0 Release
2 parents f9bd963 + 388a520 commit 8bc11d5

File tree

14 files changed

+267
-53
lines changed

14 files changed

+267
-53
lines changed

ChangeLog.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
2016.08.30 Version 4.4.0
2+
* Fixed a bug in client-side encryption for tables that was preventing the Java client from decrypting entities encrypted with the .NET client, and vice versa.
3+
14
2016.07.06 Version 4.3.0
25
* Added support for server-side encryption.
36
* Added support for getBlobReferenceFromServer methods on CloudBlobContainer to support retrieving a blob without knowing its type.

microsoft-azure-storage-samples/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
<dependency>
2727
<groupId>com.microsoft.azure</groupId>
2828
<artifactId>azure-storage</artifactId>
29-
<version>4.3.0</version>
29+
<version>4.4.0</version>
3030
</dependency>
3131
<dependency>
3232
<groupId>com.microsoft.azure</groupId>

microsoft-azure-storage-test/src/com/microsoft/azure/storage/GenericTests.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
import java.util.HashMap;
3131
import java.util.UUID;
3232

33+
import org.junit.After;
34+
import org.junit.Before;
3335
import org.junit.Test;
3436
import org.junit.experimental.categories.Category;
3537

@@ -53,6 +55,16 @@
5355
@Category({ DevFabricTests.class, DevStoreTests.class, CloudTests.class })
5456
public class GenericTests {
5557

58+
@Before
59+
public void genericTestMethodSetUp() {
60+
OperationContext.setDefaultProxy(Proxy.NO_PROXY);
61+
}
62+
63+
@After
64+
public void genericTestMethodTearDown() {
65+
OperationContext.setDefaultProxy(Proxy.NO_PROXY);
66+
}
67+
5668
/**
5769
* ReadTimeout must always be explicitly set on HttpUrlConnection to avoid a bug in JDK 6. In certain cases this
5870
* bug causes an immediate SocketException to be thrown indicating that the read timed out even if ReadTimeout was

microsoft-azure-storage-test/src/com/microsoft/azure/storage/TestRunners.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ public static class EncryptionTestSuite {
138138

139139
@RunWith(Suite.class)
140140
@SuiteClasses({ CoreTestSuite.class, BlobTestSuite.class, QueueTestSuite.class, TableTestSuite.class,
141-
FileTestSuite.class, AnalyticsTestSuite.class })
141+
FileTestSuite.class, AnalyticsTestSuite.class, EncryptionTestSuite.class })
142142
public static class AllTestSuite {
143143
}
144144

microsoft-azure-storage-test/src/com/microsoft/azure/storage/table/TableEncryptionTests.java

Lines changed: 74 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import com.microsoft.azure.storage.table.TableTestHelper.Class1;
4646
import com.microsoft.azure.storage.table.TableTestHelper.EncryptedClass1;
4747
import com.microsoft.azure.storage.table.TableTestHelper.ComplexEntity;
48+
import com.sun.jersey.core.util.Base64;
4849

4950
public class TableEncryptionTests {
5051
CloudTable table = null;
@@ -457,6 +458,18 @@ private void doTableQueryDTEProjectionEncryption(TablePayloadFormat format, Symm
457458
assertTrue(ent.getProperties().get("A").getValueAsString().equals("a")
458459
|| ent.getProperties().get("A").getValueAsString().equals(Constants.EMPTY_STRING));
459460
}
461+
462+
// Test to make sure that we don't specify encryption columns when there aren't any columns specified at all.
463+
query = TableQuery.from(DynamicTableEntity.class);
464+
465+
for (DynamicTableEntity ent : this.table.execute(query, options, null)) {
466+
assertNotNull(ent.getPartitionKey());
467+
assertNotNull(ent.getRowKey());
468+
assertNotNull(ent.getTimestamp());
469+
470+
assertTrue(ent.getProperties().get("A").getValueAsString().equals("a")
471+
|| ent.getProperties().get("A").getValueAsString().equals(Constants.EMPTY_STRING));
472+
}
460473
}
461474

462475
@Test
@@ -1022,8 +1035,7 @@ public void testTableOperationEncryptionWithStrictModeOnMerge() throws InvalidKe
10221035
}
10231036

10241037
@Test
1025-
public void testTableOperationsIgnoreEncryption() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, URISyntaxException, StorageException
1026-
{
1038+
public void testTableOperationsIgnoreEncryption() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, URISyntaxException, StorageException {
10271039
SymmetricKey aesKey = TestHelper.getSymmetricKey();
10281040
TableRequestOptions options = new TableRequestOptions();
10291041
options.setEncryptionPolicy(new TableEncryptionPolicy(aesKey, null));
@@ -1072,6 +1084,65 @@ public void testTableOperationsIgnoreEncryption() throws InvalidKeyException, No
10721084
{
10731085
testTable.deleteIfExists();
10741086
}
1087+
}
1088+
1089+
@Test
1090+
public void testCrossPlatformCompatibility() throws StorageException, URISyntaxException {
1091+
CloudTable testTable = TableTestHelper.getRandomTableReference();
1092+
1093+
try
1094+
{
1095+
testTable.createIfNotExists();
1096+
1097+
// Hard code some sample data, then see if we can decrypt it.
1098+
// This key is used only for test, do not use to encrypt any sensitive data.
1099+
SymmetricKey sampleKEK = new SymmetricKey("key1", Base64.decode("rFz7+tv4hRiWdWUJMFlxl1xxtU/qFUeTriGaxwEcxjU="));
1100+
1101+
// This data here was created using Fiddler to capture the .NET library uploading an encrypted entity, encrypted with the specified KEK and CEK.
1102+
// Note that this data is lacking the library information in the KeyWrappingMetadata.
1103+
DynamicTableEntity dteNetOld = new DynamicTableEntity("pk", "netUp");
1104+
dteNetOld.getProperties().put("sampleProp", new EntityProperty(Base64.decode("27cLSlSFqy9C0xUCr57XAA==")));
1105+
dteNetOld.getProperties().put("sampleProp2", new EntityProperty(Base64.decode("pZR6Ln/DwbwyyOCEezL/hg==")));
1106+
dteNetOld.getProperties().put("sampleProp3", new EntityProperty(Base64.decode("JOix4N8eX/WuCtIvlD2QxQ==")));
1107+
dteNetOld.getProperties().put("_ClientEncryptionMetadata1", new EntityProperty("{\"WrappedContentKey\":{\"KeyId\":\"key1\",\"EncryptedKey\":\"pwSKxpJkwCS2zCaykh0m8e4OApeLuQ4FiahZ9zdwxaLL1HsWqQ4DSw==\",\"Algorithm\":\"A256KW\"},\"EncryptionAgent\":{\"Protocol\":\"1.0\",\"EncryptionAlgorithm\":\"AES_CBC_256\"},\"ContentEncryptionIV\":\"obTAQcYeFQ3IU7Jfcema7Q==\",\"KeyWrappingMetadata\":{}}"));
1108+
dteNetOld.getProperties().put("_ClientEncryptionMetadata2", new EntityProperty(Base64.decode("MWA7LlvXSJnKhf8f7MVhfjWECkxrCyCXGIlYY6ucpr34IVDU7fN6IHvKxV15WiXp")));
1109+
1110+
testTable.execute(TableOperation.insert(dteNetOld));
1111+
1112+
// This data here was created using Fiddler to capture the Java library uploading an encrypted entity, encrypted with the specified KEK and CEK.
1113+
// Note that this data is lacking the KeyWrappingMetadata. It also constructs an IV with PK + RK + column name.
1114+
DynamicTableEntity dteJavaOld = new DynamicTableEntity("pk", "javaUp");
1115+
dteJavaOld.getProperties().put("sampleProp", new EntityProperty(Base64.decode("sa3bCvXq79ImSPveChS+cg==")));
1116+
dteJavaOld.getProperties().put("sampleProp2", new EntityProperty(Base64.decode("KXjuBNn9DesCmMcdVpamJw==")));
1117+
dteJavaOld.getProperties().put("sampleProp3", new EntityProperty(Base64.decode("wykVEni1rV+H6oNjoNml6A==")));
1118+
dteJavaOld.getProperties().put("_ClientEncryptionMetadata1", new EntityProperty("{\"WrappedContentKey\":{\"KeyId\":\"key1\",\"EncryptedKey\":\"2F4rIuDmGPgEmhpvTtE7x6281BetKz80EsgRwGxTjL8rRt7Z7GrOgg==\",\"Algorithm\":\"A256KW\"},\"EncryptionAgent\":{\"Protocol\":\"1.0\",\"EncryptionAlgorithm\":\"AES_CBC_256\"},\"ContentEncryptionIV\":\"8st/uXffG+6DxBhw4D1URw==\"}"));
1119+
dteJavaOld.getProperties().put("_ClientEncryptionMetadata2", new EntityProperty(Base64.decode("WznUoytxkvl9KhZ4mNlqkBvRTUHN/D5IgJmNl7kQBOtFBOSgZZrTfZXKH8GjmvKA")));
1120+
1121+
testTable.execute(TableOperation.insert(dteJavaOld));
1122+
1123+
TableEncryptionPolicy policy = new TableEncryptionPolicy(sampleKEK, null);
1124+
TableRequestOptions options = new TableRequestOptions();
1125+
options.setEncryptionPolicy(policy);
1126+
options.setEncryptionResolver(new EncryptionResolver() {
1127+
public boolean encryptionResolver(String pk, String rk, String key) {
1128+
return true;
1129+
}
1130+
});
1131+
1132+
for (DynamicTableEntity dte : testTable.execute(TableQuery.from(DynamicTableEntity.class), options, null))
1133+
{
1134+
assertTrue("String not properly decoded.", dte.getProperties().get("sampleProp").getValueAsString().equals("sampleValue"));
1135+
assertTrue("String not properly decoded.", dte.getProperties().get("sampleProp2").getValueAsString().equals("sampleValue"));
1136+
assertTrue("String not properly decoded.", dte.getProperties().get("sampleProp3").getValueAsString().equals("sampleValue"));
1137+
assertEquals("Incorrect number or properties", dte.getProperties().size(), 3);
1138+
}
1139+
}
1140+
finally
1141+
{
1142+
if (testTable != null) {
1143+
testTable.deleteIfExists();
1144+
}
1145+
}
10751146
}
10761147

10771148
private ArrayList<String> listAllTables(CloudTableClient tableClient, String prefix, TableRequestOptions options) throws StorageException
@@ -1096,6 +1167,4 @@ private static DynamicTableEntity generateRandomEntity(String pk) {
10961167
ent.setRowKey(UUID.randomUUID().toString());
10971168
return ent;
10981169
}
1099-
}
1100-
1101-
1170+
}

microsoft-azure-storage/src/com/microsoft/azure/storage/Constants.java

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,41 @@ public static class EncryptionConstants
254254
* Additional property name to store the encryption metadata.
255255
*/
256256
public static final String TABLE_ENCRYPTION_PROPERTY_DETAILS = "_ClientEncryptionMetadata2";
257+
258+
/**
259+
* Constant for the key for the encryption mode.
260+
*/
261+
public static final String ENCRYPTION_MODE = "EncryptionMode";
262+
263+
/**
264+
* FullBlob string constant for the encryption mode.
265+
*/
266+
public static final String FULL_BLOB = "FullBlob";
267+
268+
/**
269+
* Constant for the key for the wrapped CEK.
270+
*/
271+
public static final String WRAPPED_CONTENT_KEY = "WrappedContentKey";
272+
273+
/**
274+
* Constant for the key for the encryption agent.
275+
*/
276+
public static final String ENCRYPTION_AGENT = "EncryptionAgent";
277+
278+
/**
279+
* Constant for the key for the IV.
280+
*/
281+
public static final String CONTENT_ENCRYPTION_IV = "ContentEncryptionIV";
282+
283+
/**
284+
* Constant for the key wrapping metadata.
285+
*/
286+
public static final String KEY_WRAPPING_METADATA = "KeyWrappingMetadata";
287+
288+
/**
289+
* Constant for the key for the encryption library in the key wrapping metadata.
290+
*/
291+
public static final String ENCRYPTION_LIBRARY = "EncryptionLibrary";
257292
}
258293

259294
/**
@@ -611,7 +646,7 @@ public static class HeaderConstants {
611646
/**
612647
* Specifies the value to use for UserAgent header.
613648
*/
614-
public static final String USER_AGENT_VERSION = "4.3.0";
649+
public static final String USER_AGENT_VERSION = "4.4.0";
615650

616651
/**
617652
* The default type for content-type and accept

microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobEncryptionData.java

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import com.fasterxml.jackson.core.JsonParser;
2222
import com.fasterxml.jackson.core.JsonProcessingException;
2323
import com.fasterxml.jackson.core.JsonToken;
24+
import com.microsoft.azure.storage.Constants;
2425
import com.microsoft.azure.storage.core.EncryptionAgent;
2526
import com.microsoft.azure.storage.core.EncryptionData;
2627
import com.microsoft.azure.storage.core.JsonUtilities;
@@ -66,7 +67,7 @@ public String serialize() throws IOException {
6667
generator.writeStartObject();
6768

6869
// write the encryption mode
69-
generator.writeStringField("EncryptionMode", "FullBlob");
70+
generator.writeStringField(Constants.EncryptionConstants.ENCRYPTION_MODE, Constants.EncryptionConstants.FULL_BLOB);
7071

7172
// write the encryption data
7273
this.serialize(generator);
@@ -98,18 +99,24 @@ public static BlobEncryptionData deserialize(String inputData)
9899
String name = parser.getCurrentName();
99100
parser.nextToken();
100101

101-
if (name.equals("EncryptionMode")) {
102+
if (name.equals(Constants.EncryptionConstants.ENCRYPTION_MODE)) {
102103
data.setEncryptionMode(parser.getValueAsString());
103104
}
104-
else if (name.equals("WrappedContentKey")) {
105+
else if (name.equals(Constants.EncryptionConstants.WRAPPED_CONTENT_KEY)) {
105106
data.setWrappedContentKey(WrappedContentKey.deserialize(parser));
106107
}
107-
else if (name.equals("EncryptionAgent")) {
108+
else if (name.equals(Constants.EncryptionConstants.ENCRYPTION_AGENT)) {
108109
data.setEncryptionAgent(EncryptionAgent.deserialize(parser));
109110
}
110-
else if (name.equals("ContentEncryptionIV")) {
111+
else if (name.equals(Constants.EncryptionConstants.CONTENT_ENCRYPTION_IV)) {
111112
data.setContentEncryptionIV(parser.getBinaryValue());
112113
}
114+
else if (name.equals(Constants.EncryptionConstants.KEY_WRAPPING_METADATA)) {
115+
data.setKeyWrappingMetadata(deserializeKeyWrappingMetadata(parser));
116+
}
117+
else {
118+
consumeJsonObject(parser);
119+
}
113120
parser.nextToken();
114121
}
115122

microsoft-azure-storage/src/com/microsoft/azure/storage/blob/BlobEncryptionPolicy.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package com.microsoft.azure.storage.blob;
1616

1717
import java.io.OutputStream;
18+
import java.util.HashMap;
1819
import java.util.Map;
1920

2021
import javax.crypto.Cipher;
@@ -288,6 +289,11 @@ Cipher createAndSetEncryptionContext(Map<String, String> metadata, boolean noPad
288289
myAes.init(Cipher.ENCRYPT_MODE, aesKey);
289290

290291
BlobEncryptionData encryptionData = new BlobEncryptionData();
292+
if (encryptionData.getKeyWrappingMetadata() == null) {
293+
encryptionData.setKeyWrappingMetadata(new HashMap<String, String>());
294+
}
295+
296+
encryptionData.getKeyWrappingMetadata().put(Constants.EncryptionConstants.ENCRYPTION_LIBRARY, "Java " + Constants.HeaderConstants.USER_AGENT_VERSION);
291297
encryptionData.setEncryptionAgent(new EncryptionAgent(Constants.EncryptionConstants.ENCRYPTION_PROTOCOL_V1,
292298
EncryptionAlgorithm.AES_CBC_256));
293299

0 commit comments

Comments
 (0)