Skip to content

Commit

Permalink
Merge pull request #572 from southworkscom/master
Browse files Browse the repository at this point in the history
Add support for Widevine (DRM) dynamic encryption to Azure Media Services SDK
  • Loading branch information
jianghaolu committed Jan 6, 2016
2 parents b66df65 + 9b9cb2a commit 0fca4c7
Show file tree
Hide file tree
Showing 16 changed files with 523 additions and 53 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.microsoft.windowsazure.services.media.implementation.templates.tokenrestriction;

public final class ErrorMessages {

public static final String PRIMARY_VERIFICATIONKEY_AND_OPENIDCONNECTDISCOVERYDOCUMENT_ARE_NULL
= "Both PrimaryVerificationKey and OpenIdConnectDiscoveryDocument are null.";

public static final String OPENIDDISCOVERYURI_STRING_IS_NULL_OR_EMPTY
= "OpenIdConnectDiscoveryDocument.OpenIdDiscoveryUri string value is null or empty.";

public static final String OPENIDDISCOVERYURI_STRING_IS_NOT_ABSOLUTE_URI
= "String representation of OpenIdConnectDiscoveryDocument.OpenIdDiscoveryUri is not valid absolute Uri.";

private ErrorMessages() {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.microsoft.windowsazure.services.media.implementation.templates.tokenrestriction;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "OpenIdConnectDiscoveryDocument")
public class OpenIdConnectDiscoveryDocument {

@XmlElement(name = "OpenIdDiscoveryUri")
private String openIdDiscoveryUri;

/**
* @return the openIdDiscoveryUri
*/
public String getOpenIdDiscoveryUri() {
return openIdDiscoveryUri;
}

/**
* @param openIdDiscoveryUri the openIdDiscoveryUri to set
*/
public void setOpenIdDiscoveryUri(String openIdDiscoveryUri) {
this.openIdDiscoveryUri = openIdDiscoveryUri;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,47 +10,49 @@
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;


@XmlRootElement(name = "TokenRestrictionTemplate")
@XmlAccessorType(XmlAccessType.FIELD)
public class TokenRestrictionTemplate {

@XmlElementWrapper(name = "AlternateVerificationKeys")
@XmlElement(name = "TokenVerificationKey")
private List<TokenVerificationKey> alternateVerificationKeys;

@XmlElement(name = "Audience", required = true)
private URI audience;

@XmlElement(name = "Issuer", required = true)
private URI issuer;
@XmlElement(name = "PrimaryVerificationKey")

@XmlElement(name = "PrimaryVerificationKey", nillable = true)
private TokenVerificationKey primaryVerificationKey;

@XmlElementWrapper(name = "RequiredClaims")
@XmlElement(name = "TokenClaim")
private List<TokenClaim> requiredClaims;

@XmlElement(name = "TokenType")
private TokenType tokenType;


@XmlElement(name = "OpenIdConnectDiscoveryDocument")
private OpenIdConnectDiscoveryDocument openIdConnectDiscoveryDocument;

@SuppressWarnings("unused")
private TokenRestrictionTemplate() {
this.setTokenType(TokenType.SWT);
initCollections();
}

public TokenRestrictionTemplate(TokenType tokenType) {
this.setTokenType(tokenType);
initCollections();
}

private void initCollections() {
setRequiredClaims(new ArrayList<TokenClaim>());
setAlternateVerificationKeys(new ArrayList<TokenVerificationKey>());
}

/**
* @return the audience
*/
Expand All @@ -59,7 +61,8 @@ public URI getAudience() {
}

/**
* @param audience the audience to set
* @param audience
* the audience to set
* @return this
*/
public TokenRestrictionTemplate setAudience(URI audience) {
Expand All @@ -75,7 +78,8 @@ public URI getIssuer() {
}

/**
* @param issuer the issuer to set
* @param issuer
* the issuer to set
* @return this
*/
public TokenRestrictionTemplate setIssuer(URI issuer) {
Expand All @@ -91,7 +95,8 @@ public TokenType getTokenType() {
}

/**
* @param tokenType the tokenType to set
* @param tokenType
* the tokenType to set
* @return this
*/
public TokenRestrictionTemplate setTokenType(TokenType tokenType) {
Expand All @@ -107,7 +112,8 @@ public TokenVerificationKey getPrimaryVerificationKey() {
}

/**
* @param primaryVerificationKey the primaryVerificationKey to set
* @param primaryVerificationKey
* the primaryVerificationKey to set
* @return this
*/
public TokenRestrictionTemplate setPrimaryVerificationKey(TokenVerificationKey primaryVerificationKey) {
Expand All @@ -123,7 +129,8 @@ public List<TokenClaim> getRequiredClaims() {
}

/**
* @param requiredClaims the requiredClaims to set
* @param requiredClaims
* the requiredClaims to set
* @return this
*/
public TokenRestrictionTemplate setRequiredClaims(List<TokenClaim> requiredClaims) {
Expand All @@ -139,11 +146,31 @@ public List<TokenVerificationKey> getAlternateVerificationKeys() {
}

/**
* @param alternateVerificationKeys the alternateVerificationKeys to set
* @param alternateVerificationKeys
* the alternateVerificationKeys to set
* @return this
*/
public TokenRestrictionTemplate setAlternateVerificationKeys(List<TokenVerificationKey> alternateVerificationKeys) {
this.alternateVerificationKeys = alternateVerificationKeys;
return this;
}

/**
* @return the alternateVerificationKeys
*/
public OpenIdConnectDiscoveryDocument getOpenIdConnectDiscoveryDocument() {
return openIdConnectDiscoveryDocument;
}

/**
* @param alternateVerificationKeys
* the alternateVerificationKeys to set
* @return this
*/
public TokenRestrictionTemplate setOpenIdConnectDiscoveryDocument(
OpenIdConnectDiscoveryDocument openIdConnectDiscoveryDocument) {
this.openIdConnectDiscoveryDocument = openIdConnectDiscoveryDocument;
return this;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import java.io.File;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
Expand Down Expand Up @@ -37,16 +39,19 @@ private TokenRestrictionTemplateSerializer() {
}

public static String serialize(TokenRestrictionTemplate template) throws JAXBException {

validateTokenRestrictionTemplate(template);

StringWriter writer = new StringWriter();
JAXBContext context = JAXBContext.newInstance(TokenRestrictionTemplate.class);
Marshaller m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
m.setProperty("com.sun.xml.bind.namespacePrefixMapper", new NamespacePrefixMapper() {
@Override
public String[] getPreDeclaredNamespaceUris() {
return new String[] {
XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI
};
return new String[] {
XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI
};
}

@Override
Expand All @@ -61,6 +66,31 @@ public String getPreferredPrefix(String namespaceUri, String suggestion, boolean
return writer.toString();
}

private static void validateTokenRestrictionTemplate(TokenRestrictionTemplate template) {
if (template.getPrimaryVerificationKey() == null && template.getOpenIdConnectDiscoveryDocument() == null) {
throw new IllegalArgumentException(
ErrorMessages.PRIMARY_VERIFICATIONKEY_AND_OPENIDCONNECTDISCOVERYDOCUMENT_ARE_NULL);
}

if (template.getOpenIdConnectDiscoveryDocument() != null) {
if (template.getOpenIdConnectDiscoveryDocument().getOpenIdDiscoveryUri() == null
|| template.getOpenIdConnectDiscoveryDocument().getOpenIdDiscoveryUri().isEmpty()) {
throw new IllegalArgumentException(ErrorMessages.OPENIDDISCOVERYURI_STRING_IS_NULL_OR_EMPTY);
}

boolean openIdDiscoveryUrlValid = true;
try {
new URL(template.getOpenIdConnectDiscoveryDocument().getOpenIdDiscoveryUri());
} catch (MalformedURLException e) {
openIdDiscoveryUrlValid = false;
}

if (!openIdDiscoveryUrlValid) {
throw new IllegalArgumentException(ErrorMessages.OPENIDDISCOVERYURI_STRING_IS_NOT_ABSOLUTE_URI);
}
}
}

public static TokenRestrictionTemplate deserialize(String xml) throws JAXBException {
try {
return deserialize(xml, null);
Expand All @@ -79,7 +109,9 @@ public static TokenRestrictionTemplate deserialize(String xml, String validation
Schema schema = factory.newSchema(new File(validationSchemaFileName));
u.setSchema(schema);
}
return (TokenRestrictionTemplate) u.unmarshal(new StringReader(xml));
TokenRestrictionTemplate template = (TokenRestrictionTemplate) u.unmarshal(new StringReader(xml));
validateTokenRestrictionTemplate(template);
return template;
}

private static String generateTokenExpiry(Date expiry) {
Expand All @@ -101,8 +133,8 @@ private static String urlEncode(String toEncode) {
}

public static String generateTestToken(TokenRestrictionTemplate tokenTemplate, TokenVerificationKey signingKeyToUse,
UUID keyIdForContentKeyIdentifierClaim, Date tokenExpiration, Date notBefore) {
UUID keyIdForContentKeyIdentifierClaim, Date tokenExpiration, Date notBefore) {

if (tokenTemplate == null) {
throw new NullPointerException("tokenTemplate");
}
Expand All @@ -117,61 +149,63 @@ public static String generateTestToken(TokenRestrictionTemplate tokenTemplate, T
cal.add(Calendar.MINUTE, 10);
tokenExpiration = cal.getTime();
}

if (notBefore == null) {
Calendar cal = Calendar.getInstance();
cal.setTime(new Date());
cal.add(Calendar.MINUTE, -5);
notBefore = cal.getTime();
}

if (tokenTemplate.getTokenType().equals(TokenType.SWT)) {
return generateTestTokenSWT(tokenTemplate, signingKeyToUse, keyIdForContentKeyIdentifierClaim, tokenExpiration);
return generateTestTokenSWT(tokenTemplate, signingKeyToUse, keyIdForContentKeyIdentifierClaim,
tokenExpiration);
} else {
return generateTestTokenJWT(tokenTemplate, signingKeyToUse, keyIdForContentKeyIdentifierClaim, tokenExpiration, notBefore);
return generateTestTokenJWT(tokenTemplate, signingKeyToUse, keyIdForContentKeyIdentifierClaim,
tokenExpiration, notBefore);
}
}

public static String generateTestTokenJWT(TokenRestrictionTemplate tokenTemplate, TokenVerificationKey signingKeyToUse,
UUID keyIdForContentKeyIdentifierClaim, Date tokenExpiration, Date notBefore) {

public static String generateTestTokenJWT(TokenRestrictionTemplate tokenTemplate,
TokenVerificationKey signingKeyToUse, UUID keyIdForContentKeyIdentifierClaim, Date tokenExpiration,
Date notBefore) {

SymmetricVerificationKey signingKey = (SymmetricVerificationKey) signingKeyToUse;
SecretKeySpec secretKey = new SecretKeySpec(signingKey.getKeyValue(), "HmacSHA256");

// Mapping Claims.
Map<String, Object> claims = new HashMap<String, Object>();
for (TokenClaim claim : tokenTemplate.getRequiredClaims()) {
String claimValue = claim.getClaimValue();
if (claimValue == null && claim.getClaimType().equals(TokenClaim.getContentKeyIdentifierClaimType())) {
if (keyIdForContentKeyIdentifierClaim == null) {
throw new IllegalArgumentException(String.format("The 'keyIdForContentKeyIdentifierClaim' parameter cannot be null when the token template contains a required '%s' claim type.", TokenClaim.getContentKeyIdentifierClaimType()));
throw new IllegalArgumentException(String.format(
"The 'keyIdForContentKeyIdentifierClaim' parameter cannot be null when the token template contains a required '%s' claim type.",
TokenClaim.getContentKeyIdentifierClaimType()));
}
claimValue = keyIdForContentKeyIdentifierClaim.toString();
}
claims.put(claim.getClaimType(), claimValue);
claims.put(claim.getClaimType(), claimValue);
}

return Jwts.builder()
.setHeaderParam("typ", "JWT")
.setClaims(claims)
.setIssuer(tokenTemplate.getIssuer().toString())
.setAudience(tokenTemplate.getAudience().toString())
.setIssuedAt(notBefore)
.setExpiration(tokenExpiration)
.signWith(SignatureAlgorithm.HS256, secretKey)
return Jwts.builder().setHeaderParam("typ", "JWT").setClaims(claims)
.setIssuer(tokenTemplate.getIssuer().toString()).setAudience(tokenTemplate.getAudience().toString())
.setIssuedAt(notBefore).setExpiration(tokenExpiration).signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
}
public static String generateTestTokenSWT(TokenRestrictionTemplate tokenTemplate, TokenVerificationKey signingKeyToUse,
UUID keyIdForContentKeyIdentifierClaim, Date tokenExpiration) {

public static String generateTestTokenSWT(TokenRestrictionTemplate tokenTemplate,
TokenVerificationKey signingKeyToUse, UUID keyIdForContentKeyIdentifierClaim, Date tokenExpiration) {

StringBuilder builder = new StringBuilder();

for (TokenClaim claim : tokenTemplate.getRequiredClaims()) {
String claimValue = claim.getClaimValue();
if (claim.getClaimType().equals(TokenClaim.getContentKeyIdentifierClaimType())) {
if (keyIdForContentKeyIdentifierClaim == null) {
throw new IllegalArgumentException(String.format("The 'keyIdForContentKeyIdentifierClaim' parameter cannot be null when the token template contains a required '%s' claim type.", TokenClaim.getContentKeyIdentifierClaimType()));
throw new IllegalArgumentException(String.format(
"The 'keyIdForContentKeyIdentifierClaim' parameter cannot be null when the token template contains a required '%s' claim type.",
TokenClaim.getContentKeyIdentifierClaimType()));
}
claimValue = keyIdForContentKeyIdentifierClaim.toString();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.microsoft.windowsazure.services.media.implementation.templates.widevine;

public enum AllowedTrackTypes {
SD_ONLY, SD_HD
}
Loading

0 comments on commit 0fca4c7

Please sign in to comment.