WMRM SDK 10.1.2 best practice sample code

I wrote this sample code a while back to show how to validate the latest individualization version when using predelivery. Here are a few tricks that you need to do to make this work. Namely, you need to parse the “ClientInfo” XML fragment the hard way. This sample code shows you how to do just that. The documentation below is a complete walkthrough of the code but the code is also well documented. This is a great place to start if you are trying to do WM DRM predelivery form ASP.net. Keep in mind that this code will only work with the WMRM SDK version 10.1.2 or better. Older versions of the SDK may not work as expected. If you have an older version of the SDK you may need to modify the code. The code is published on my MSDN Code Gallery page and can be found here: https://code.msdn.microsoft.com/Release/ProjectReleases.aspx?ProjectName=mediasdkstuff&ReleaseId=1585

This code sample demonstrates how to validate the client’s player version and individualization version as well as how to use ASP.net to predeliver licenses using the “GetLicenseFromURL” method. When used as intended this sample validates the client’s player version, requiring WMP 11 and the clients individualization version, requiring version 3.6.0.1. Once the client is validated the client has the option to get a license. License delivery is completed via the “GetLicenseFromURL” method. 

Checking for WMP 11 and an individualization version of 3.6.0.1 is completed by manually parsing the “ClientInfo” data returned via a call to “MSNetObj.GetSystemInfo”. GetSystemInfor returns an XML fragment that contains information about the client, i.e. WM Player. We send this fragment from the client to the server via a “form post”. The data is sent from “DRMCheckVersion.htm” to “DRMCheckVersion.aspx”. The validation happens on the server side for simplicity but it theoretically could be handled on the client in JavaScript.

To validate the versions on the server we need to parse the XML fragment returned. To do this I have chosen a “brute force” approach. I’ve gone ahead and written a simple tokenizer and tag element parser. The XML fragment is first tokenized, placing each token into a separate element in an array list. Tokens are defined as being any text between “<” and “>” or any text between “>” and “<”. This causes the XML element text to be handled as a separate token. This makes parsing the token list trivial. We then parse the list and find the specified tag.  We then know that the next item in the list is the element we are looking for and it is returned. Remember that the element of the tag is everything between the beginning tag (<tag>) and ending tag (</tag>). Since we are sending an XML fragment from the client it is necessary to turn off page validation. You should make yourself aware of the security ramifications of turning off request validation by visiting this page: https://www.asp.net/learn/whitepapers/request-validation/. The method used to parse the XML fragment sent from the client acts on direct input from the client. While as far as I can tell it does not appear to be directly exploitable, it is HIGHLY recommended that you review it for possible security vulnerabilities before using it on an internet facing machine.  Remember this is just sample code and not production ready.

To validate the player version parse the “ClientInfo” looking for the element value of the “<CLIENTVERSION>” tag. This returns the version of the player that is being used. The player version is in the form [major].[minor].[???].[build]. For example: “10.00.00.3700”. We then use the .net “String.Split” method to separate the numbers into an array using “.” as the delimiter. We convert the strings to unsigned integers and compare them to our pre-defined constants. In this case we are requiring version 11 of the player and a build greater than 5000. We probably want to make sure that the user is at least on version 10 of the player. This is because the latest individualization update is only compatible with v10 or better. We also check for the build number. You could use this value to require a specific build. In this case we just verify that they are using version 5000 or better. Keep in mind that there are two build ranges depending on the OS WMP is installed on. On XP the build number will be between 5000 and 5999. On Vista the build number will be between 6000 and 6999. If the validation fails for some reason, the page redirects to another file in the project. In this case “DRMUpgradeWMP.htm”. This page prompts the user to update their player and provides a link to the download.

