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