Skip to content

Commit

Permalink
Merge pull request #319 from gcheng/dev
Browse files Browse the repository at this point in the history
Implementation of rebind content key.
  • Loading branch information
Albert Cheng committed Apr 9, 2013
2 parents b4b7fb5 + 44e2421 commit 5740210
Show file tree
Hide file tree
Showing 11 changed files with 445 additions and 10 deletions.
6 changes: 6 additions & 0 deletions microsoft-azure-api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,12 @@
<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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ public <T> T action(EntityTypeActionOperation<T> 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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -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<String> 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<String> rebind(String contentKeyId) {
return rebind(contentKeyId, "");
}

private static class RebindContentKeyActionOperation extends DefaultEntityTypeActionOperation<String> {
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<RebindContentKeyType> rebindContentKeyTypeJaxbElement;
try {
rebindContentKeyTypeJaxbElement = unmarshaller.unmarshal(new StreamSource(inputStream),
RebindContentKeyType.class);
}
catch (JAXBException e) {
throw new RuntimeException(e);
}
return rebindContentKeyTypeJaxbElement.getValue();
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,19 @@

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;

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;
Expand All @@ -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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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"));
}
}
Loading

0 comments on commit 5740210

Please sign in to comment.