Browse Source

Initial commit of saved code

master
Joshua Ashton 11 months ago
commit
8cad4e039d
  1. 1
      .gitignore
  2. 21
      LICENSE
  3. 41
      README.md
  4. 212
      content_key_decryption.js
  5. 26
      content_script.js
  6. BIN
      docs/WidevineModularDRMSecurityIntegrationGuideforCENC.pdf
  7. BIN
      docs/Widevine_DRM_Architecture_Overview.pdf
  8. 418
      eme_interception.js
  9. 43
      lib/cryptojs-aes_0.2.0.min.js
  10. 1
      lib/pbf.3.0.5.min.js
  11. 549
      license_protocol.proto
  12. 29
      manifest.json
  13. 890
      protobuf-generated/license_protocol.proto.js

1
.gitignore

@ -0,0 +1 @@
.vscode/*

21
LICENSE

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Tomer
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

41
README.md

@ -0,0 +1,41 @@
# Widevine L3 Decryptor
[Widevine](https://www.widevine.com/solutions/widevine-drm) is a Google-owned DRM system that's in use by many popular streaming services (Netflix, Spotify, etc.) to prevent media content from being downloaded.
But Widevine's least secure security level, L3, as used in most browsers and PCs, is implemented 100% in software (i.e no hardware TEEs), thereby making it reversible and bypassable.
This Chrome extension demonstrates how it's possible to bypass Widevine DRM by hijacking calls to the browser's [Encrypted Media Extensions (EME)](https://www.html5rocks.com/en/tutorials/eme/basics) and decrypting all Widevine content keys transferred - effectively turning it into a clearkey DRM.
## Usage
To see this concept in action, just load the extension in Developer Mode and browse to any website that plays Widevine-protected content, such as https://bitmovin.com/demos/drm _[Update: link got broken?]_.
Keys will be logged in plaintext to the javascript console.
e.g:
```
WidevineDecryptor: Found key: 100b6c20940f779a4589152b57d2dacb (KID=eb676abbcb345e96bbcf616630f1a3da)
```
Decrypting the media itself is then just a matter of using a tool that can decrypt MPEG-CENC streams, like `ffmpeg`.
e.g:
```
ffmpeg -decryption_key 100b6c20940f779a4589152b57d2dacb -i encrypted_media.mp4 -codec copy decrypted_media.mp4
```
**NOTE**: The extension currently supports the Windows platform only.
## How
In the context of browsers the actual decryption of the media is usually done inside a proprietary binary (`widevinecdm.dll`, known as the Content Decryption Module or CDM) only after receiving the license from a license server with an encrypted key in it.
This binary is usually heavily obfuscated and makes use of third-party solutions that claim to offer software "protection" such as [Arxan](https://digital.ai/application-protection) or [Whitecryption](https://www.intertrust.com/products/application-shielding).
Some reversing job on that binary can then be done to extract the secret keys and mimic the key decryption algorithm from the license response.
## Why
This PoC was done to further show that code obfuscation, anti-debugging tricks, whitebox cryptography algorithms and other methods of security-by-obscurity will eventually by defeated anyway, and are, in a way, pointless.
This is **NOT** intended for copyright infringement or encouraging piracy.
## Legal Disclaimer
This is for educational purposes only. Downloading copyrighted materials from streaming services may violate their Terms of Service. **Use at your own risk.**

212
content_key_decryption.js

@ -0,0 +1,212 @@
/*
This is where the magic happens
*/
var WidevineCrypto = {};
(function() {
// The public 2048-bit RSA key Widevine uses for Chrome devices in L3, on Windows
WidevineCrypto.chromeRSAPublicKey =
`-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtdHcRBiDWWxdJyKDLTPO9OTapumVnW+9g6k3RSflM0CESFEufZUJGC73UKe9e+u789HVZT04pB5or3WB0XOx
aOibJklLBkd7Yfn1OndVrenMKTE1F4/6jg5rmwyv4qFQ1u8M/ThZUrAgb8pTmKfb9vrv1V8AApwVzcQg3s48eESnKjBU99Vk8alPTjPSfOgoTDluGxQONWiwCaMwftNs
YrOzlde+V3UOb5FVzPcrOmaERfyujV3h4sHGRbTCsqYVwMalO7hmNmtemwt0xBuf5Juia7t1scuJypQ8lI1iEsB+JZVo3Uovfa9nNX0gl5TAq1tAh6M55/ttpWAirWHv
CQIDAQAB
-----END PUBLIC KEY-----`;
// The private 2048-bit RSA key Widevine uses for authenticating Chrome devices in L3, on Windows
// Extracted by applying some mathematical tricks to Arxan's white-box algorithm
WidevineCrypto.chromeRSAPrivateKey =
`-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC10dxEGINZbF0nIoMtM8705Nqm6ZWdb72DqTdFJ+UzQIRIUS59lQkYLvdQp71767vz0dVlPTikHmiv
dYHRc7Fo6JsmSUsGR3th+fU6d1Wt6cwpMTUXj/qODmubDK/ioVDW7wz9OFlSsCBvylOYp9v2+u/VXwACnBXNxCDezjx4RKcqMFT31WTxqU9OM9J86ChMOW4bFA41aLAJ
ozB+02xis7OV175XdQ5vkVXM9ys6ZoRF/K6NXeHiwcZFtMKyphXAxqU7uGY2a16bC3TEG5/km6Jru3Wxy4nKlDyUjWISwH4llWjdSi99r2c1fSCXlMCrW0CHoznn+22l
YCKtYe8JAgMBAAECggEAGOPDJvFCHd43PFG9qlTyylR/2CSWzigLRfhGsClfd24oDaxLVHav+YcIZRqpVkr1flGlyEeittjQ1OAdptoTGbzp7EpRQmlLqyRoHRpT+MxO
Hf91+KVFk+fGdEG+3CPgKKQt34Y0uByTPCpy2i10b7F3Xnq0Sicq1vG33DhYT9A/DRIjYr8Y0AVovq0VDjWqA1FW5OO9p7vky6e+PDMjSHucQ+uaLzVZSc7vWOh0tH5M
0GVk17YpBiB/iTpw4zBUIcaneQX3eaIfSCDHK0SCD6IRF7kl+uORzvWqiWlGzpdG2B96uyP4hd3WoPcZntM79PKm4dAotdgmalbueFJfpwKBgQDUy0EyA9Fq0aPF4LID
HqDPduIm4hEAZf6sQLd8Fe6ywM4p9KOEVx7YPaFxQHFSgIiWXswildPJl8Cg5cM2EyMU1tdn5xaR4VIDk8e2JEDfhPtaWskpJp2rU2wHvAXOeAES7UFMrkhKVqqVOdbo
IhlLdcYp5KxiJ3mwINSSO94ShwKBgQDavJvF+c8AINfCaMocUX0knXz+xCwdP430GoPQCHa1rUj5bZ3qn3XMwSWa57J4x3pVhYmgJv4jpEK+LBULFezNLV5N4C7vH63a
Zo4OF7IUedFBS5B508yAq7RiPhN2VOC8LRdDh5oqnFufjafF82y9d+/czCrVIG43D+KO2j4F7wKBgDg/HZWF0tYEYeDNGuCeOO19xBt5B/tt+lo3pQhkl7qiIhyO8KXr
jVilOcZAvXOMTA5LMnQ13ExeE2m0MdxaRJyeiUOKnrmisFYHuvNXM9qhQPtKIgABmA2QOG728SX5LHd/RRJqwur7a42UQ00Krlr235F1Q2eSfaTjmKyqrHGDAoGAOTrd
2ueoZFUzfnciYlRj1L+r45B6JlDpmDOTx0tfm9sx26j1h1yfWqoyZ5w1kupGNLgSsSdimPqyR8WK3/KlmW1EXkXIoeH8/8aTZlaGzlqtCFN4ApgKyqOiN44cU3qTrkhx
7MY+7OUqB83tVpqBGfWWeYOltUud6qQqV8v8LFsCgYEAnOq+Ls83CaHIWCjpVfiWC+R7mqW+ql1OGtoaajtA4AzhXzX8HIXpYjupPBlXlQ1FFfPem6jwa1UTZf8CpIb8
pPULAN9ZRrxG8V+bvkZWVREPTZj7xPCwPaZHNKoAmi3Dbv7S5SEYDbBX/NyPCLE4sj/AgTPbUsUtaiw5TvrPsFE=
-----END PRIVATE KEY-----`;
WidevineCrypto.initializeKeys = async function()
{
// load the device RSA keys for various purposes
this.publicKeyEncrypt = await crypto.subtle.importKey('spki', PEM2Binary(this.chromeRSAPublicKey), {name: 'RSA-OAEP', hash: { name: 'SHA-1' },}, true, ['encrypt']);
this.publicKeyVerify = await crypto.subtle.importKey('spki', PEM2Binary(this.chromeRSAPublicKey), {name: 'RSA-PSS', hash: { name: 'SHA-1' },}, true, ['verify']);
this.privateKeyDecrypt = await crypto.subtle.importKey('pkcs8', PEM2Binary(this.chromeRSAPrivateKey), {name: 'RSA-OAEP', hash: { name: 'SHA-1' },}, true, ['decrypt']);
var isRSAGood = await isRSAConsistent(this.publicKeyEncrypt, this.privateKeyDecrypt);
if (!isRSAGood)
{
throw "Can't verify RSA keys consistency; This means the public key does not match the private key!";
}
this.keysInitialized = true;
}
WidevineCrypto.decryptContentKey = async function(licenseRequest, licenseResponse)
{
licenseRequest = SignedMessage.read(new Pbf(licenseRequest));
licenseResponse = SignedMessage.read(new Pbf(licenseResponse));
if (licenseRequest.type != SignedMessage.MessageType.LICENSE_REQUEST.value) return;
license = License.read(new Pbf(licenseResponse.msg));
if (!this.keysInitialized) await this.initializeKeys();
// make sure the signature in the license request validates under the private key
var signatureVerified = await window.crypto.subtle.verify({name: "RSA-PSS", saltLength: 20,}, this.publicKeyVerify,
licenseRequest.signature, licenseRequest.msg)
if (!signatureVerified)
{
console.log("Can't verify license request signature; either the platform is wrong or the key has changed!");
return null;
}
// decrypt the session key
var sessionKey = await crypto.subtle.decrypt({name: "RSA-OAEP"}, this.privateKeyDecrypt, licenseResponse.session_key);
// calculate context_enc
var encoder = new TextEncoder();
var keySize = 128;
var context_enc = concatBuffers([[0x01], encoder.encode("ENCRYPTION"), [0x00], licenseRequest.msg, intToBuffer(keySize)]);
// calculate encrypt_key using CMAC
var encryptKey = wordToByteArray(
CryptoJS.CMAC(arrayToWordArray(new Uint8Array(sessionKey)),
arrayToWordArray(new Uint8Array(context_enc))).words);
// iterate the keys we got to find those we want to decrypt (the content key(s))
var contentKeys = []
for (currentKey of license.key)
{
if (currentKey.type != License.KeyContainer.KeyType.CONTENT.value) continue;
var keyId = currentKey.id;
var keyData = currentKey.key.slice(0, 16);
var keyIv = currentKey.iv.slice(0, 16);
// finally decrypt the content key
var decryptedKey = wordToByteArray(
CryptoJS.AES.decrypt({ ciphertext: arrayToWordArray(keyData) }, arrayToWordArray(encryptKey), { iv: arrayToWordArray(keyIv) }).words);
contentKeys.push(decryptedKey);
console.log("WidevineDecryptor: Found key: " + toHexString(decryptedKey) + " (KID=" + toHexString(keyId) + ")");
}
return contentKeys[0];
}
//
// Helper functions
//
async function isRSAConsistent(publicKey, privateKey)
{
// See if the data is correctly decrypted after encryption
var testData = new Uint8Array([0x41, 0x42, 0x43, 0x44]);
var encryptedData = await crypto.subtle.encrypt({name: "RSA-OAEP"}, publicKey, testData);
var testDecryptedData = await crypto.subtle.decrypt({name: "RSA-OAEP"}, privateKey, encryptedData);
return areBuffersEqual(testData, testDecryptedData);
}
function areBuffersEqual(buf1, buf2)
{
if (buf1.byteLength != buf2.byteLength) return false;
var dv1 = new Int8Array(buf1);
var dv2 = new Int8Array(buf2);
for (var i = 0 ; i != buf1.byteLength ; i++)
{
if (dv1[i] != dv2[i]) return false;
}
return true;
}
function concatBuffers(arrays)
{
// Get the total length of all arrays.
let length = 0;
arrays.forEach(item => {
length += item.length;
});
// Create a new array with total length and merge all source arrays.
let mergedArray = new Uint8Array(length);
let offset = 0;
arrays.forEach(item => {
mergedArray.set(new Uint8Array(item), offset);
offset += item.length;
});
return mergedArray;
}
// CryptoJS format to byte array
function wordToByteArray(wordArray)
{
var byteArray = [], word, i, j;
for (i = 0; i < wordArray.length; ++i) {
word = wordArray[i];
for (j = 3; j >= 0; --j) {
byteArray.push((word >> 8 * j) & 0xFF);
}
}
return byteArray;
}
// byte array to CryptoJS format
function arrayToWordArray(u8Array)
{
var words = [], i = 0, len = u8Array.length;
while (i < len) {
words.push(
(u8Array[i++] << 24) |
(u8Array[i++] << 16) |
(u8Array[i++] << 8) |
(u8Array[i++])
);
}
return {
sigBytes: len,
words: words
};
}
const toHexString = bytes => bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), '');
const intToBuffer = num =>
{
let b = new ArrayBuffer(4);
new DataView(b).setUint32(0, num);
return Array.from(new Uint8Array(b));
}
function PEM2Binary(pem)
{
var encoded = '';
var lines = pem.split('\n');
for (var i = 0; i < lines.length; i++) {
if (lines[i].indexOf('-----') < 0) {
encoded += lines[i];
}
}
var byteStr = atob(encoded);
var bytes = new Uint8Array(byteStr.length);
for (var i = 0; i < byteStr.length; i++) {
bytes[i] = byteStr.charCodeAt(i);
}
return bytes.buffer;
}
}());

