<--- larley.dev 1759563670 ----------------------------------------------------
I've finally set up my own, self-hosted PlayReady License Server. Contact me on Discord if you encounter any problems. The server provides two functionalities: 1 - License Acquisition ----------------------- Accessible via: https://playready.larley.dev/RightsManager/AcquireLicense It can act in 3 different ways: 1) If no URL parameters are supplied, the server will act as a drop-in replacement for the official PlayReady license server (test.playready.microsoft.com) by generating appropriate content keys for the key IDs provided in the WRMHEADER using the PlayReady Test Server Key Seed "XVBovsmzhP9gRIZxWfFta3VVRPzVEWmJsazEJ46I". 2) The license generated by the key IDs specified can be modified by providing an "adjustment" URL parameter set to a Base64-URL encoded JSON configuration. Syntax is given at the end. 3) As soon as the "override" URL parameter is supplied, the WRMHEADER's contents are completely ignored and custom content keys can be specified in a Base64-URL encoded JSON configuration. Note that a License Challenge is still required to get the binding key for the license. JSON Syntax ----------- Both configurations "adjustment" and "override" can contain the same fields, but located in different parts of the JSON. These default fields include: { // Minimum Security Level "security_level": 150, // Type of Content Key // Supported values: "AES128BIT_CTR", "AES128BIT_CBC" "content_key_type": "AES128BIT_CTR", // Type of License // Supported values: "PERSISTENT", NON_PERSISTENT" "license_type": "NON_PERSISTENT", // See: microsoft.media.drm.advancedlicense.realtimeexpiration "real_time_expiration": false, // The date and time before which the license is not valid // See: microsoft.media.drm.advancedlicense.begindate "begin_date": 1759200000, // The date and time after which the license is not valid // See: microsoft.media.drm.advancedlicense.expirationdate "expiration_date": 1759200000, // The date after which the license may be permanently deleted // from the PlayReady license store on the client // See: microsoft.media.drm.medialicense.removaldate "removal_date": 1759200000, // DEPRECATED as of PK v3.0 // Specifies the time period in seconds during which protected // content can be played on a client after its clock becomes unset // See: microsoft.media.drm.medialicense.graceperiod "grace_period": 10, // The metering identifier (UUID) for the metering aggregation service // that meters this content // See: microsoft.media.drm.medialicense.meteringid "metering_id": "b5b94d9b-2988-4cbd-bf0f-105636f8e1c8", // The number of seconds until the license expires after the client // first plays the content // See: microsoft.media.drm.playright.firstplayexpiration "first_play_expiration": 120, // Minimum Analog Television Output Protection Level // See: learn.microsoft.com/playready/overview/output-protection-levels "avopl": 100, // Minimum Compressed Digital Video Output Protection Level // See: learn.microsoft.com/playready/overview/output-protection-levels "cdvopl": 400, // Minimum Uncompressed Digital Video Output Protection Level // See: learn.microsoft.com/playready/overview/output-protection-levels "udvopl": 100, // Minimum Compressed Digital Audio Output Protection Level // See: learn.microsoft.com/playready/overview/output-protection-levels "cdaopl": 100, // Minimum Uncompressed Digital Audio Output Protection Level // See: learn.microsoft.com/playready/overview/output-protection-levels "udaopl": 100, // Explicit Analog Video Output Protection // See: Compliance Rules 6.5 "avop": ["760ae755-682a-41e0-b1b3-dcdf836a7306", "AAAAAQ=="], // Explicit Digital Audio Output Protection // See: Compliance Rules 6.4 "daop": ["6d5cfa59-c250-4426-930e-fac72c8fcfa6", "AAAAAQ=="], // Explicit Digital Video Output Protection // See: Compliance Rules 6.6 "dvop": ["abb2c6f1-e663-4625-a945-972d17b231e7", "AAAAAQ=="], // Play Enablers // See: Compliance Rules "play_enablers": ["786627d8-c2a6-44be-8f88-08ae255b01a7"] } For the adjustment config, these values are located in the root of the JSON, along with other values that are specific to the adjustment config. { // Override the Key Seed used to generate content keys // 30 bytes, Base64 encoded "custom_key_seed": "XVBovsmzhP9gRIZxWfFta3VVRPzVEWmJsazEJ46I", // default values go here... } The override model contains a list of licenses, each with their own key ID and content key. Because of this, the default config values are located in each license. { "licenses": [ { // Base64 encoded big-endian key ID "key_id": "b2Ua4dvkRDS8tGkNFWTEHA==", // Base64 encoded content key "content_key": "iNqFKuT6Lh42rrLVyUmXsQ==", // default values go here... }, { // second license... } ] } 2 - Client Information ---------------------- Accessible via: https://playready.larley.dev/RightsManager/ClientInfo This endpoint provides some insights into the client certificate and Tee/Ree features provided by the client in the encrypted challenge payload. Simply send a POST request to this endpoint containing a normal liense challenge and the server will respond in XML Additional information about the server --------------------------------------- Revocation Information ---------------------- The server refreshes its revocation information every hour and the current version is present in the <CustomData> field of every license response. Error responses --------------- The server returns very detailed error messages about why an opration might have failed. This can be very useful when developing PlayReady clients for troubleshooting problems. A general response is located below (namespaces removed): <soap:Envelope> <soap:Body> <soap:Fault> <faultcode>soap:Server</faultcode> <faultstring>ERROR MESSAGE</faultstring> <faultactor>ENDPOINT</faultactor> <detail> <Exception> <StatusCode>STATUS CODE</StatusCode> </Exception> </detail> </soap:Fault> </soap:Body> </soap:Envelope> Python examples --------------- Normal license acquisition: response = requests.post( url="https://playready.larley.dev/RightsManager/AcquireLicense", data=request ) License adjustment example (limiting devices to SL3000): response = requests.post( url="https://playready.larley.dev/RightsManager/AcquireLicense", params={ "adjustment": base64.urlsafe_b64encode(json.dumps({ "security_level": 3000 }).encode()) }, data=request ) License override example (custom content key and key id): response = requests.post( url="https://playready.larley.dev/RightsManager/AcquireLicense", params={ "override": base64.urlsafe_b64encode(json.dumps({ "licenses": [{ "key_id": "b2Ua4dvkRDS8tGkNFWTEHA==", "content_key": "JmkiPT3oQ3-Y_7Qp7rpOuQ==" }] }).encode()) }, data=request ) Client info example: response = requests.post( url="https://playready.larley.dev/RightsManager/ClientInfo", data=request )