Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge pull request #19 from jcookems/encTest

Enc test
  • Loading branch information...
commit c77f402c2d99d3a9acce7303f128a285b7277b13 2 parents a744d2f + 1b0c74e
@jcookems jcookems authored
View
6 microsoft-azure-api/pom.xml
@@ -104,12 +104,6 @@
<artifactId>commons-lang3</artifactId>
<version>3.1</version>
</dependency>
- <dependency>
- <groupId>org.bouncycastle</groupId>
- <artifactId>bcprov-jdk16</artifactId>
- <version>1.46</version>
- <scope>test</scope>
- </dependency>
</dependencies>
<build>
View
86 microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/media/EncryptionHelper.java
@@ -16,100 +16,50 @@
package com.microsoft.windowsazure.services.media;
import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
import java.io.InputStream;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
import java.security.Key;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
import java.security.SecureRandom;
-import java.security.cert.CertificateException;
+import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.util.Random;
-import java.util.UUID;
-import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import com.microsoft.windowsazure.services.core.storage.utils.Base64;
-public class EncryptionHelper {
-
- public static byte[] createRandomVector(int numberOfBits) {
- int numberOfBytes = numberOfBits / 8;
- byte[] randomVector = new byte[numberOfBytes];
- Random random = new Random();
- random.nextBytes(randomVector);
- return randomVector;
- }
-
- public static byte[] EncryptSymmetricKey(String protectionKey, byte[] inputData) throws Exception {
-
- X509Certificate x509Certificate = createX509CertificateFromString(protectionKey);
- return EncryptSymmetricKey(x509Certificate.getPublicKey(), inputData);
- }
-
- private static X509Certificate createX509CertificateFromString(String protectionKey) throws CertificateException {
+class EncryptionHelper {
+ public static byte[] encryptSymmetricKey(String protectionKey, byte[] inputData) throws Exception {
+ Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
- ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(Base64.decode(protectionKey));
- X509Certificate x509cert = (X509Certificate) certificateFactory.generateCertificate(byteArrayInputStream);
- return x509cert;
-
- }
-
- public static byte[] EncryptSymmetricKey(Key publicKey, byte[] inputData) throws NoSuchAlgorithmException,
- NoSuchProviderException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException,
- BadPaddingException {
- Cipher cipher = Cipher.getInstance("RSA/None/OAEPWithSHA1AndMGF1Padding", "BC");
+ byte[] protectionKeyBytes = Base64.decode(protectionKey);
+ ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(protectionKeyBytes);
+ Certificate certificate = certificateFactory.generateCertificate(byteArrayInputStream);
+ Key publicKey = certificate.getPublicKey();
SecureRandom secureRandom = new SecureRandom();
cipher.init(Cipher.ENCRYPT_MODE, publicKey, secureRandom);
byte[] cipherText = cipher.doFinal(inputData);
return cipherText;
}
- public static String calculateChecksum(UUID uuid, byte[] aesKey) throws NoSuchAlgorithmException,
- NoSuchProviderException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException,
- InvalidKeyException {
+ public static String calculateContentKeyChecksum(String uuid, byte[] aesKey) throws Exception {
+ Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKeySpec secretKeySpec = new SecretKeySpec(aesKey, "AES");
- Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5PADDING", "BC");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
- byte[] encryptionResult = cipher.doFinal(uuid.toString().getBytes());
+ byte[] encryptionResult = cipher.doFinal(uuid.getBytes("UTF8"));
byte[] checksumByteArray = new byte[8];
- for (int i = 0; i < 8; i++) {
- checksumByteArray[i] = encryptionResult[i];
- }
+ System.arraycopy(encryptionResult, 0, checksumByteArray, 0, 8);
String checksum = Base64.encode(checksumByteArray);
return checksum;
}
- public static byte[] EncryptFile(InputStream inputStream, byte[] aesKey, byte[] initializationVector)
- throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException, InvalidKeyException,
- InvalidAlgorithmParameterException, IOException {
- // preparation
- SecretKeySpec key = new SecretKeySpec(aesKey, "AES");
- IvParameterSpec ivParameterSpec = new IvParameterSpec(initializationVector);
- Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding", "BC");
-
- // encryption
- cipher.init(Cipher.ENCRYPT_MODE, key, ivParameterSpec);
+ public static InputStream encryptFile(InputStream inputStream, byte[] key, byte[] iv) throws Exception {
+ Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
+ SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
+ IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
+ cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivParameterSpec);
CipherInputStream cipherInputStream = new CipherInputStream(inputStream, cipher);
- ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
-
- int ch;
- while ((ch = cipherInputStream.read()) >= 0) {
- byteArrayOutputStream.write(ch);
- }
-
- byte[] cipherText = byteArrayOutputStream.toByteArray();
- return cipherText;
+ return cipherInputStream;
}
-
}
View
258 microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/media/EncryptionIntegrationTest.java
@@ -17,11 +17,13 @@
import static org.junit.Assert.*;
-import java.io.ByteArrayInputStream;
+import java.io.IOException;
import java.io.InputStream;
-import java.security.Security;
+import java.math.BigInteger;
+import java.net.URL;
import java.util.EnumSet;
import java.util.List;
+import java.util.Random;
import java.util.UUID;
import org.junit.Test;
@@ -55,141 +57,183 @@
import com.microsoft.windowsazure.services.media.models.TaskState;
public class EncryptionIntegrationTest extends IntegrationTestBase {
-
private final String strorageDecryptionProcessor = "Storage Decryption";
- private String createContentKeyId(UUID uuid) {
- String randomContentKey = String.format("nb:kid:UUID:%s", uuid);
- return randomContentKey;
- }
+ @Test
+ public void uploadAesProtectedAssetAndDownloadSuccess() throws Exception {
+ // Arrange
- private String getProtectionKeyId() throws ServiceException {
- String protectionKeyId = (String) service.action(ProtectionKey
- .getProtectionKeyId(ContentKeyType.StorageEncryption));
- return protectionKeyId;
- }
+ // Media Services requires 256-bit (32-byte) keys and
+ // 128-bit (16-byte) initialization vectors (IV) for AES encryption,
+ // and also requires that only the first 8 bytes of the IV is filled.
+ Random random = new Random();
+ byte[] aesKey = new byte[32];
+ random.nextBytes(aesKey);
+ byte[] effectiveIv = new byte[8];
+ random.nextBytes(effectiveIv);
+ byte[] iv = new byte[16];
+ System.arraycopy(effectiveIv, 0, iv, 0, effectiveIv.length);
+
+ InputStream mpeg4H264InputStream = getClass().getResourceAsStream("/media/MPEG4-H264.mp4");
+ InputStream encryptedContent = EncryptionHelper.encryptFile(mpeg4H264InputStream, aesKey, iv);
+ int durationInMinutes = 10;
- private JobInfo decodeAsset(String name, AssetInfo assetInfo) throws ServiceException, InterruptedException {
- MediaProcessorInfo mediaProcessorInfo = GetMediaProcessor(strorageDecryptionProcessor);
- JobInfo jobInfo = createJob(name, assetInfo, mediaProcessorInfo);
- return waitJobForCompletion(jobInfo);
+ // Act
+ AssetInfo assetInfo = service.create(Asset.create().setName(testAssetPrefix + "uploadAesProtectedAssetSuccess")
+ .setOptions(AssetOption.StorageEncrypted));
+ WritableBlobContainerContract blobWriter = getBlobWriter(assetInfo.getId(), durationInMinutes);
+
+ // gets the public key for storage encryption.
+ String contentKeyId = makeContentKeyId(aesKey);
+
+ // link the content key with the asset.
+ service.action(Asset.linkContentKey(assetInfo.getId(), contentKeyId));
+
+ // upload the encrypted file to the server.
+ uploadEncryptedAssetFile(assetInfo, blobWriter, "MPEG4-H264.mp4", encryptedContent, contentKeyId, iv);
+
+ // submit and execute the decoding job.
+ JobInfo jobInfo = decodeAsset("uploadAesProtectedAssetSuccess", assetInfo.getId());
+ // assert
+ LinkInfo taskLinkInfo = jobInfo.getTasksLink();
+ List<TaskInfo> taskInfos = service.list(Task.list(taskLinkInfo));
+ for (TaskInfo taskInfo : taskInfos) {
+ assertEquals(TaskState.Completed, taskInfo.getState());
+ ListResult<AssetInfo> outputs = service.list(Asset.list(taskInfo.getOutputAssetsLink()));
+ assertEquals(1, outputs.size());
+ }
+ assertEquals(JobState.Finished, jobInfo.getState());
+
+ // Verify that the contents match
+ InputStream expected = getClass().getResourceAsStream("/media/MPEG4-H264.mp4");
+
+ ListResult<AssetInfo> outputAssets = service.list(Asset.list(jobInfo.getOutputAssetsLink()));
+ assertEquals(1, outputAssets.size());
+ AssetInfo outputAsset = outputAssets.get(0);
+ ListResult<AssetFileInfo> assetFiles = service.list(AssetFile.list(assetInfo.getAssetFilesLink()));
+ assertEquals(1, assetFiles.size());
+ AssetFileInfo outputFile = assetFiles.get(0);
+
+ InputStream actual = getFileContents(outputAsset.getId(), outputFile.getName(), durationInMinutes);
+ assertStreamsEqual(expected, actual);
}
- private JobInfo createJob(String name, AssetInfo assetInfo, MediaProcessorInfo mediaProcessorInfo)
- throws ServiceException {
- // String configuration = "H.264 256k DSL CBR";
+ private JobInfo decodeAsset(String name, String inputAssetId) throws ServiceException, InterruptedException {
+ MediaProcessorInfo mediaProcessorInfo = service.list(
+ MediaProcessor.list().set("$filter", "Name eq '" + strorageDecryptionProcessor + "'")).get(0);
+
String taskBody = "<taskBody>"
+ "<inputAsset>JobInputAsset(0)</inputAsset><outputAsset assetCreationOptions=\"0\" assetName=\"Output\">JobOutputAsset(0)</outputAsset></taskBody>";
- JobInfo jobInfo = service.create(Job.create().addInputMediaAsset(assetInfo.getId())
+ JobInfo jobInfo = service.create(Job.create().addInputMediaAsset(inputAssetId)
.addTaskCreator(Task.create(mediaProcessorInfo.getId(), taskBody).setName(name)));
- return jobInfo;
- }
- private JobInfo waitJobForCompletion(JobInfo jobInfo) throws ServiceException, InterruptedException {
JobInfo currentJobInfo = jobInfo;
- while (currentJobInfo.getState().getCode() < 3) {
+ int retryCounter = 0;
+ while (currentJobInfo.getState().getCode() < 3 && retryCounter < 10) {
+ Thread.sleep(10000);
currentJobInfo = service.get(Job.get(jobInfo.getId()));
- Thread.sleep(4000);
+ retryCounter++;
}
return currentJobInfo;
}
- private MediaProcessorInfo GetMediaProcessor(String mediaProcessorName) throws ServiceException {
- List<MediaProcessorInfo> mediaProcessorInfos = service.list(MediaProcessor.list());
- for (MediaProcessorInfo mediaProcessorInfo : mediaProcessorInfos) {
- if (mediaProcessorInfo.getName().equals(mediaProcessorName)) {
- return mediaProcessorInfo;
- }
- }
- return null;
- }
-
- private String getProtectionKey(String protectionKeyId) throws ServiceException {
+ private String makeContentKeyId(byte[] aesKey) throws ServiceException, Exception {
+ String protectionKeyId = (String) service.action(ProtectionKey
+ .getProtectionKeyId(ContentKeyType.StorageEncryption));
String protectionKey = (String) service.action(ProtectionKey.getProtectionKey(protectionKeyId));
- return protectionKey;
- }
- private AssetFileInfo uploadEncryptedAssetFile(AssetInfo assetInfo, LocatorInfo locatorInfo,
- ContentKeyInfo contentKeyInfo, String blobName, byte[] encryptedContent) throws ServiceException {
- WritableBlobContainerContract blobWriter = service.createBlobWriter(locatorInfo);
- InputStream blobContent = new ByteArrayInputStream(encryptedContent);
- blobWriter.createBlockBlob(blobName, blobContent);
- AssetFileInfo assetFileInfo = service.create(AssetFile.create(assetInfo.getId(), blobName).setIsPrimary(true)
- .setIsEncrypted(true).setContentFileSize(new Long(encryptedContent.length))
- .setEncryptionScheme("StorageEncryption").setEncryptionVersion("1.0")
- .setEncryptionKeyId(contentKeyInfo.getId()));
- return assetFileInfo;
- }
+ String contentKeyIdUuid = UUID.randomUUID().toString();
+ String contentKeyId = String.format("nb:kid:UUID:%s", contentKeyIdUuid);
- private ContentKeyInfo createContentKey(byte[] aesKey, ContentKeyType contentKeyType, String protectionKeyId,
- String protectionKey) throws Exception {
- UUID contentKeyIdUuid = UUID.randomUUID();
- String contentKeyId = createContentKeyId(contentKeyIdUuid);
- byte[] encryptedContentKey = EncryptionHelper.EncryptSymmetricKey(protectionKey, aesKey);
+ byte[] encryptedContentKey = EncryptionHelper.encryptSymmetricKey(protectionKey, aesKey);
String encryptedContentKeyString = Base64.encode(encryptedContentKey);
- String checksum = EncryptionHelper.calculateChecksum(contentKeyIdUuid, aesKey);
+ String checksum = EncryptionHelper.calculateContentKeyChecksum(contentKeyIdUuid, aesKey);
+
ContentKeyInfo contentKeyInfo = service.create(ContentKey
- .create(contentKeyId, contentKeyType, encryptedContentKeyString).setChecksum(checksum)
- .setProtectionKeyId(protectionKeyId));
- return contentKeyInfo;
+ .create(contentKeyId, ContentKeyType.StorageEncryption, encryptedContentKeyString)
+ .setChecksum(checksum).setProtectionKeyId(protectionKeyId));
+ return contentKeyInfo.getId();
}
- @Test
- public void uploadAesProtectedAssetAndDownloadSuccess() throws Exception {
- // Arrange
- Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
- InputStream smallWMVInputStream = getClass().getResourceAsStream("/media/SmallWMV.wmv");
- byte[] aesKey = EncryptionHelper.createRandomVector(256);
- byte[] initializationVector = EncryptionHelper.createRandomVector(128);
- int durationInMinutes = 10;
-
- // Act
-
- // creates asset
- AssetInfo assetInfo = service.create(Asset.create().setName("uploadAesProtectedAssetSuccess")
- .setOptions(AssetOption.StorageEncrypted));
+ private void uploadEncryptedAssetFile(AssetInfo asset, WritableBlobContainerContract blobWriter, String blobName,
+ InputStream blobContent, String encryptionKeyId, byte[] iv) throws ServiceException {
+ blobWriter.createBlockBlob(blobName, blobContent);
+ service.action(AssetFile.createFileInfos(asset.getId()));
+ ListResult<AssetFileInfo> files = service.list(AssetFile.list(asset.getAssetFilesLink()).set("$filter",
+ "Name eq '" + blobName + "'"));
+ assertEquals(1, files.size());
+ AssetFileInfo file = files.get(0);
+ byte[] sub = new byte[9];
+ // Offset bytes to ensure that the sign-bit is not set.
+ // Media Services expects unsigned Int64 values.
+ System.arraycopy(iv, 0, sub, 1, 8);
+ BigInteger longIv = new BigInteger(sub);
+ String initializationVector = longIv.toString();
+
+ service.update(AssetFile.update(file.getId()).setIsEncrypted(true).setEncryptionKeyId(encryptionKeyId)
+ .setEncryptionScheme("StorageEncryption").setEncryptionVersion("1.0")
+ .setInitializationVector(initializationVector));
+ }
- // creates writable access policy
- AccessPolicyInfo accessPolicyInfo = service.create(AccessPolicy.create("uploadAesPortectedAssetSuccess",
- durationInMinutes, EnumSet.of(AccessPolicyPermission.WRITE)));
+ private WritableBlobContainerContract getBlobWriter(String assetId, int durationInMinutes) throws ServiceException {
+ AccessPolicyInfo accessPolicyInfo = service.create(AccessPolicy.create(testPolicyPrefix
+ + "uploadAesPortectedAssetSuccess", durationInMinutes, EnumSet.of(AccessPolicyPermission.WRITE)));
// creates locator for the input media asset
- LocatorInfo locatorInfo = service.create(Locator.create(accessPolicyInfo.getId(), assetInfo.getId(),
- LocatorType.SAS));
-
- // gets the public key for storage encryption.
-
- String protectionKeyId = getProtectionKeyId();
- String protectionKey = getProtectionKey(protectionKeyId);
-
- // creates the content key with encrypted
- ContentKeyInfo contentKeyInfo = createContentKey(aesKey, ContentKeyType.StorageEncryption, protectionKeyId,
- protectionKey);
-
- // link the content key with the asset.
- service.action(Asset.linkContentKey(assetInfo.getId(), contentKeyInfo.getId()));
-
- // encrypt the file.
- byte[] encryptedContent = EncryptionHelper.EncryptFile(smallWMVInputStream, aesKey, initializationVector);
-
- // upload the encrypted file to the server.
- AssetFileInfo assetFileInfo = uploadEncryptedAssetFile(assetInfo, locatorInfo, contentKeyInfo,
- "uploadAesProtectedAssetSuccess", encryptedContent);
-
- // submit and execute the decoding job.
- JobInfo jobInfo = decodeAsset("uploadAesProtectedAssetSuccess", assetInfo);
+ LocatorInfo locatorInfo = service.create(Locator.create(accessPolicyInfo.getId(), assetId, LocatorType.SAS));
+ WritableBlobContainerContract blobWriter = service.createBlobWriter(locatorInfo);
+ return blobWriter;
+ }
- // assert
- LinkInfo taskLinkInfo = jobInfo.getTasksLink();
- List<TaskInfo> taskInfos = service.list(Task.list(taskLinkInfo));
- for (TaskInfo taskInfo : taskInfos) {
- assertEquals(TaskState.Completed, taskInfo.getState());
- ListResult<AssetInfo> outputs = service.list(Asset.list(taskInfo.getOutputAssetsLink()));
- assertEquals(1, outputs.size());
+ private InputStream getFileContents(String assetId, String fileName, int availabilityWindowInMinutes)
+ throws ServiceException, InterruptedException, IOException {
+ AccessPolicyInfo readAP = service.create(AccessPolicy.create(testPolicyPrefix + "tempAccessPolicy",
+ availabilityWindowInMinutes, EnumSet.of(AccessPolicyPermission.READ)));
+ LocatorInfo readLocator = service.create(Locator.create(readAP.getId(), assetId, LocatorType.SAS));
+ URL file = new URL(readLocator.getBaseUri() + "/" + fileName + readLocator.getContentAccessToken());
+
+ // There can be a delay before a new read locator is applied for the asset files.
+ InputStream reader = null;
+ for (int counter = 0; true; counter++) {
+ try {
+ reader = file.openConnection().getInputStream();
+ break;
+ }
+ catch (IOException e) {
+ System.out.println("Got error, wait a bit and try again");
+ if (counter < 6) {
+ Thread.sleep(10000);
+ }
+ else {
+ // No more retries.
+ throw e;
+ }
+ }
}
- assertEquals(JobState.Finished, jobInfo.getState());
+ return reader;
}
+ private void assertStreamsEqual(InputStream inputStream1, InputStream inputStream2) throws IOException {
+ byte[] buffer1 = new byte[1024];
+ byte[] buffer2 = new byte[1024];
+ try {
+ while (true) {
+ int n1 = inputStream1.read(buffer1);
+ int n2 = inputStream2.read(buffer2);
+ assertEquals("number of bytes read from streams", n1, n2);
+ if (n1 == -1) {
+ break;
+ }
+ for (int i = 0; i < n1; i++) {
+ assertEquals("byte " + i + " read from streams", buffer1[i], buffer2[i]);
+ }
+ }
+ }
+ finally {
+ inputStream1.close();
+ inputStream2.close();
+ }
+ }
}
View
7 microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/media/IntegrationTestBase.java
@@ -96,7 +96,12 @@ private static void removeAllTestContentKeys() {
List<ContentKeyInfo> contentKeyInfos = service.list(ContentKey.list());
for (ContentKeyInfo contentKeyInfo : contentKeyInfos) {
- service.delete(ContentKey.delete(contentKeyInfo.getId()));
+ try {
+ service.delete(ContentKey.delete(contentKeyInfo.getId()));
+ }
+ catch (Exception e) {
+ e.printStackTrace();
+ }
}
}
catch (Exception e) {
View
BIN  microsoft-azure-api/src/test/resources/media/SmallWMV.wmv
Binary file not shown
Please sign in to comment.
Something went wrong with that request. Please try again.