26
content_script.js

@ -0,0 +1,26 @@
injectScripts();
async function injectScripts()
{
await injectScript('lib/pbf.3.0.5.min.js');
await injectScript('lib/cryptojs-aes_0.2.0.min.js');
await injectScript('protobuf-generated/license_protocol.proto.js');
await injectScript('content_key_decryption.js');
await injectScript('eme_interception.js');
}
function injectScript(scriptName)
{
return new Promise(function(resolve, reject)
{
var s = document.createElement('script');
s.src = chrome.extension.getURL(scriptName);
s.onload = function() {
this.parentNode.removeChild(this);
resolve(true);
};
(document.head||document.documentElement).appendChild(s);
});
}

BIN
docs/WidevineModularDRMSecurityIntegrationGuideforCENC.pdf

Binary file not shown.

BIN
docs/Widevine_DRM_Architecture_Overview.pdf

Binary file not shown.

418
eme_interception.js

@ -0,0 +1,418 @@
/**
* Hooks EME calls and forwards them for analysis and decryption.
*
* Most of the code here was borrowed from https://github.com/google/eme_logger/blob/master/eme_listeners.js
*/
var lastReceivedLicenseRequest = null;
var lastReceivedLicenseResponse = null;
/** Set up the EME listeners. */
function startEMEInterception()
{
var listener = new EmeInterception();
listener.setUpListeners();
}
/**
* Gets called whenever an EME method is getting called or an EME event fires
*/
EmeInterception.onOperation = function(operationType, args)
{
if (operationType == "GenerateRequestCall")
{
// got initData
// console.log(args);
}
else if (operationType == "MessageEvent")
{
var licenseRequest = args.message;
lastReceivedLicenseRequest = licenseRequest;
}
else if (operationType == "UpdateCall")
{
var licenseResponse = args[0];
lastReceivedLicenseResponse = licenseResponse;
// OK, let's try to decrypt it, assuming the response correlates to the request
WidevineCrypto.decryptContentKey(lastReceivedLicenseRequest, lastReceivedLicenseResponse);
}
};
/**
* Manager for EME event and method listeners.
* @constructor
*/
function EmeInterception()
{
this.unprefixedEmeEnabled = Navigator.prototype.requestMediaKeySystemAccess ? true : false;
this.prefixedEmeEnabled = HTMLMediaElement.prototype.webkitGenerateKeyRequest ? true : false;
}
/**
* The number of types of HTML Media Elements to track.
* @const {number}
*/
EmeInterception.NUM_MEDIA_ELEMENT_TYPES = 3;
/**
* Sets up EME listeners for whichever type of EME is enabled.
*/
EmeInterception.prototype.setUpListeners = function()
{
if (!this.unprefixedEmeEnabled && !this.prefixedEmeEnabled) {
// EME is not enabled, just ignore
return;
}
if (this.unprefixedEmeEnabled) {
this.addListenersToNavigator_();
}
if (this.prefixedEmeEnabled) {
// Prefixed EME is enabled
}
this.addListenersToAllEmeElements_();
};
/**
* Adds listeners to the EME methods on the Navigator object.
* @private
*/
EmeInterception.prototype.addListenersToNavigator_ = function()
{
if (navigator.listenersAdded_)
return;
var originalRequestMediaKeySystemAccessFn = EmeInterception.extendEmeMethod(
navigator,
navigator.requestMediaKeySystemAccess,
"RequestMediaKeySystemAccessCall");
navigator.requestMediaKeySystemAccess = function()
{
var options = arguments[1];
// slice "It is recommended that a robustness level be specified" warning
var modifiedArguments = arguments;
var modifiedOptions = EmeInterception.addRobustnessLevelIfNeeded(options);
modifiedArguments[1] = modifiedOptions;
var result = originalRequestMediaKeySystemAccessFn.apply(null, modifiedArguments);
// Attach listeners to returned MediaKeySystemAccess object
return result.then(function(mediaKeySystemAccess)
{
this.addListenersToMediaKeySystemAccess_(mediaKeySystemAccess);
return Promise.resolve(mediaKeySystemAccess);
}.bind(this));
}.bind(this);
navigator.listenersAdded_ = true;
};
/**
* Adds listeners to the EME methods on a MediaKeySystemAccess object.
* @param {MediaKeySystemAccess} mediaKeySystemAccess A MediaKeySystemAccess
* object to add listeners to.
* @private
*/
EmeInterception.prototype.addListenersToMediaKeySystemAccess_ = function(mediaKeySystemAccess)
{
if (mediaKeySystemAccess.listenersAdded_) {
return;
}
mediaKeySystemAccess.originalGetConfiguration = mediaKeySystemAccess.getConfiguration;
mediaKeySystemAccess.getConfiguration = EmeInterception.extendEmeMethod(
mediaKeySystemAccess,
mediaKeySystemAccess.getConfiguration,
"GetConfigurationCall");
var originalCreateMediaKeysFn = EmeInterception.extendEmeMethod(
mediaKeySystemAccess,
mediaKeySystemAccess.createMediaKeys,
"CreateMediaKeysCall");
mediaKeySystemAccess.createMediaKeys = function()
{
var result = originalCreateMediaKeysFn.apply(null, arguments);
// Attach listeners to returned MediaKeys object
return result.then(function(mediaKeys) {
mediaKeys.keySystem_ = mediaKeySystemAccess.keySystem;
this.addListenersToMediaKeys_(mediaKeys);
return Promise.resolve(mediaKeys);
}.bind(this));
}.bind(this);
mediaKeySystemAccess.listenersAdded_ = true;
};
/**
* Adds listeners to the EME methods on a MediaKeys object.
* @param {MediaKeys} mediaKeys A MediaKeys object to add listeners to.
* @private
*/
EmeInterception.prototype.addListenersToMediaKeys_ = function(mediaKeys)
{
if (mediaKeys.listenersAdded_) {
return;
}
var originalCreateSessionFn = EmeInterception.extendEmeMethod(mediaKeys, mediaKeys.createSession, "CreateSessionCall");
mediaKeys.createSession = function()
{
var result = originalCreateSessionFn.apply(null, arguments);
result.keySystem_ = mediaKeys.keySystem_;
// Attach listeners to returned MediaKeySession object
this.addListenersToMediaKeySession_(result);
return result;
}.bind(this);
mediaKeys.setServerCertificate = EmeInterception.extendEmeMethod(mediaKeys, mediaKeys.setServerCertificate, "SetServerCertificateCall");
mediaKeys.listenersAdded_ = true;
};
/** Adds listeners to the EME methods and events on a MediaKeySession object.
* @param {MediaKeySession} session A MediaKeySession object to add
* listeners to.
* @private
*/
EmeInterception.prototype.addListenersToMediaKeySession_ = function(session)
{
if (session.listenersAdded_) {
return;
}
session.generateRequest = EmeInterception.extendEmeMethod(session,session.generateRequest, "GenerateRequestCall");
session.load = EmeInterception.extendEmeMethod(session, session.load, "LoadCall");
session.update = EmeInterception.extendEmeMethod(session,session.update, "UpdateCall");
session.close = EmeInterception.extendEmeMethod(session, session.close, "CloseCall");
session.remove = EmeInterception.extendEmeMethod(session, session.remove, "RemoveCall");
session.addEventListener('message', function(e)
{
e.keySystem = session.keySystem_;
EmeInterception.interceptEvent("MessageEvent", e);
});
session.addEventListener('keystatuseschange', EmeInterception.interceptEvent.bind(null, "KeyStatusesChangeEvent"));
session.listenersAdded_ = true;
};
/**
* Adds listeners to all currently created media elements (audio, video) and sets up a
* mutation-summary observer to add listeners to any newly created media
* elements.
* @private
*/
EmeInterception.prototype.addListenersToAllEmeElements_ = function()
{
this.addEmeInterceptionToInitialMediaElements_();
// TODO: Use MutationObserver directry
// var observer = new MutationSummary({
// callback: function(summaries) {
// applyListeners(summaries);
// },
// queries: [{element: 'video'}, {element: 'audio'}, {element: 'media'}]
// });
// var applyListeners = function(summaries) {
// for (var i = 0; i < EmeInterception.NUM_MEDIA_ELEMENT_TYPES; i++) {
// var elements = summaries[i];
// elements.added.forEach(function(element) {
// this.addListenersToEmeElement_(element, true);
// }.bind(this));
// }
// }.bind(this);
};
/**
* Adds listeners to the EME elements currently in the document.
* @private
*/
EmeInterception.prototype.addEmeInterceptionToInitialMediaElements_ = function()
{
var audioElements = document.getElementsByTagName('audio');
for (var i = 0; i < audioElements.length; ++i) {
this.addListenersToEmeElement_(audioElements[i], false);
}
var videoElements = document.getElementsByTagName('video');
for (var i = 0; i < videoElements.length; ++i) {
this.addListenersToEmeElement_(videoElements[i], false);
}
var mediaElements = document.getElementsByTagName('media');
for (var i = 0; i < mediaElements.length; ++i) {
this.addListenersToEmeElement_(mediaElements[i], false);
}
};
/**
* Adds method and event listeners to media element.
* @param {HTMLMediaElement} element A HTMLMedia element to add listeners to.
* @private
*/
EmeInterception.prototype.addListenersToEmeElement_ = function(element)
{
this.addEmeEventListeners_(element);
this.addEmeMethodListeners_(element);
console.info('EME listeners successfully added to:', element);
};
/**
* Adds event listeners to a media element.
* @param {HTMLMediaElement} element A HTMLMedia element to add listeners to.
* @private
*/
EmeInterception.prototype.addEmeEventListeners_ = function(element)
{
if (element.eventListenersAdded_) {
return;
}
if (this.prefixedEmeEnabled)
{
element.addEventListener('webkitneedkey', EmeInterception.interceptEvent.bind(null, "NeedKeyEvent"));
element.addEventListener('webkitkeymessage', EmeInterception.interceptEvent.bind(null, "KeyMessageEvent"));
element.addEventListener('webkitkeyadded', EmeInterception.interceptEvent.bind(null, "KeyAddedEvent"));
element.addEventListener('webkitkeyerror', EmeInterception.interceptEvent.bind(null, "KeyErrorEvent"));
}
element.addEventListener('encrypted', EmeInterception.interceptEvent.bind(null, "EncryptedEvent"));
element.addEventListener('play', EmeInterception.interceptEvent.bind(null, "PlayEvent"));
element.addEventListener('error', function(e) {
console.error('Error Event');
EmeInterception.interceptEvent("ErrorEvent", e);
});
element.eventListenersAdded_ = true;
};
/**
* Adds method listeners to a media element.
* @param {HTMLMediaElement} element A HTMLMedia element to add listeners to.
* @private
*/
EmeInterception.prototype.addEmeMethodListeners_ = function(element)
{
if (element.methodListenersAdded_) {
return;
}
element.play = EmeInterception.extendEmeMethod(element, element.play, "PlayCall");
if (this.prefixedEmeEnabled) {
element.canPlayType = EmeInterception.extendEmeMethod(element, element.canPlayType, "CanPlayTypeCall");
element.webkitGenerateKeyRequest = EmeInterception.extendEmeMethod(element, element.webkitGenerateKeyRequest, "GenerateKeyRequestCall");
element.webkitAddKey = EmeInterception.extendEmeMethod(element, element.webkitAddKey, "AddKeyCall");
element.webkitCancelKeyRequest = EmeInterception.extendEmeMethod(element, element.webkitCancelKeyRequest, "CancelKeyRequestCall");
}
if (this.unprefixedEmeEnabled) {
element.setMediaKeys = EmeInterception.extendEmeMethod(element, element.setMediaKeys, "SetMediaKeysCall");
}
element.methodListenersAdded_ = true;
};
/**
* Creates a wrapper function that logs calls to the given method.
* @param {!Object} element An element or object whose function
* call will be logged.
* @param {!Function} originalFn The function to log.
* @param {!Function} type The constructor for a logger class that will
* be instantiated to log the originalFn call.
* @return {!Function} The new version, with logging, of orginalFn.
*/
EmeInterception.extendEmeMethod = function(element, originalFn, type)
{
return function()
{
try
{
var result = originalFn.apply(element, arguments);
var args = [].slice.call(arguments);
EmeInterception.interceptCall(type, args, result, element);
}
catch (e)
{
console.error(e);
}
return result;
};
};
/**
* Intercepts a method call to the console and a separate frame.
* @param {!Function} constructor The constructor for a logger class that will
* be instantiated to log this call.
* @param {Array} args The arguments this call was made with.
* @param {Object} result The result of this method call.
* @param {!Object} target The element this method was called on.
* @return {!eme.EmeMethodCall} The data that has been logged.
*/
EmeInterception.interceptCall = function(type, args, result, target)
{
EmeInterception.onOperation(type, args);
return args;
};
/**
* Intercepts an event to the console and a separate frame.
* @param {!Function} constructor The constructor for a logger class that will
* be instantiated to log this event.
* @param {!Event} event An EME event.
* @return {!eme.EmeEvent} The data that has been logged.
*/
EmeInterception.interceptEvent = function(type, event)
{
EmeInterception.onOperation(type, event);
return event;
};
EmeInterception.addRobustnessLevelIfNeeded = function(options)
{
for (var i = 0; i < options.length; i++)
{
var option = options[i];
var videoCapabilities = option["videoCapabilities"];
var audioCapabilties = option["audioCapabilities"];
if (videoCapabilities != null)
{
for (var j = 0; j < videoCapabilities.length; j++)
if (videoCapabilities[j]["robustness"] == undefined) videoCapabilities[j]["robustness"] = "SW_SECURE_CRYPTO";
}
if (audioCapabilties != null)
{
for (var j = 0; j < audioCapabilties.length; j++)
if (audioCapabilties[j]["robustness"] == undefined) audioCapabilties[j]["robustness"] = "SW_SECURE_CRYPTO";
}
option["videoCapabilities"] = videoCapabilities;
option["audioCapabilities"] = audioCapabilties;
options[i] = option;
}
return options;
}
startEMEInterception();