If the WMP version is validated then we check to make sure that the client has the latest individualization update. First we parse the client info XML fragment looking for the element  value for the “<MACHINECERTIFICATE>” tag. This value is  a modified Base64 encoded value. We need to convert it to standard Base64 encoding so that we can decode it. To do this we replace all of the “!” characters with “+” characters, and “*” characters with “/” characters. We then use “Convert.FromBase64String”. This yields us a byte array that contains yet another XML fragment. We convert the byte array to a string. After the conversation we parse the new XML fragment looking for the element value for the “<c:SecurityVersion>” tag. This is the current individualization version of the client. The individualization version is in the form “[major].[minor].[???].[individualized flag]”. For example: “3.6.0.1”. In this example the last digit is a one so we know that this machine has been individualized. If this last digit is a zero we know that it has not been individualized. We then use the .net “String.Split” method to separate the numbers into an array using “.” as the delimiter. We convert the strings to unsigned integers and compare them to our pre-defined constants. We check to make sure that we have a major version of “3” a minor version of “6” and an individualized flag of “1”. If the validation fails we redirect to “DRMNeedIndiv.htm”. This page then links to a page where the user can update their individualization version. If this check succeeds we then redirect to the license delivery page “DRMPredeliverLicense.htm”.

The “DRMPredeliverLicense.htm” page offers a button that the user can click on to get a license. When the button is clicked, the javascript on the page calls “MSNetObj.GetLicenseFromURL”. This method takes the license acquisition URL that will be used to generate the license. The URL that is passed to this method also includes a parameter that represents the KeyID of the license that will be delivered. Keep in mind that this KeyID value must match the KeyID that was used to encode the content when it was packaged. If the KeyID does not match you will not be able to play the content as expected. Having a KeyID mismatch between the license and the file is very common mistake. Keep in mind that typically you should not use the KeyID to identify the content. You should use the “CID” or “content ID” to identify the content and then look up the KeyID from a database on the server. However, to simplify the sample we use the KeyID directly. Remember that the KeyID should always be a 128 bit Base64 encoded value. Failure to use a 128 bit Base64 encoded value for the KeyID may result in unexpected behavior. Since Base64 encoding may use symbols that can’t be used in a URL we must URL encode the KeyID value and then URL decode the value on the server. Failure to do this may cause unexpected behavior when attempting to generate a license. The “MSNetObj.GetLicenseFromURL” calls our license acquisition script “DRMPredeliverLicense.aspx” and automatically posts challenge data to it along with our KeyID.

In “DRMPredeliverLicense.aspx” we first need to make sure to flush our response. If we forget to flush the response buffer, our license delivery will fail. This is because ASP.net will try to generate an HTML page for us. This is usually a good thing but in this case we only want to send back the license data. If we send back anything other than the license data the license storage will fail on the client. This is a very common mistake when predelivering licenses via “GetLicenseFromURL”. Once we clear the response buffer we URL decode our KeyID value and assign our “challenge” data to a new “challenge object”. We then deal with our revocation information (certificate revocation list - CRL). We store the revocation information in a local variable for use later. We then setup our “keys object” and our “rights object”. In this case we are allowing the user to play on a PC and allow them to play the content three times. Once we have our rights setup we setup our “license generator object”. After that we get a list of supported CRLs and store them in a local variable. The supported CRLs are used for various DRM enabled devices. We then check our individualization version again. It is very important to do this again even though we already did it in the “DRMCheckVersion.htm” script. This is because due to our configuration it may be possible for an attacker to post to this page directly bypassing the validation script. If this version check fails we throw an error. After the check we setup our “response object” and add the CRL data to the license. We then get the XML license blob and write it to the response stream. It is very important that we only write out the license response data. If we write any data other than the license response we run the risk of corruption the license and causing a failure on the client. Finally we must end the response. We cannot allow the ASP.net subsystem to exit normally. If the subsystem exits normally we run the risk of corruption the license. The license is then sent back to the client. The “MSNetObj.GetLicenseFromURL” call automatically stores the license data for us in the DRM secure store. We should now have a license and be able to play the content.

If you get an error on the client when delivering the license, something to the effect “the license could not be stored, contact Microsoft technical support for help”, you know that you have corrupted the license in some way. Common things that cause the license to become corrupted are: not flushing the response buffer so that only the license data is sent, having a malformed KeyID or using incorrect server keys.