How to write a custom DRM module that integrates with key server technology

Wowza Streaming Engine™ media server software has a rich API for securing your video streams. Use the best practices described in this article to create a custom module that integrates with a third-party Digital Rights Management (DRM) key service for securing media streaming.

The best place to get started is to review the articles How to use server-side modules and HTTP Providers and How to extend Wowza Streaming Engine using Java, which describe how to extend the Streaming Engine software using Java. The Wowza Streaming Engine Server-Side API Reference is a reference of all of the public Java APIs. The article How to extend Wowza Streaming Engine using the Wowza IDE can be used to simplify the process of creating your module.

Below are articles that describe in more detail how to secure streaming for Apple HLS, MPEG-DASH, and Smooth Streaming:

Apple HLS

How to secure Apple HLS streaming using DRM encryption
How to add custom playlist headers to Apple HLS manifests

MPEG-DASH

How to secure MPEG-DASH streaming using Common Encryption (CENC)

Smooth Streaming

How to secure Smooth Streaming using PlayReady DRM (Silverlight)

Most likely, you'll create a single Wowza Streaming Engine custom module that's used to encrypt on-demand and live streams. That module will be configured on a per-application basis using application-level properties in [conf]/[application-namew]/Application.xml. You can use the properties to configure the customer's account credentials (user name and password), URL to a remote key server API, and a map file for mapping stream names to DRM information. The suggested property name format is:
drm[service-name][camel-case-property-name]
For example, if your service name is ACME Encryption, the properties to configure your service might be:
<Property>
	<Name>drmACMEEncryptionUserName</Name>
	<Value>JUser</Value>
	<Type>String</Type>
</Property>
<Property>
	<Name>drmACMEEncryptionPassword</Name>
	<Value>password1</Value>
	<Type>String</Type>
</Property>
<Property>
	<Name>drmACMEEncryptionRESTUrl</Name>
	<Value>https://acmedrm.com/rest</Value>
	<Type>String</Type>
</Property>
<Property>
	<Name>drmACMEEncryptionStreamNameMapFile</Name>
	<Value>${com.wowza.wms.context.VHostConfigHome}/${com.wowza.wms.context.Application}/conf/acmedrmstreammap.txt</Value>
	<Type>String</Type>
</Property>
The following are the common methods used to extract properties set in Application.xml:
String WMSProperties.getPropertyStr(String name, String defaultVal)
boolean WMSProperties.getPropertyBoolean(String name, boolean defaultVal)
public int WMSProperties.getPropertyInt(String name, int defaultVal)
public long WMSProperties.getPropertyLong(String name, long defaultVal)
public double WMSProperties.getPropertyDouble(String name, double defaultVal)
For example:
public void onAppStart(IApplicationInstance appInstance)
{
	String userName = props.getPropertyStr("drmACMEEncryptionUserName", null;);
	String password = props.getPropertyStr("drmACMEEncryptionPassword", null;);
	String restUrl = props.getPropertyStr("drmACMEEncryptionRESTUrl", null;);
}
In your module, the path variables for the drmACMEEncryptionStreamNameMapFile property can be resolved using the following code in your onAppStart method:
public static final String DEFAULT_DRMSTREAMNAMEMAPFILE = "${com.wowza.wms.context.VHostConfigHome}/${com.wowza.wms.context.Application}/conf/acmedrmstreammap.txt";

...

public void onAppStart(IApplicationInstance appInstance)
{

...

WMSProperties props = appInstance.getProperties();

Map pathMap = new HashMap();

pathMap.put("com.wowza.wms.context.VHost", appInstance.getVHost().getName());
pathMap.put("com.wowza.wms.context.VHostConfigHome", appInstance.getVHost().getHomePath());
pathMap.put("com.wowza.wms.context.Application", appInstance.getApplication().getName());
pathMap.put("com.wowza.wms.context.ApplicationInstance", appInstance.getName());

String mapFilePath = props.getPropertyStr("drmACMEEncryptionStreamNameMapFile", DEFAULT_DRMSTREAMNAMEMAPFILE);
mapFilePath =  SystemUtils.expandEnvironmentVariables(mapFilePath, pathMap);

...

}
The general process to apply DRM encryption to a stream involves the following steps:
 
  1. Map a stream name to a private encryption key and public key identifier, as well as to additional DRM metadata.
     
  2. Call the Wowza Streaming Engine API to apply the encryption key, key identifier, and DRM metadata to the stream.
In general, two components are needed to perform the first step: (1) a map file to map stream names to DRM key information and (2) a remote service or local calculation for using the DRM key information to retrieve the private encryption key. The suggested method for mapping stream names to DRM key information is to use a map file, which maps a single stream to DRM key information. The suggested format of this file is a hybrid JSON format like this:
stream1={"publicKeyId":"stream1", "keyDomain":"myContent"}
stream2={"publicKeyId":"stream2", "keyDomain":"myContent"}
The left side is the stream name and the right side is JSON data that represents DRM key information that's needed to fetch or calculate the appropriate private encryption key. Wowza Streaming Engine includes the Jackson JSON Processor. To use this API, import the following libraries.
import com.wowza.util.JSON;
The following example code shows how to parse the map file and extract the stream name and JSON parameters:
String delimiter = "=";

BufferedReader inf = new BufferedReader(new FileReader(file));
String line;
while ((line = inf.readLine()) != null)
{
	line = line.trim();
	if (line.startsWith("#"))
		continue;

	if (line.length() == 0)
		continue;

	int pos = line.indexOf(delimter);
	if (pos < 0)
	{
		// report malformed map entry line
		continue;
	}

	String streanName = line.substring(0, pos).trim();
	String jsonStr = line.substring(pos+1).trim();

	JSON data = new JSON();
	data.readValues(jsonStr);
	Map<String, Object> entries = data.getEntrys();
}

inf.close();
After the per-stream DRM information is known, you must retrieve or calculate the private encryption key for the stream. For many DRM systems, this is done by communicating with a remote key server using REST or a web services API. If communicating with a remote API over the network, it's very important that this is done in a way that doesn't impede the streaming server operation. The following are some recommendations:
 
  • If possible, do network communication outside of encryption callbacks so that network latency or service downtime don't impact streaming.
     
  • Set short network timeouts where possible.
     
  • Create a thread, which runs in the background, to periodically verify that your network service is running. If it goes down for a while, then suspend calls to the service until it's restored. In the case where the service is down, it might make sense to encrypt streams with a random encryption key so that they're not playable.
     
  • Cache key requests so that they only need to be retrieved once per-stream name.
The goal is to try and avoid or limit the time spent blocked, waiting for network traffic in the encryption callbacks.
Originally Published: For Wowza Streaming Engine on 10-05-2015.
 

If you're having problems or want to discuss this article, post in our forum.