43
lib/cryptojs-aes_0.2.0.min.js

File diff suppressed because one or more lines are too long

1
lib/pbf.3.0.5.min.js

File diff suppressed because one or more lines are too long

549
license_protocol.proto

@ -0,0 +1,549 @@
// ----------------------------------------------------------------------------
// license_protocol.proto
// ----------------------------------------------------------------------------
// Copyright 2013 Google Inc. All Rights Reserved.
//
// Description:
// Definitions of the protocol buffer messages used in the Widevine license
// exchange protocol.
syntax = "proto2";
package video_widevine_server.sdk;
// need this if we are using libprotobuf-cpp-2.3.0-lite
option optimize_for = LITE_RUNTIME;
enum LicenseType {
STREAMING = 1;
OFFLINE = 2;
}
// LicenseIdentification is propagated from LicenseRequest to License,
// incrementing version with each iteration.
message LicenseIdentification {
optional bytes request_id = 1;
optional bytes session_id = 2;
optional bytes purchase_id = 3;
optional LicenseType type = 4;
optional int32 version = 5;
optional bytes provider_session_token = 6;
}
message License {
message Policy {
// Indicates that playback of the content is allowed.
optional bool can_play = 1 [default = false];
// Indicates that the license may be persisted to non-volatile
// storage for offline use.
optional bool can_persist = 2 [default = false];
// Indicates that renewal of this license is allowed.
optional bool can_renew = 3 [default = false];
// For the |*duration*| fields, playback must halt when
// license_start_time (seconds since the epoch (UTC)) +
// license_duration_seconds is exceeded. A value of 0
// indicates that there is no limit to the duration.
// Indicates the rental window.
optional int64 rental_duration_seconds = 4 [default = 0];
// Indicates the viewing window, once playback has begun.
optional int64 playback_duration_seconds = 5 [default = 0];
// Indicates the time window for this specific license.
optional int64 license_duration_seconds = 6 [default = 0];
// The |renewal*| fields only apply if |can_renew| is true.
// The window of time, in which playback is allowed to continue while
// renewal is attempted, yet unsuccessful due to backend problems with
// the license server.
optional int64 renewal_recovery_duration_seconds = 7 [default = 0];
// All renewal requests for this license shall be directed to the
// specified URL.
optional string renewal_server_url = 8;
// How many seconds after license_start_time, before renewal is first
// attempted.
optional int64 renewal_delay_seconds = 9 [default = 0];
// Specifies the delay in seconds between subsequent license
// renewal requests, in case of failure.
optional int64 renewal_retry_interval_seconds = 10 [default = 0];
// Indicates that the license shall be sent for renewal when usage is
// started.
optional bool renew_with_usage = 11 [default = false];
// Indicates to client that license renewal and release requests ought to
// include ClientIdentification (client_id).
optional bool renew_with_client_id = 12 [default = false];
}
message KeyContainer {
enum KeyType {
// Exactly one key of this type must appear.
SIGNING = 1;
CONTENT = 2;
KEY_CONTROL = 3;
OPERATOR_SESSION = 4;
}
// The SecurityLevel enumeration allows the server to communicate the level
// of robustness required by the client, in order to use the key.
enum SecurityLevel {
// Software-based whitebox crypto is required.
SW_SECURE_CRYPTO = 1;
// Software crypto and an obfuscated decoder is required.
SW_SECURE_DECODE = 2;
// The key material and crypto operations must be performed within a
// hardware backed trusted execution environment.
HW_SECURE_CRYPTO = 3;
// The crypto and decoding of content must be performed within a hardware
// backed trusted execution environment.
HW_SECURE_DECODE = 4;
// The crypto, decoding and all handling of the media (compressed and
// uncompressed) must be handled within a hardware backed trusted
// execution environment.
HW_SECURE_ALL = 5;
}
message KeyControl {
// If present, the key control must be communicated to the secure
// environment prior to any usage. This message is automatically generated
// by the Widevine License Server SDK.
optional bytes key_control_block = 1;
optional bytes iv = 2;
}
message OutputProtection {
// Indicates whether HDCP is required on digital outputs, and which
// version should be used.
enum HDCP {
HDCP_NONE = 0;
HDCP_V1 = 1;
HDCP_V2 = 2;
HDCP_V2_1 = 3;
HDCP_V2_2 = 4;
HDCP_NO_DIGITAL_OUTPUT = 0xff;
}
optional HDCP hdcp = 1 [default = HDCP_NONE];
// Indicate the CGMS setting to be inserted on analog output.
enum CGMS {
CGMS_NONE = 42;
COPY_FREE = 0;
COPY_ONCE = 2;
COPY_NEVER = 3;
}
optional CGMS cgms_flags = 2 [default = CGMS_NONE];
}
message VideoResolutionConstraint {
// Minimum and maximum video resolutions in the range (height x width).
optional uint32 min_resolution_pixels = 1;
optional uint32 max_resolution_pixels = 2;
// Optional output protection requirements for this range. If not
// specified, the OutputProtection in the KeyContainer applies.
optional OutputProtection required_protection = 3;
}
message OperatorSessionKeyPermissions {
// Permissions/key usage flags for operator service keys
// (type = OPERATOR_SESSION).
optional bool allow_encrypt = 1 [default = false];
optional bool allow_decrypt = 2 [default = false];
optional bool allow_sign = 3 [default = false];
optional bool allow_signature_verify = 4 [default = false];
}
optional bytes id = 1;
optional bytes iv = 2;
optional bytes key = 3;
optional KeyType type = 4;
optional SecurityLevel level = 5 [default = SW_SECURE_CRYPTO];
optional OutputProtection required_protection = 6;
// NOTE: Use of requested_protection is not recommended as it is only
// supported on a small number of platforms.
optional OutputProtection requested_protection = 7;
optional KeyControl key_control = 8;
optional OperatorSessionKeyPermissions operator_session_key_permissions = 9;
// Optional video resolution constraints. If the video resolution of the
// content being decrypted/decoded falls within one of the specified ranges,
// the optional required_protections may be applied. Otherwise an error will
// be reported.
// NOTE: Use of this feature is not recommended, as it is only supported on
// a small number of platforms.
repeated VideoResolutionConstraint video_resolution_constraints = 10;
// Optional flag to indicate the key must only be used if the client
// supports anti rollback of the user table. Content provider can query the
// client capabilities to determine if the client support this feature.
optional bool anti_rollback_usage_table = 11 [default = false];
}
optional LicenseIdentification id = 1;
optional Policy policy = 2;
repeated KeyContainer key = 3;
optional int64 license_start_time = 4;
optional bool remote_attestation_verified = 5 [default = false];
// Client token generated by the content provider. Optional.
optional bytes provider_client_token = 6;
}
enum ProtocolVersion {
VERSION_2_0 = 20;
VERSION_2_1 = 21;
}
message LicenseRequest {
message ContentIdentification {
message CENC {
repeated bytes pssh = 1;
optional LicenseType license_type = 2;
optional bytes request_id = 3; // Opaque, client-specified.
}
message WebM {
optional bytes header = 1;
optional LicenseType license_type = 2;
optional bytes request_id = 3; // Opaque, client-specified.
}
message ExistingLicense {
optional LicenseIdentification license_id = 1;
optional int64 seconds_since_started = 2;
optional int64 seconds_since_last_played = 3;
optional bytes session_usage_table_entry = 4;
}
// Exactly one of these must be present.
optional CENC cenc_id = 1;
optional WebM webm_id = 2;
optional ExistingLicense license = 3;
}
enum RequestType {
NEW = 1;
RENEWAL = 2;
RELEASE = 3;
}
// The client_id provides information authenticating the calling device. It
// contains the Widevine keybox token that was installed on the device at the
// factory. This field or encrypted_client_id below is required for a valid
// license request, but both should never be present in the same request.
optional ClientIdentification client_id = 1;
optional ContentIdentification content_id = 2;
optional RequestType type = 3;
optional int64 request_time = 4;
// Old-style decimal-encoded string key control nonce.
optional bytes key_control_nonce_deprecated = 5;
optional ProtocolVersion protocol_version = 6 [default = VERSION_2_0];
// New-style uint32 key control nonce, please use instead of
// key_control_nonce_deprecated.
optional uint32 key_control_nonce = 7;
// Encrypted ClientIdentification message, used for privacy purposes.
optional EncryptedClientIdentification encrypted_client_id = 8;
}
message LicenseError {
enum Error {
// The device credentials are invalid. The device must re-provision.
INVALID_DEVICE_CERTIFICATE = 1;
// The device credentials have been revoked. Re-provisioning is not
// possible.
REVOKED_DEVICE_CERTIFICATE = 2;
// The service is currently unavailable due to the backend being down
// or similar circumstances.
SERVICE_UNAVAILABLE = 3;
}
optional Error error_code = 1;
}
message RemoteAttestation {
// Encrypted ClientIdentification message containing the device remote
// attestation certificate. Required.
optional EncryptedClientIdentification certificate = 1;
// Bytes of salt which were added to the remote attestation challenge prior to
// signing it. Required.
optional bytes salt = 2;
// Signed remote attestation challenge + salt. Required.
optional bytes signature = 3;
}
message SignedMessage {
enum MessageType {
LICENSE_REQUEST = 1;
LICENSE = 2;
ERROR_RESPONSE = 3;
SERVICE_CERTIFICATE_REQUEST = 4;
SERVICE_CERTIFICATE = 5;
}
optional MessageType type = 1;
optional bytes msg = 2;
optional bytes signature = 3;
optional bytes session_key = 4;
// Remote attestation data which will be present in the initial license
// request for ChromeOS client devices operating in verified mode. Remote
// attestation challenge data is |msg| field above. Optional.
optional RemoteAttestation remote_attestation = 5;
}
// ----------------------------------------------------------------------------
// certificate_provisioning.proto
// ----------------------------------------------------------------------------
// Copyright 2013 Google Inc. All Rights Reserved.
//
// Description:
// Public protocol buffer definitions for Widevine Device Certificate
// Provisioning protocol.
// ProvisioningOptions specifies the type of certificate to specify and
// in the case of X509 certificates, the certificate authority to use.
message ProvisioningOptions {
enum CertificateType {
WIDEVINE_DRM = 0; // Default. The original certificate type.
X509 = 1; // X.509 certificate.
}
optional CertificateType certificate_type = 1;
// It is recommended that the certificate_authority specify the X.509
// Subject of the signing certificate.
optional string certificate_authority = 2;
}
// Provisioning request sent by client devices to provisioning service.
message ProvisioningRequest {
// Device root of trust and other client identification. Required.
optional ClientIdentification client_id = 1;
// Nonce value used to prevent replay attacks. Required.
optional bytes nonce = 2;
// Options for type of certificate to generate. Optional.
optional ProvisioningOptions options = 3;
// Stable identifier, unique for each device + application (or origin).
// Required if doing per-origin provisioning.
optional bytes stable_id = 4;
}
// Provisioning response sent by the provisioning server to client devices.
message ProvisioningResponse {
// AES-128 encrypted device private RSA key. PKCS#1 ASN.1 DER-encoded.
// Required.
optional bytes device_rsa_key = 1;
// Initialization vector used to encrypt device_rsa_key. Required.
optional bytes device_rsa_key_iv = 2;
// Serialized SignedDeviceCertificate. Required.
optional bytes device_certificate = 3;
// Nonce value matching nonce in ProvisioningRequest. Required.
optional bytes nonce = 4;
}
// Serialized ProvisioningRequest or ProvisioningResponse signed with
// The message authentication key.
message SignedProvisioningMessage {
// Serialized ProvisioningRequest or ProvisioningResponse. Required.
optional bytes message = 1;
// HMAC-SHA256 signature of message. Required.
optional bytes signature = 2;
}
// ----------------------------------------------------------------------------
// client_identification.proto
// ----------------------------------------------------------------------------
// Copyright 2013 Google Inc. All Rights Reserved.
//
// Description:
// ClientIdentification messages used by provisioning and license protocols.
// ClientIdentification message used to authenticate the client device.
message ClientIdentification {
enum TokenType {
KEYBOX = 0;
DEVICE_CERTIFICATE = 1;
REMOTE_ATTESTATION_CERTIFICATE = 2;
}
message NameValue {
optional string name = 1;
optional string value = 2;
}
// Capabilities which not all clients may support. Used for the license
// exchange protocol only.
message ClientCapabilities {
enum HdcpVersion {
HDCP_NONE = 0;
HDCP_V1 = 1;
HDCP_V2 = 2;
HDCP_V2_1 = 3;
HDCP_V2_2 = 4;
HDCP_NO_DIGITAL_OUTPUT = 0xff;
}
optional bool client_token = 1 [default = false];
optional bool session_token = 2 [default = false];
optional bool video_resolution_constraints = 3 [default = false];
optional HdcpVersion max_hdcp_version = 4 [default = HDCP_NONE];
optional uint32 oem_crypto_api_version = 5;
optional bool anti_rollback_usage_table = 6 [default = false];
}
// Type of factory-provisioned device root of trust. Optional.
optional TokenType type = 1 [default = KEYBOX];
// Factory-provisioned device root of trust. Required.
optional bytes token = 2;
// Optional client information name/value pairs.
repeated NameValue client_info = 3;
// Client token generated by the content provider. Optional.
optional bytes provider_client_token = 4;
// Number of licenses received by the client to which the token above belongs.
// Only present if client_token is specified.
optional uint32 license_counter = 5;
// List of non-baseline client capabilities.
optional ClientCapabilities client_capabilities = 6;
}
// EncryptedClientIdentification message used to hold ClientIdentification
// messages encrypted for privacy purposes.
message EncryptedClientIdentification {
// Service ID for which the ClientIdentifcation is encrypted (owner of service
// certificate).
optional string service_id = 1;
// Serial number for the service certificate for which ClientIdentification is
// encrypted.
optional bytes service_certificate_serial_number = 2;
// Serialized ClientIdentification message, encrypted with the privacy key using
// AES-128-CBC with PKCS#5 padding.
optional bytes encrypted_client_id = 3;
// Initialization vector needed to decrypt encrypted_client_id.
optional bytes encrypted_client_id_iv = 4;
// AES-128 privacy key, encrytped with the service public public key using
// RSA-OAEP.
optional bytes encrypted_privacy_key = 5;
}
// ----------------------------------------------------------------------------
// device_certificate.proto
// ----------------------------------------------------------------------------
// Copyright 2013 Google Inc. All Rights Reserved.
//
// Description:
// Device certificate and certificate status list format definitions.
// Certificate definition for user devices, intermediate, service, and root
// certificates.
message DeviceCertificate {
enum CertificateType {
ROOT = 0;
INTERMEDIATE = 1;
USER_DEVICE = 2;
SERVICE = 3;
}
// Type of certificate. Required.
optional CertificateType type = 1;
// 128-bit globally unique serial number of certificate.
// Value is 0 for root certificate. Required.
optional bytes serial_number = 2;
// POSIX time, in seconds, when the certificate was created. Required.
optional uint32 creation_time_seconds = 3;
// Device public key. PKCS#1 ASN.1 DER-encoded. Required.
optional bytes public_key = 4;
// Widevine system ID for the device. Required for intermediate and
// user device certificates.
optional uint32 system_id = 5;
// Deprecated field, which used to indicate whether the device was a test
// (non-production) device. The test_device field in ProvisionedDeviceInfo
// below should be observed instead.
optional bool test_device_deprecated = 6 [deprecated = true];
// Service identifier (web origin) for the service which owns the certificate.
// Required for service certificates.
optional string service_id = 7;
}
// DeviceCertificate signed with intermediate or root certificate private key.
message SignedDeviceCertificate {
// Serialized DeviceCertificate. Required.
optional bytes device_certificate = 1;
// Signature of device_certificate. Signed with root or intermediate
// certificate private key using RSASSA-PSS. Required.
optional bytes signature = 2;
// Intermediate signing certificate. Present only for user device
// certificates. All others signed with root certificate private key.
optional SignedDeviceCertificate signer = 3;
}
// Contains device model information for a provisioned device.
message ProvisionedDeviceInfo {
enum WvSecurityLevel {
// Defined in "WV Modular DRM Security Integration Guide for
// Common Encryption (CENC)"
LEVEL_UNSPECIFIED = 0;
LEVEL_1 = 1;
LEVEL_2 = 2;
LEVEL_3 = 3;
}
// Widevine system ID for the device. Mandatory.
optional uint32 system_id = 1;
// Name of system-on-a-chip. Optional.
optional string soc = 2;
// Name of manufacturer. Optional.
optional string manufacturer = 3;
// Manufacturer's model name. Matches "brand" in device metadata. Optional.
optional string model = 4;
// Type of device (Phone, Tablet, TV, etc).
optional string device_type = 5;
// Device model year. Optional.
optional uint32 model_year = 6;
// Widevine-defined security level. Optional.
optional WvSecurityLevel security_level = 7 [default = LEVEL_UNSPECIFIED];
// True if the certificate corresponds to a test (non production) device.
// Optional.
optional bool test_device = 8 [default = false];
}
// Contains the status of the root or an intermediate DeviceCertificate.
message DeviceCertificateStatus {
enum CertificateStatus {
VALID = 0;
REVOKED = 1;
};
// Serial number of the DeviceCertificate to which this message refers.
// Required.
optional bytes serial_number = 1;
// Status of the certificate. Optional.
optional CertificateStatus status = 2 [default = VALID];
// Device model information about the device to which the certificate
// corresponds. Required.
optional ProvisionedDeviceInfo device_info = 4;
}
// List of DeviceCertificateStatus. Used to propagate certificate revocation and
// update list.
message DeviceCertificateStatusList {
// POSIX time, in seconds, when the list was created. Required.
optional uint32 creation_time_seconds = 1;
// DeviceCertificateStatus for each certifificate.
repeated DeviceCertificateStatus certificate_status = 2;
}
// Signed CertificateStatusList
message SignedCertificateStatusList {
// Serialized DeviceCertificateStatusList. Required.
optional bytes certificate_status_list = 1;
// Signature of certificate_status_list. Signed with root certificate private
// key using RSASSA-PSS. Required.
optional bytes signature = 2;
}

