diff --git a/microsoft-azure-api/pom.xml b/microsoft-azure-api/pom.xml
index a931921be3c4..9b8f7eb43446 100644
--- a/microsoft-azure-api/pom.xml
+++ b/microsoft-azure-api/pom.xml
@@ -104,6 +104,12 @@
commons-lang3
3.1
+
+ org.bouncycastle
+ bcprov-jdk16
+ 1.46
+ test
+
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/entityoperations/EntityRestProxy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/entityoperations/EntityRestProxy.java
index c99273aaabf3..00366fac6f60 100644
--- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/entityoperations/EntityRestProxy.java
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/entityoperations/EntityRestProxy.java
@@ -172,6 +172,7 @@ public T action(EntityTypeActionOperation entityTypeActionOperation) thro
.queryParams(entityTypeActionOperation.getQueryParameters())
.accept(entityTypeActionOperation.getAcceptType()).accept(MediaType.APPLICATION_XML_TYPE)
.entity(entityTypeActionOperation.getRequestContents(), MediaType.APPLICATION_XML_TYPE);
+
ClientResponse clientResponse = webResource.method(entityTypeActionOperation.getVerb(), ClientResponse.class);
return entityTypeActionOperation.processTypeResponse(clientResponse);
}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/implementation/ResourceLocationManager.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/implementation/ResourceLocationManager.java
index e1d4807f51a2..50c8b54a8f55 100644
--- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/implementation/ResourceLocationManager.java
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/implementation/ResourceLocationManager.java
@@ -36,7 +36,7 @@ public URI getBaseURI() {
public URI getRedirectedURI(URI originalURI) {
UriBuilder uriBuilder = UriBuilder.fromUri(baseURI).path(originalURI.getPath());
- String queryString = originalURI.getQuery();
+ String queryString = originalURI.getRawQuery();
if (queryString != null && !queryString.isEmpty()) {
uriBuilder.replaceQuery(queryString);
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/implementation/content/ObjectFactory.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/implementation/content/ObjectFactory.java
index 3769e3a1aa02..3aa8d5bfbfe9 100644
--- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/implementation/content/ObjectFactory.java
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/implementation/content/ObjectFactory.java
@@ -105,11 +105,20 @@ public ContentKeyRestType createContentKeyRestType() {
}
/**
- * Create an instance of {@link AssetFileType}
+ * Create an instance of {@link AssetFileType}.
*
* @return a new AssetFileType instance.
*/
public AssetFileType createAssetFileType() {
return new AssetFileType();
}
+
+ /**
+ * Creates an instance of (@link RebindContentKeyType).
+ *
+ * @return the rebind content key type instance.
+ */
+ public RebindContentKeyType createRebindContentKeyType() {
+ return new RebindContentKeyType();
+ }
}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/implementation/content/RebindContentKeyType.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/implementation/content/RebindContentKeyType.java
new file mode 100644
index 000000000000..9f84670b1068
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/implementation/content/RebindContentKeyType.java
@@ -0,0 +1,53 @@
+/**
+ * Copyright Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.microsoft.windowsazure.services.media.implementation.content;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlValue;
+
+/**
+ * The Class RebindContentKeyType.
+ */
+@XmlAccessorType(XmlAccessType.FIELD)
+@XmlRootElement(name = "RebindContentKey", namespace = Constants.ODATA_DATA_NS)
+public class RebindContentKeyType implements MediaServiceDTO {
+
+ /** The rebind content key. */
+ @XmlValue
+ String rebindContentKey;
+
+ /**
+ * Gets the content key.
+ *
+ * @return the content key
+ */
+ public String getContentKey() {
+ return rebindContentKey;
+ }
+
+ /**
+ * Sets the content key.
+ *
+ * @param rebindContentKey
+ * the new content key
+ */
+ public void setContentKey(String rebindContentKey) {
+ this.rebindContentKey = rebindContentKey;
+ }
+
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/models/ContentKey.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/models/ContentKey.java
index fea8d2ba62e2..6f579dd608a5 100644
--- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/models/ContentKey.java
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/media/models/ContentKey.java
@@ -15,14 +15,30 @@
package com.microsoft.windowsazure.services.media.models;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.security.InvalidParameterException;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBElement;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Unmarshaller;
+import javax.xml.transform.stream.StreamSource;
+
+import com.microsoft.windowsazure.services.core.utils.pipeline.PipelineHelpers;
import com.microsoft.windowsazure.services.media.entityoperations.DefaultDeleteOperation;
+import com.microsoft.windowsazure.services.media.entityoperations.DefaultEntityTypeActionOperation;
import com.microsoft.windowsazure.services.media.entityoperations.DefaultGetOperation;
import com.microsoft.windowsazure.services.media.entityoperations.DefaultListOperation;
import com.microsoft.windowsazure.services.media.entityoperations.EntityCreateOperation;
import com.microsoft.windowsazure.services.media.entityoperations.EntityDeleteOperation;
import com.microsoft.windowsazure.services.media.entityoperations.EntityGetOperation;
import com.microsoft.windowsazure.services.media.entityoperations.EntityOperationSingleResultBase;
+import com.microsoft.windowsazure.services.media.entityoperations.EntityTypeActionOperation;
import com.microsoft.windowsazure.services.media.implementation.content.ContentKeyRestType;
+import com.microsoft.windowsazure.services.media.implementation.content.RebindContentKeyType;
+import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.GenericType;
/**
@@ -213,4 +229,82 @@ public static EntityDeleteOperation delete(String contentKeyId) {
return new DefaultDeleteOperation(ENTITY_SET, contentKeyId);
}
+ /**
+ * Rebind content key with specified content key and X509 Certificate.
+ *
+ * @param contentKeyId
+ * the content key id
+ * @param x509Certificate
+ * the x509 certificate
+ * @return the entity action operation
+ */
+ public static EntityTypeActionOperation rebind(String contentKeyId, String x509Certificate) {
+ return new RebindContentKeyActionOperation(contentKeyId, x509Certificate);
+ }
+
+ /**
+ * Rebind content key with specified content key Id.
+ *
+ * @param contentKeyId
+ * the content key id
+ * @return the entity action operation
+ */
+ public static EntityTypeActionOperation rebind(String contentKeyId) {
+ return rebind(contentKeyId, "");
+ }
+
+ private static class RebindContentKeyActionOperation extends DefaultEntityTypeActionOperation {
+ private final JAXBContext jaxbContext;
+
+ private final Unmarshaller unmarshaller;
+
+ public RebindContentKeyActionOperation(String contentKeyId, String x509Certificate) {
+ super("RebindContentKey");
+
+ String escapedContentKeyId;
+ try {
+ escapedContentKeyId = URLEncoder.encode(contentKeyId, "UTF-8");
+ }
+ catch (UnsupportedEncodingException e) {
+ throw new InvalidParameterException("UTF-8 encoding is not supported.");
+ }
+ this.addQueryParameter("x509Certificate", "'" + x509Certificate + "'");
+ this.addQueryParameter("id", "'" + escapedContentKeyId + "'");
+
+ try {
+ jaxbContext = JAXBContext.newInstance(RebindContentKeyType.class);
+ }
+ catch (JAXBException e) {
+ throw new RuntimeException(e);
+ }
+
+ try {
+ unmarshaller = jaxbContext.createUnmarshaller();
+ }
+ catch (JAXBException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public String processTypeResponse(ClientResponse clientResponse) {
+ PipelineHelpers.ThrowIfNotSuccess(clientResponse);
+ RebindContentKeyType rebindContentKeyType = parseResponse(clientResponse);
+ return rebindContentKeyType.getContentKey();
+ }
+
+ private RebindContentKeyType parseResponse(ClientResponse clientResponse) {
+ InputStream inputStream = clientResponse.getEntityInputStream();
+ JAXBElement rebindContentKeyTypeJaxbElement;
+ try {
+ rebindContentKeyTypeJaxbElement = unmarshaller.unmarshal(new StreamSource(inputStream),
+ RebindContentKeyType.class);
+ }
+ catch (JAXBException e) {
+ throw new RuntimeException(e);
+ }
+ return rebindContentKeyTypeJaxbElement.getValue();
+ }
+
+ }
}
diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/media/ContentKeyIntegrationTest.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/media/ContentKeyIntegrationTest.java
index d7c6f40b96d7..f2fb697ad98c 100644
--- a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/media/ContentKeyIntegrationTest.java
+++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/media/ContentKeyIntegrationTest.java
@@ -17,6 +17,11 @@
import static org.junit.Assert.*;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.security.PrivateKey;
+import java.security.Security;
+import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@@ -24,6 +29,7 @@
import org.junit.Test;
import com.microsoft.windowsazure.services.core.ServiceException;
+import com.microsoft.windowsazure.services.core.storage.utils.Base64;
import com.microsoft.windowsazure.services.media.models.ContentKey;
import com.microsoft.windowsazure.services.media.models.ContentKeyInfo;
import com.microsoft.windowsazure.services.media.models.ContentKeyType;
@@ -36,6 +42,57 @@ public class ContentKeyIntegrationTest extends IntegrationTestBase {
private final ContentKeyType testContentKeyType = ContentKeyType.CommonEncryption;
private final String testEncryptedContentKey = "ThisIsEncryptedContentKey";
+ private void assertByteArrayEquals(byte[] source, byte[] target) {
+ assertEquals(source.length, target.length);
+ for (int i = 0; i < source.length; i++) {
+ assertEquals(source[i], target[i]);
+ }
+ }
+
+ private ContentKeyInfo createTestContentKey(String contentKeyNameSuffix) throws ServiceException {
+ String testContentKeyId = createRandomContentKeyId();
+ String testContentKeyName = testContentKeyPrefix + contentKeyNameSuffix;
+
+ ContentKeyInfo contentKeyInfo = service.create(ContentKey.create(testContentKeyId, testContentKeyType,
+ testEncryptedContentKey).setName(testContentKeyName));
+ return contentKeyInfo;
+ }
+
+ private ContentKeyInfo createValidTestContentKeyWithAesKey(String contentKeyNameSuffix, byte[] aesKey)
+ throws Exception {
+ String testContnetKeyName = testContentKeyPrefix + contentKeyNameSuffix;
+ String protectionKeyId = service.action(ProtectionKey.getProtectionKeyId(ContentKeyType.StorageEncryption));
+ String protectionKey = service.action(ProtectionKey.getProtectionKey(protectionKeyId));
+
+ String testContentKeyIdUuid = UUID.randomUUID().toString();
+ String testContentKeyId = String.format("nb:kid:UUID:%s", testContentKeyIdUuid);
+
+ byte[] encryptedContentKey = EncryptionHelper.encryptSymmetricKey(protectionKey, aesKey);
+ String encryptedContentKeyString = Base64.encode(encryptedContentKey);
+ String checksum = EncryptionHelper.calculateContentKeyChecksum(testContentKeyIdUuid, aesKey);
+
+ ContentKeyInfo contentKeyInfo = service.create(ContentKey
+ .create(testContentKeyId, ContentKeyType.StorageEncryption, encryptedContentKeyString)
+ .setChecksum(checksum).setProtectionKeyId(protectionKeyId).setName(testContnetKeyName));
+
+ return contentKeyInfo;
+ }
+
+ private ContentKeyInfo createValidTestContentKey(String contentKeyNameSuffix) throws Exception {
+ byte[] aesKey = createTestAesKey();
+ return createValidTestContentKeyWithAesKey(contentKeyNameSuffix, aesKey);
+ }
+
+ private byte[] createTestAesKey() {
+ byte[] aesKey = new byte[32];
+ int i;
+ for (i = 0; i < 32; i++) {
+ aesKey[i] = 1;
+ }
+
+ return aesKey;
+ }
+
private String createRandomContentKeyId() {
UUID uuid = UUID.randomUUID();
String randomContentKey = String.format("nb:kid:UUID:%s", uuid);
@@ -65,7 +122,7 @@ private void verifyContentKeyProperties(String message, String id, ContentKeyTyp
public void canCreateContentKey() throws Exception {
// Arrange
String testCanCreateContentKeyId = createRandomContentKeyId();
- String testCanCreateContentKeyName = "testCanCreateContentKey";
+ String testCanCreateContentKeyName = testContentKeyPrefix + "testCanCreateContentKey";
String protectionKeyId = service.action(ProtectionKey.getProtectionKeyId(testContentKeyType));
// Act
@@ -195,4 +252,53 @@ public void cannotDeleteContentKeyByNonexistId() throws Exception {
service.delete(ContentKey.delete(validButNonexistContentKeyId));
}
+ @Test
+ public void rebindContentKeyNoX509CertificateSuccess() throws Exception {
+
+ ContentKeyInfo contentKeyInfo = createValidTestContentKey("rebindContentKeyNoX509Success");
+
+ String contentKey = service.action(ContentKey.rebind(contentKeyInfo.getId()));
+ assertNotNull(contentKey);
+
+ }
+
+ @Test
+ public void rebindInvalidContentKeyNoX509CertificateFail() throws ServiceException {
+ expectedException.expect(ServiceException.class);
+ expectedException.expect(new ServiceExceptionMatcher(400));
+ ContentKeyInfo contentKeyInfo = createTestContentKey("rebindInvalidContentKeyNoX509Fail");
+
+ service.action(ContentKey.rebind(contentKeyInfo.getId()));
+
+ }
+
+ @Test
+ public void rebindContentKeyWithX509CertficateSuccess() throws Exception {
+ Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
+ byte[] aesKey = createTestAesKey();
+ ContentKeyInfo contentKeyInfo = createValidTestContentKeyWithAesKey("rebindContentKeyWithX509Success", aesKey);
+ URL serverCertificateUri = getClass().getResource("/certificate/server.crt");
+ X509Certificate x509Certificate = EncryptionHelper.loadX509Certificate(serverCertificateUri.getFile());
+ URL serverPrivateKey = getClass().getResource("/certificate/server.der");
+ PrivateKey privateKey = EncryptionHelper.getPrivateKey(serverPrivateKey.getFile());
+
+ String rebindedContentKey = service.action(ContentKey.rebind(contentKeyInfo.getId(),
+ URLEncoder.encode(Base64.encode(x509Certificate.getEncoded()), "UTF-8")));
+ byte[] decryptedAesKey = EncryptionHelper.decryptSymmetricKey(rebindedContentKey, privateKey);
+ assertByteArrayEquals(aesKey, decryptedAesKey);
+ }
+
+ @Test
+ public void rebindContentKeyWithIncorrectContentKeyIdFailed() throws ServiceException {
+ expectedException.expect(ServiceException.class);
+ service.action(ContentKey.rebind("invalidContentKeyId"));
+ }
+
+ @Test
+ public void rebindContentKeyWithIncorrectX509CertificateFailed() throws ServiceException {
+ expectedException.expect(ServiceException.class);
+ ContentKeyInfo contentKeyInfo = createTestContentKey("rebindContentKeyWithIncorrectX509CertficateFailed");
+
+ service.action(ContentKey.rebind(contentKeyInfo.getId(), "InvalidX509Certificate"));
+ }
}
diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/media/EncryptionHelper.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/media/EncryptionHelper.java
index 5954e6a8cdc4..823163c9bbf0 100644
--- a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/media/EncryptionHelper.java
+++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/media/EncryptionHelper.java
@@ -16,11 +16,18 @@
package com.microsoft.windowsazure.services.media;
import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.File;
+import java.io.FileInputStream;
import java.io.InputStream;
import java.security.Key;
+import java.security.KeyFactory;
+import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.PKCS8EncodedKeySpec;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
@@ -43,11 +50,19 @@ public static boolean canUseStrongCrypto() {
}
public static byte[] encryptSymmetricKey(String protectionKey, byte[] inputData) throws Exception {
- Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
- CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
byte[] protectionKeyBytes = Base64.decode(protectionKey);
- ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(protectionKeyBytes);
+ return encryptSymmetricKey(protectionKeyBytes, inputData);
+ }
+
+ public static byte[] encryptSymmetricKey(byte[] protectionKey, byte[] inputData) throws Exception {
+ CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
+ ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(protectionKey);
Certificate certificate = certificateFactory.generateCertificate(byteArrayInputStream);
+ return encryptSymmetricKey(certificate, inputData);
+ }
+
+ public static byte[] encryptSymmetricKey(Certificate certificate, byte[] inputData) throws Exception {
+ Cipher cipher = Cipher.getInstance("RSA/None/OAEPWithSHA1AndMGF1Padding", "BC");
Key publicKey = certificate.getPublicKey();
SecureRandom secureRandom = new SecureRandom();
cipher.init(Cipher.ENCRYPT_MODE, publicKey, secureRandom);
@@ -74,4 +89,57 @@ public static InputStream encryptFile(InputStream inputStream, byte[] key, byte[
CipherInputStream cipherInputStream = new CipherInputStream(inputStream, cipher);
return cipherInputStream;
}
+
+ public static byte[] decryptSymmetricKey(String encryptedContent, PrivateKey privateKey) throws Exception {
+ byte[] encryptedContentByteArray = Base64.decode(encryptedContent);
+ return decryptSymmetricKey(encryptedContentByteArray, privateKey);
+ }
+
+ public static byte[] decryptSymmetricKey(byte[] encryptedContent, PrivateKey privateKey) throws Exception {
+ if (encryptedContent == null) {
+ throw new IllegalArgumentException("The encrypted content cannot be null.");
+ }
+
+ if (encryptedContent.length == 0) {
+ throw new IllegalArgumentException("The encrypted content cannot be empty.");
+ }
+
+ if (privateKey == null) {
+ throw new IllegalArgumentException("The private key cannot be null.");
+ }
+
+ Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA1AndMGF1Padding", "BC");
+ cipher.init(Cipher.DECRYPT_MODE, privateKey);
+ byte[] decrypted = cipher.doFinal(encryptedContent);
+ return decrypted;
+ }
+
+ public static X509Certificate loadX509Certificate(String certificateFileName) throws Exception {
+ if ((certificateFileName == null) || certificateFileName.isEmpty()) {
+ throw new IllegalArgumentException("certificate file name cannot be null or empty.");
+ }
+
+ CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
+ FileInputStream certificateInputStream = new FileInputStream(certificateFileName);
+ X509Certificate x509Certificate = (X509Certificate) certificateFactory
+ .generateCertificate(certificateInputStream);
+ return x509Certificate;
+ }
+
+ public static PrivateKey getPrivateKey(String certificateFileName) throws Exception {
+ if ((certificateFileName == null) || certificateFileName.isEmpty()) {
+ throw new IllegalArgumentException("certificate file name cannot be null or empty.");
+ }
+
+ File file = new File(certificateFileName);
+ FileInputStream fis = new FileInputStream(file);
+ DataInputStream dataInputStream = new DataInputStream(fis);
+ byte[] keyBytes = new byte[(int) file.length()];
+ dataInputStream.readFully(keyBytes);
+ dataInputStream.close();
+
+ PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(keyBytes);
+ KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+ return keyFactory.generatePrivate(pkcs8EncodedKeySpec);
+ }
}
diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/media/EncryptionIntegrationTest.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/media/EncryptionIntegrationTest.java
index 2b2e78d125e8..a444fe750d89 100644
--- a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/media/EncryptionIntegrationTest.java
+++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/media/EncryptionIntegrationTest.java
@@ -21,13 +21,23 @@
import java.io.InputStream;
import java.math.BigInteger;
import java.net.URL;
+import java.security.Key;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.cert.X509Certificate;
import java.util.EnumSet;
import java.util.List;
import java.util.Random;
import java.util.UUID;
+import javax.crypto.Cipher;
+
import junit.framework.Assert;
+import org.junit.BeforeClass;
import org.junit.Test;
import com.microsoft.windowsazure.services.core.ServiceException;
@@ -59,7 +69,19 @@
import com.microsoft.windowsazure.services.media.models.TaskState;
public class EncryptionIntegrationTest extends IntegrationTestBase {
- private final String strorageDecryptionProcessor = "Storage Decryption";
+ private final String storageDecryptionProcessor = "Storage Decryption";
+
+ private void assertByteArrayEquals(byte[] source, byte[] target) {
+ assertEquals(source.length, target.length);
+ for (int i = 0; i < source.length; i++) {
+ assertEquals(source[i], target[i]);
+ }
+ }
+
+ @BeforeClass
+ public static void Setup() {
+ Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
+ }
@Test
public void uploadAesProtectedAssetAndDownloadSuccess() throws Exception {
@@ -89,7 +111,7 @@ public void uploadAesProtectedAssetAndDownloadSuccess() throws Exception {
WritableBlobContainerContract blobWriter = getBlobWriter(assetInfo.getId(), durationInMinutes);
// gets the public key for storage encryption.
- String contentKeyId = makeContentKeyId(aesKey);
+ String contentKeyId = createContentKey(aesKey);
// link the content key with the asset.
service.action(Asset.linkContentKey(assetInfo.getId(), contentKeyId));
@@ -124,9 +146,68 @@ public void uploadAesProtectedAssetAndDownloadSuccess() throws Exception {
assertStreamsEqual(expected, actual);
}
+ @Test
+ public void encryptedContentCanBeDecrypted() throws Exception {
+ byte[] aesKey = new byte[32];
+ for (int i = 0; i < 32; i++) {
+ aesKey[i] = 1;
+ }
+ URL serverCertificateUri = getClass().getResource("/certificate/server.crt");
+ X509Certificate x509Certificate = EncryptionHelper.loadX509Certificate(serverCertificateUri.getFile());
+ URL serverPrivateKey = getClass().getResource("/certificate/server.der");
+ PrivateKey privateKey = EncryptionHelper.getPrivateKey(serverPrivateKey.getFile());
+ byte[] encryptedAesKey = EncryptionHelper.encryptSymmetricKey(x509Certificate, aesKey);
+ byte[] decryptedAesKey = EncryptionHelper.decryptSymmetricKey(encryptedAesKey, privateKey);
+
+ assertByteArrayEquals(aesKey, decryptedAesKey);
+ }
+
+ @Test
+ public void testEncryptedContentCanBeDecryptedUsingPreGeneratedKeyPair() throws Exception {
+ byte[] input = "abc".getBytes();
+ Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA1AndMGF1Padding", "BC");
+ SecureRandom random = new SecureRandom();
+ URL serverCertificateUri = getClass().getResource("/certificate/server.crt");
+ X509Certificate x509Certificate = EncryptionHelper.loadX509Certificate(serverCertificateUri.getFile());
+ URL serverPrivateKey = getClass().getResource("/certificate/server.der");
+ PrivateKey privateKey = EncryptionHelper.getPrivateKey(serverPrivateKey.getFile());
+ Key pubKey = x509Certificate.getPublicKey();
+ cipher.init(Cipher.ENCRYPT_MODE, pubKey, random);
+ byte[] cipherText = cipher.doFinal(input);
+ cipher.init(Cipher.DECRYPT_MODE, privateKey);
+
+ //Act
+ byte[] plainText = cipher.doFinal(cipherText);
+
+ // Assert
+ assertByteArrayEquals(input, plainText);
+ }
+
+ @Test
+ public void testEncryptionDecryptionFunctionUsingGeneratedKeyPair() throws Exception {
+ // Arrange
+ byte[] input = "abc".getBytes();
+ Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA1AndMGF1Padding", "BC");
+ SecureRandom random = new SecureRandom();
+ KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", "BC");
+ generator.initialize(386, random);
+ KeyPair pair = generator.generateKeyPair();
+ Key pubKey = pair.getPublic();
+ Key privKey = pair.getPrivate();
+ cipher.init(Cipher.ENCRYPT_MODE, pubKey, random);
+ byte[] cipherText = cipher.doFinal(input);
+ cipher.init(Cipher.DECRYPT_MODE, privKey);
+
+ //Act
+ byte[] plainText = cipher.doFinal(cipherText);
+
+ // Assert
+ assertByteArrayEquals(input, plainText);
+ }
+
private JobInfo decodeAsset(String name, String inputAssetId) throws ServiceException, InterruptedException {
MediaProcessorInfo mediaProcessorInfo = service.list(
- MediaProcessor.list().set("$filter", "Name eq '" + strorageDecryptionProcessor + "'")).get(0);
+ MediaProcessor.list().set("$filter", "Name eq '" + storageDecryptionProcessor + "'")).get(0);
String taskBody = ""
+ "JobInputAsset(0)JobOutputAsset(0)";
@@ -143,7 +224,7 @@ private JobInfo decodeAsset(String name, String inputAssetId) throws ServiceExce
return currentJobInfo;
}
- private String makeContentKeyId(byte[] aesKey) throws ServiceException, Exception {
+ private String createContentKey(byte[] aesKey) throws ServiceException, Exception {
String protectionKeyId = service.action(ProtectionKey.getProtectionKeyId(ContentKeyType.StorageEncryption));
String protectionKey = service.action(ProtectionKey.getProtectionKey(protectionKeyId));
diff --git a/microsoft-azure-api/src/test/resources/certificate/server.crt b/microsoft-azure-api/src/test/resources/certificate/server.crt
new file mode 100644
index 000000000000..45100ad45f50
--- /dev/null
+++ b/microsoft-azure-api/src/test/resources/certificate/server.crt
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICqTCCAhICCQDm00hjjGf/ITANBgkqhkiG9w0BAQUFADCBlzELMAkGA1UEBhMC
+VVMxCzAJBgNVBAgTAldBMRAwDgYDVQQHEwdSZWRtb25kMRMwEQYDVQQKEwpNaWNy
+b3NvZnQgMRYwFAYDVQQLEw1XaW5kb3dzIEF6dXJlMRUwEwYDVQQDEwxBbGJlcnQg
+Q2hlbmcxJTAjBgkqhkiG9w0BCQEWFmdvbmdjaGVuQG1pY3Jvc29mdC5jb20wIBcN
+MTMwMzA2MDAxNzU2WhgPMTkyMzA5MTIyMDMxNDhaMIGXMQswCQYDVQQGEwJVUzEL
+MAkGA1UECBMCV0ExEDAOBgNVBAcTB1JlZG1vbmQxEzARBgNVBAoTCk1pY3Jvc29m
+dCAxFjAUBgNVBAsTDVdpbmRvd3MgQXp1cmUxFTATBgNVBAMTDEFsYmVydCBDaGVu
+ZzElMCMGCSqGSIb3DQEJARYWZ29uZ2NoZW5AbWljcm9zb2Z0LmNvbTCBnzANBgkq
+hkiG9w0BAQEFAAOBjQAwgYkCgYEAxct8f2TOECFGtZs5zJN9Vmtk6Jeo2ThbJ8XO
+0GPgjKjfLGUgUHrUKSpaHrObaEQWtU9/qdCmctHep5veulGApZ6cBKjL+7xKGQfP
+KHq+6nsvF2EutZenPvsFDb7msezcT+Ut1yMnCUd9sjcD0/g2AO5CpplnUR7MOIaq
+j/ifsNMCAwEAATANBgkqhkiG9w0BAQUFAAOBgQCAY9QBsXtEfDTZ6Gmplkd2mGGf
+aFxRXtEtEXxBrEjhq3c2F8le1ZpMysWfmgpsImZODf3rPN5+UMmL2cqxF0RU+7kG
+qPSo8egxg33IfEMTeH5WYHr8ilOxBfw25nnUGr7Cym0m0JAmh5xR47vmEb/EHIXf
+iFKpK6o4bjjnszUV2g==
+-----END CERTIFICATE-----
diff --git a/microsoft-azure-api/src/test/resources/certificate/server.der b/microsoft-azure-api/src/test/resources/certificate/server.der
new file mode 100644
index 000000000000..e34ad905b9f1
Binary files /dev/null and b/microsoft-azure-api/src/test/resources/certificate/server.der differ