29
manifest.json

@ -0,0 +1,29 @@
{
"manifest_version": 2,
"name": "Widevine Decryptor",
"short_name": "WidevineDecryptor",
"description": "Decrypts and logs media keys from websites that use Widevine DRM",
"version": "1.0.0",
"permissions":
[
],
"icons":
{
},
"browser_action": {
},
"content_scripts":
[
{
"matches": ["https://*/*"],
"js": ["content_script.js"],
"css": [],
"run_at": "document_start",
"all_frames": true
}
],
"web_accessible_resources": ["content_key_decryption.js", "eme_interception.js", "lib/*", "protobuf-generated/*"]
}

890
protobuf-generated/license_protocol.proto.js

@ -0,0 +1,890 @@
'use strict'; // code generated by pbf v3.2.1
var LicenseType = self.LicenseType = {
"STREAMING": {
"value": 1,
"options": {}
},
"OFFLINE": {
"value": 2,
"options": {}
}
};
var ProtocolVersion = self.ProtocolVersion = {
"VERSION_2_0": {
"value": 20,
"options": {}
},
"VERSION_2_1": {
"value": 21,
"options": {}
}
};
// LicenseIdentification ========================================
var LicenseIdentification = self.LicenseIdentification = {};
LicenseIdentification.read = function (pbf, end) {
return pbf.readFields(LicenseIdentification._readField, {request_id: null, session_id: null, purchase_id: null, type: 0, version: 0, provider_session_token: null}, end);
};
LicenseIdentification._readField = function (tag, obj, pbf) {
if (tag === 1) obj.request_id = pbf.readBytes();
else if (tag === 2) obj.session_id = pbf.readBytes();
else if (tag === 3) obj.purchase_id = pbf.readBytes();
else if (tag === 4) obj.type = pbf.readVarint();
else if (tag === 5) obj.version = pbf.readVarint(true);
else if (tag === 6) obj.provider_session_token = pbf.readBytes();
};
LicenseIdentification.write = function (obj, pbf) {
if (obj.request_id) pbf.writeBytesField(1, obj.request_id);
if (obj.session_id) pbf.writeBytesField(2, obj.session_id);
if (obj.purchase_id) pbf.writeBytesField(3, obj.purchase_id);
if (obj.type) pbf.writeVarintField(4, obj.type);
if (obj.version) pbf.writeVarintField(5, obj.version);
if (obj.provider_session_token) pbf.writeBytesField(6, obj.provider_session_token);
};
// License ========================================
var License = self.License = {};
License.read = function (pbf, end) {
return pbf.readFields(License._readField, {id: null, policy: null, key: [], license_start_time: 0, remote_attestation_verified: false, provider_client_token: null}, end);
};
License._readField = function (tag, obj, pbf) {
if (tag === 1) obj.id = LicenseIdentification.read(pbf, pbf.readVarint() + pbf.pos);
else if (tag === 2) obj.policy = License.Policy.read(pbf, pbf.readVarint() + pbf.pos);
else if (tag === 3) obj.key.push(License.KeyContainer.read(pbf, pbf.readVarint() + pbf.pos));
else if (tag === 4) obj.license_start_time = pbf.readVarint(true);
else if (tag === 5) obj.remote_attestation_verified = pbf.readBoolean();
else if (tag === 6) obj.provider_client_token = pbf.readBytes();
};
License.write = function (obj, pbf) {
if (obj.id) pbf.writeMessage(1, LicenseIdentification.write, obj.id);
if (obj.policy) pbf.writeMessage(2, License.Policy.write, obj.policy);
if (obj.key) for (var i = 0; i < obj.key.length; i++) pbf.writeMessage(3, License.KeyContainer.write, obj.key[i]);
if (obj.license_start_time) pbf.writeVarintField(4, obj.license_start_time);
if (obj.remote_attestation_verified) pbf.writeBooleanField(5, obj.remote_attestation_verified);
if (obj.provider_client_token) pbf.writeBytesField(6, obj.provider_client_token);
};
// License.Policy ========================================
License.Policy = {};
License.Policy.read = function (pbf, end) {
return pbf.readFields(License.Policy._readField, {can_play: false, can_persist: false, can_renew: false, rental_duration_seconds: 0, playback_duration_seconds: 0, license_duration_seconds: 0, renewal_recovery_duration_seconds: 0, renewal_server_url: "", renewal_delay_seconds: 0, renewal_retry_interval_seconds: 0, renew_with_usage: false, renew_with_client_id: false}, end);
};
License.Policy._readField = function (tag, obj, pbf) {
if (tag === 1) obj.can_play = pbf.readBoolean();
else if (tag === 2) obj.can_persist = pbf.readBoolean();
else if (tag === 3) obj.can_renew = pbf.readBoolean();
else if (tag === 4) obj.rental_duration_seconds = pbf.readVarint(true);
else if (tag === 5) obj.playback_duration_seconds = pbf.readVarint(true);
else if (tag === 6) obj.license_duration_seconds = pbf.readVarint(true);
else if (tag === 7) obj.renewal_recovery_duration_seconds = pbf.readVarint(true);
else if (tag === 8) obj.renewal_server_url = pbf.readString();
else if (tag === 9) obj.renewal_delay_seconds = pbf.readVarint(true);
else if (tag === 10) obj.renewal_retry_interval_seconds = pbf.readVarint(true);
else if (tag === 11) obj.renew_with_usage = pbf.readBoolean();
else if (tag === 12) obj.renew_with_client_id = pbf.readBoolean();
};
License.Policy.write = function (obj, pbf) {
if (obj.can_play) pbf.writeBooleanField(1, obj.can_play);
if (obj.can_persist) pbf.writeBooleanField(2, obj.can_persist);
if (obj.can_renew) pbf.writeBooleanField(3, obj.can_renew);
if (obj.rental_duration_seconds) pbf.writeVarintField(4, obj.rental_duration_seconds);
if (obj.playback_duration_seconds) pbf.writeVarintField(5, obj.playback_duration_seconds);
if (obj.license_duration_seconds) pbf.writeVarintField(6, obj.license_duration_seconds);
if (obj.renewal_recovery_duration_seconds) pbf.writeVarintField(7, obj.renewal_recovery_duration_seconds);
if (obj.renewal_server_url) pbf.writeStringField(8, obj.renewal_server_url);
if (obj.renewal_delay_seconds) pbf.writeVarintField(9, obj.renewal_delay_seconds);
if (obj.renewal_retry_interval_seconds) pbf.writeVarintField(10, obj.renewal_retry_interval_seconds);
if (obj.renew_with_usage) pbf.writeBooleanField(11, obj.renew_with_usage);
if (obj.renew_with_client_id) pbf.writeBooleanField(12, obj.renew_with_client_id);
};
// License.KeyContainer ========================================
License.KeyContainer = {};
License.KeyContainer.read = function (pbf, end) {
return pbf.readFields(License.KeyContainer._readField, {id: null, iv: null, key: null, type: 0, level: {"value":1,"options":{}}, required_protection: null, requested_protection: null, key_control: null, operator_session_key_permissions: null, video_resolution_constraints: [], anti_rollback_usage_table: false}, end);
};
License.KeyContainer._readField = function (tag, obj, pbf) {
if (tag === 1) obj.id = pbf.readBytes();
else if (tag === 2) obj.iv = pbf.readBytes();
else if (tag === 3) obj.key = pbf.readBytes();
else if (tag === 4) obj.type = pbf.readVarint();
else if (tag === 5) obj.level = pbf.readVarint();
else if (tag === 6) obj.required_protection = License.KeyContainer.OutputProtection.read(pbf, pbf.readVarint() + pbf.pos);
else if (tag === 7) obj.requested_protection = License.KeyContainer.OutputProtection.read(pbf, pbf.readVarint() + pbf.pos);
else if (tag === 8) obj.key_control = License.KeyContainer.KeyControl.read(pbf, pbf.readVarint() + pbf.pos);
else if (tag === 9) obj.operator_session_key_permissions = License.KeyContainer.OperatorSessionKeyPermissions.read(pbf, pbf.readVarint() + pbf.pos);
else if (tag === 10) obj.video_resolution_constraints.push(License.KeyContainer.VideoResolutionConstraint.read(pbf, pbf.readVarint() + pbf.pos));
else if (tag === 11) obj.anti_rollback_usage_table = pbf.readBoolean();
};
License.KeyContainer.write = function (obj, pbf) {
if (obj.id) pbf.writeBytesField(1, obj.id);
if (obj.iv) pbf.writeBytesField(2, obj.iv);
if (obj.key) pbf.writeBytesField(3, obj.key);
if (obj.type) pbf.writeVarintField(4, obj.type);
if (obj.level != undefined && obj.level !== {"value":1,"options":{}}) pbf.writeVarintField(5, obj.level);
if (obj.required_protection) pbf.writeMessage(6, License.KeyContainer.OutputProtection.write, obj.required_protection);
if (obj.requested_protection) pbf.writeMessage(7, License.KeyContainer.OutputProtection.write, obj.requested_protection);
if (obj.key_control) pbf.writeMessage(8, License.KeyContainer.KeyControl.write, obj.key_control);
if (obj.operator_session_key_permissions) pbf.writeMessage(9, License.KeyContainer.OperatorSessionKeyPermissions.write, obj.operator_session_key_permissions);
if (obj.video_resolution_constraints) for (var i = 0; i < obj.video_resolution_constraints.length; i++) pbf.writeMessage(10, License.KeyContainer.VideoResolutionConstraint.write, obj.video_resolution_constraints[i]);
if (obj.anti_rollback_usage_table) pbf.writeBooleanField(11, obj.anti_rollback_usage_table);
};
License.KeyContainer.KeyType = {
"SIGNING": {
"value": 1,
"options": {}
},
"CONTENT": {
"value": 2,
"options": {}
},
"KEY_CONTROL": {
"value": 3,
"options": {}
},
"OPERATOR_SESSION": {
"value": 4,
"options": {}
}
};
License.KeyContainer.SecurityLevel = {
"SW_SECURE_CRYPTO": {
"value": 1,
"options": {}
},
"SW_SECURE_DECODE": {
"value": 2,
"options": {}
},
"HW_SECURE_CRYPTO": {
"value": 3,
"options": {}
},
"HW_SECURE_DECODE": {
"value": 4,
"options": {}
},
"HW_SECURE_ALL": {
"value": 5,
"options": {}
}
};