summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTomer <11458759+tomer8007@users.noreply.github.com>2020-10-17 21:25:57 +0200
committerGitHub <noreply@github.com>2020-10-17 21:25:57 +0200
commite9757b2fd5be33b2925128a5f0441fca2e953e32 (patch)
treece1b0ac137bf8ed367d9b788c2d00a7c983b959e
parentInitial commit (diff)
downloadwidevine-l3-decryptor-e9757b2fd5be33b2925128a5f0441fca2e953e32.tar
widevine-l3-decryptor-e9757b2fd5be33b2925128a5f0441fca2e953e32.tar.gz
widevine-l3-decryptor-e9757b2fd5be33b2925128a5f0441fca2e953e32.tar.bz2
widevine-l3-decryptor-e9757b2fd5be33b2925128a5f0441fca2e953e32.tar.lz
widevine-l3-decryptor-e9757b2fd5be33b2925128a5f0441fca2e953e32.tar.xz
widevine-l3-decryptor-e9757b2fd5be33b2925128a5f0441fca2e953e32.tar.zst
widevine-l3-decryptor-e9757b2fd5be33b2925128a5f0441fca2e953e32.zip
-rw-r--r--README.md32
-rw-r--r--content_key_decryption.js212
-rw-r--r--content_script.js26
-rw-r--r--docs/WidevineModularDRMSecurityIntegrationGuideforCENC.pdfbin0 -> 1748595 bytes
-rw-r--r--docs/Widevine_DRM_Architecture_Overview.pdfbin0 -> 463581 bytes
-rw-r--r--eme_interception.js418
-rw-r--r--lib/cryptojs-aes_0.2.0.min.js43
-rw-r--r--lib/pbf.3.0.5.min.js1
-rw-r--r--license_protocol.proto549
-rw-r--r--manifest.json29
-rw-r--r--protobuf-generated/license_protocol.proto.js890
11 files changed, 2200 insertions, 0 deletions
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..a6046d5
--- /dev/null
+++ b/README.md
@@ -0,0 +1,32 @@
+# 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, ...) to protect media contnet 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 demonstates 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 extnesion in developer mode and browse to any website that plays Widevine-protected content, such as https://bitmovin.com/demos/drm.
+
+Keys will be logged in plaintext to the javascript console:
+
+`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`
+
+## 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.
+
+## Legal Desclaimer
+This is for educational purposes only. Downloading copyrighted matirials from streaming services may violate their Terms Of Service. Use at your own risk.
+
diff --git a/content_key_decryption.js b/content_key_decryption.js
new file mode 100644
index 0000000..4ea8878
--- /dev/null
+++ b/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
+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
+// Extracted by applying some mathematical tricks to Araxan'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;
+}
+
+}()); \ No newline at end of file
diff --git a/content_script.js b/content_script.js
new file mode 100644
index 0000000..ad0992d
--- /dev/null
+++ b/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);
+ });
+} \ No newline at end of file
diff --git a/docs/WidevineModularDRMSecurityIntegrationGuideforCENC.pdf b/docs/WidevineModularDRMSecurityIntegrationGuideforCENC.pdf
new file mode 100644
index 0000000..6da38c8
--- /dev/null
+++ b/docs/WidevineModularDRMSecurityIntegrationGuideforCENC.pdf
Binary files differ
diff --git a/docs/Widevine_DRM_Architecture_Overview.pdf b/docs/Widevine_DRM_Architecture_Overview.pdf
new file mode 100644
index 0000000..7a81149
--- /dev/null
+++ b/docs/Widevine_DRM_Architecture_Overview.pdf
Binary files differ
diff --git a/eme_interception.js b/eme_interception.js
new file mode 100644
index 0000000..d676881
--- /dev/null
+++ b/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();
diff --git a/lib/cryptojs-aes_0.2.0.min.js b/lib/cryptojs-aes_0.2.0.min.js
new file mode 100644
index 0000000..403c305
--- /dev/null
+++ b/lib/cryptojs-aes_0.2.0.min.js
@@ -0,0 +1,43 @@
+/*
+CryptoJS v3.1.2
+code.google.com/p/crypto-js
+(c) 2009-2013 by Jeff Mott. All rights reserved.
+code.google.com/p/crypto-js/wiki/License
+*/
+var CryptoJS=CryptoJS||function(u,p){var d={},l=d.lib={},s=function(){},t=l.Base={extend:function(a){s.prototype=this;var c=new s;a&&c.mixIn(a);c.hasOwnProperty("init")||(c.init=function(){c.$super.init.apply(this,arguments)});c.init.prototype=c;c.$super=this;return c},create:function(){var a=this.extend();a.init.apply(a,arguments);return a},init:function(){},mixIn:function(a){for(var c in a)a.hasOwnProperty(c)&&(this[c]=a[c]);a.hasOwnProperty("toString")&&(this.toString=a.toString)},clone:function(){return this.init.prototype.extend(this)}},
+r=l.WordArray=t.extend({init:function(a,c){a=this.words=a||[];this.sigBytes=c!=p?c:4*a.length},toString:function(a){return(a||v).stringify(this)},concat:function(a){var c=this.words,e=a.words,j=this.sigBytes;a=a.sigBytes;this.clamp();if(j%4)for(var k=0;k<a;k++)c[j+k>>>2]|=(e[k>>>2]>>>24-8*(k%4)&255)<<24-8*((j+k)%4);else if(65535<e.length)for(k=0;k<a;k+=4)c[j+k>>>2]=e[k>>>2];else c.push.apply(c,e);this.sigBytes+=a;return this},clamp:function(){var a=this.words,c=this.sigBytes;a[c>>>2]&=4294967295<<
+32-8*(c%4);a.length=u.ceil(c/4)},clone:function(){var a=t.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var c=[],e=0;e<a;e+=4)c.push(4294967296*u.random()|0);return new r.init(c,a)}}),w=d.enc={},v=w.Hex={stringify:function(a){var c=a.words;a=a.sigBytes;for(var e=[],j=0;j<a;j++){var k=c[j>>>2]>>>24-8*(j%4)&255;e.push((k>>>4).toString(16));e.push((k&15).toString(16))}return e.join("")},parse:function(a){for(var c=a.length,e=[],j=0;j<c;j+=2)e[j>>>3]|=parseInt(a.substr(j,
+2),16)<<24-4*(j%8);return new r.init(e,c/2)}},b=w.Latin1={stringify:function(a){var c=a.words;a=a.sigBytes;for(var e=[],j=0;j<a;j++)e.push(String.fromCharCode(c[j>>>2]>>>24-8*(j%4)&255));return e.join("")},parse:function(a){for(var c=a.length,e=[],j=0;j<c;j++)e[j>>>2]|=(a.charCodeAt(j)&255)<<24-8*(j%4);return new r.init(e,c)}},x=w.Utf8={stringify:function(a){try{return decodeURIComponent(escape(b.stringify(a)))}catch(c){throw Error("Malformed UTF-8 data");}},parse:function(a){return b.parse(unescape(encodeURIComponent(a)))}},
+q=l.BufferedBlockAlgorithm=t.extend({reset:function(){this._data=new r.init;this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=x.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var c=this._data,e=c.words,j=c.sigBytes,k=this.blockSize,b=j/(4*k),b=a?u.ceil(b):u.max((b|0)-this._minBufferSize,0);a=b*k;j=u.min(4*a,j);if(a){for(var q=0;q<a;q+=k)this._doProcessBlock(e,q);q=e.splice(0,a);c.sigBytes-=j}return new r.init(q,j)},clone:function(){var a=t.clone.call(this);
+a._data=this._data.clone();return a},_minBufferSize:0});l.Hasher=q.extend({cfg:t.extend(),init:function(a){this.cfg=this.cfg.extend(a);this.reset()},reset:function(){q.reset.call(this);this._doReset()},update:function(a){this._append(a);this._process();return this},finalize:function(a){a&&this._append(a);return this._doFinalize()},blockSize:16,_createHelper:function(a){return function(b,e){return(new a.init(e)).finalize(b)}},_createHmacHelper:function(a){return function(b,e){return(new n.HMAC.init(a,
+e)).finalize(b)}}});var n=d.algo={};return d}(Math);
+(function(){var u=CryptoJS,p=u.lib.WordArray;u.enc.Base64={stringify:function(d){var l=d.words,p=d.sigBytes,t=this._map;d.clamp();d=[];for(var r=0;r<p;r+=3)for(var w=(l[r>>>2]>>>24-8*(r%4)&255)<<16|(l[r+1>>>2]>>>24-8*((r+1)%4)&255)<<8|l[r+2>>>2]>>>24-8*((r+2)%4)&255,v=0;4>v&&r+0.75*v<p;v++)d.push(t.charAt(w>>>6*(3-v)&63));if(l=t.charAt(64))for(;d.length%4;)d.push(l);return d.join("")},parse:function(d){var l=d.length,s=this._map,t=s.charAt(64);t&&(t=d.indexOf(t),-1!=t&&(l=t));for(var t=[],r=0,w=0;w<
+l;w++)if(w%4){var v=s.indexOf(d.charAt(w-1))<<2*(w%4),b=s.indexOf(d.charAt(w))>>>6-2*(w%4);t[r>>>2]|=(v|b)<<24-8*(r%4);r++}return p.create(t,r)},_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="}})();
+(function(u){function p(b,n,a,c,e,j,k){b=b+(n&a|~n&c)+e+k;return(b<<j|b>>>32-j)+n}function d(b,n,a,c,e,j,k){b=b+(n&c|a&~c)+e+k;return(b<<j|b>>>32-j)+n}function l(b,n,a,c,e,j,k){b=b+(n^a^c)+e+k;return(b<<j|b>>>32-j)+n}function s(b,n,a,c,e,j,k){b=b+(a^(n|~c))+e+k;return(b<<j|b>>>32-j)+n}for(var t=CryptoJS,r=t.lib,w=r.WordArray,v=r.Hasher,r=t.algo,b=[],x=0;64>x;x++)b[x]=4294967296*u.abs(u.sin(x+1))|0;r=r.MD5=v.extend({_doReset:function(){this._hash=new w.init([1732584193,4023233417,2562383102,271733878])},
+_doProcessBlock:function(q,n){for(var a=0;16>a;a++){var c=n+a,e=q[c];q[c]=(e<<8|e>>>24)&16711935|(e<<24|e>>>8)&4278255360}var a=this._hash.words,c=q[n+0],e=q[n+1],j=q[n+2],k=q[n+3],z=q[n+4],r=q[n+5],t=q[n+6],w=q[n+7],v=q[n+8],A=q[n+9],B=q[n+10],C=q[n+11],u=q[n+12],D=q[n+13],E=q[n+14],x=q[n+15],f=a[0],m=a[1],g=a[2],h=a[3],f=p(f,m,g,h,c,7,b[0]),h=p(h,f,m,g,e,12,b[1]),g=p(g,h,f,m,j,17,b[2]),m=p(m,g,h,f,k,22,b[3]),f=p(f,m,g,h,z,7,b[4]),h=p(h,f,m,g,r,12,b[5]),g=p(g,h,f,m,t,17,b[6]),m=p(m,g,h,f,w,22,b[7]),
+f=p(f,m,g,h,v,7,b[8]),h=p(h,f,m,g,A,12,b[9]),g=p(g,h,f,m,B,17,b[10]),m=p(m,g,h,f,C,22,b[11]),f=p(f,m,g,h,u,7,b[12]),h=p(h,f,m,g,D,12,b[13]),g=p(g,h,f,m,E,17,b[14]),m=p(m,g,h,f,x,22,b[15]),f=d(f,m,g,h,e,5,b[16]),h=d(h,f,m,g,t,9,b[17]),g=d(g,h,f,m,C,14,b[18]),m=d(m,g,h,f,c,20,b[19]),f=d(f,m,g,h,r,5,b[20]),h=d(h,f,m,g,B,9,b[21]),g=d(g,h,f,m,x,14,b[22]),m=d(m,g,h,f,z,20,b[23]),f=d(f,m,g,h,A,5,b[24]),h=d(h,f,m,g,E,9,b[25]),g=d(g,h,f,m,k,14,b[26]),m=d(m,g,h,f,v,20,b[27]),f=d(f,m,g,h,D,5,b[28]),h=d(h,f,
+m,g,j,9,b[29]),g=d(g,h,f,m,w,14,b[30]),m=d(m,g,h,f,u,20,b[31]),f=l(f,m,g,h,r,4,b[32]),h=l(h,f,m,g,v,11,b[33]),g=l(g,h,f,m,C,16,b[34]),m=l(m,g,h,f,E,23,b[35]),f=l(f,m,g,h,e,4,b[36]),h=l(h,f,m,g,z,11,b[37]),g=l(g,h,f,m,w,16,b[38]),m=l(m,g,h,f,B,23,b[39]),f=l(f,m,g,h,D,4,b[40]),h=l(h,f,m,g,c,11,b[41]),g=l(g,h,f,m,k,16,b[42]),m=l(m,g,h,f,t,23,b[43]),f=l(f,m,g,h,A,4,b[44]),h=l(h,f,m,g,u,11,b[45]),g=l(g,h,f,m,x,16,b[46]),m=l(m,g,h,f,j,23,b[47]),f=s(f,m,g,h,c,6,b[48]),h=s(h,f,m,g,w,10,b[49]),g=s(g,h,f,m,
+E,15,b[50]),m=s(m,g,h,f,r,21,b[51]),f=s(f,m,g,h,u,6,b[52]),h=s(h,f,m,g,k,10,b[53]),g=s(g,h,f,m,B,15,b[54]),m=s(m,g,h,f,e,21,b[55]),f=s(f,m,g,h,v,6,b[56]),h=s(h,f,m,g,x,10,b[57]),g=s(g,h,f,m,t,15,b[58]),m=s(m,g,h,f,D,21,b[59]),f=s(f,m,g,h,z,6,b[60]),h=s(h,f,m,g,C,10,b[61]),g=s(g,h,f,m,j,15,b[62]),m=s(m,g,h,f,A,21,b[63]);a[0]=a[0]+f|0;a[1]=a[1]+m|0;a[2]=a[2]+g|0;a[3]=a[3]+h|0},_doFinalize:function(){var b=this._data,n=b.words,a=8*this._nDataBytes,c=8*b.sigBytes;n[c>>>5]|=128<<24-c%32;var e=u.floor(a/
+4294967296);n[(c+64>>>9<<4)+15]=(e<<8|e>>>24)&16711935|(e<<24|e>>>8)&4278255360;n[(c+64>>>9<<4)+14]=(a<<8|a>>>24)&16711935|(a<<24|a>>>8)&4278255360;b.sigBytes=4*(n.length+1);this._process();b=this._hash;n=b.words;for(a=0;4>a;a++)c=n[a],n[a]=(c<<8|c>>>24)&16711935|(c<<24|c>>>8)&4278255360;return b},clone:function(){var b=v.clone.call(this);b._hash=this._hash.clone();return b}});t.MD5=v._createHelper(r);t.HmacMD5=v._createHmacHelper(r)})(Math);
+(function(){var u=CryptoJS,p=u.lib,d=p.Base,l=p.WordArray,p=u.algo,s=p.EvpKDF=d.extend({cfg:d.extend({keySize:4,hasher:p.MD5,iterations:1}),init:function(d){this.cfg=this.cfg.extend(d)},compute:function(d,r){for(var p=this.cfg,s=p.hasher.create(),b=l.create(),u=b.words,q=p.keySize,p=p.iterations;u.length<q;){n&&s.update(n);var n=s.update(d).finalize(r);s.reset();for(var a=1;a<p;a++)n=s.finalize(n),s.reset();b.concat(n)}b.sigBytes=4*q;return b}});u.EvpKDF=function(d,l,p){return s.create(p).compute(d,
+l)}})();
+CryptoJS.lib.Cipher||function(u){var p=CryptoJS,d=p.lib,l=d.Base,s=d.WordArray,t=d.BufferedBlockAlgorithm,r=p.enc.Base64,w=p.algo.EvpKDF,v=d.Cipher=t.extend({cfg:l.extend(),createEncryptor:function(e,a){return this.create(this._ENC_XFORM_MODE,e,a)},createDecryptor:function(e,a){return this.create(this._DEC_XFORM_MODE,e,a)},init:function(e,a,b){this.cfg=this.cfg.extend(b);this._xformMode=e;this._key=a;this.reset()},reset:function(){t.reset.call(this);this._doReset()},process:function(e){this._append(e);return this._process()},
+finalize:function(e){e&&this._append(e);return this._doFinalize()},keySize:4,ivSize:4,_ENC_XFORM_MODE:1,_DEC_XFORM_MODE:2,_createHelper:function(e){return{encrypt:function(b,k,d){return("string"==typeof k?c:a).encrypt(e,b,k,d)},decrypt:function(b,k,d){return("string"==typeof k?c:a).decrypt(e,b,k,d)}}}});d.StreamCipher=v.extend({_doFinalize:function(){return this._process(!0)},blockSize:1});var b=p.mode={},x=function(e,a,b){var c=this._iv;c?this._iv=u:c=this._prevBlock;for(var d=0;d<b;d++)e[a+d]^=
+c[d]},q=(d.BlockCipherMode=l.extend({createEncryptor:function(e,a){return this.Encryptor.create(e,a)},createDecryptor:function(e,a){return this.Decryptor.create(e,a)},init:function(e,a){this._cipher=e;this._iv=a}})).extend();q.Encryptor=q.extend({processBlock:function(e,a){var b=this._cipher,c=b.blockSize;x.call(this,e,a,c);b.encryptBlock(e,a);this._prevBlock=e.slice(a,a+c)}});q.Decryptor=q.extend({processBlock:function(e,a){var b=this._cipher,c=b.blockSize,d=e.slice(a,a+c);b.decryptBlock(e,a);x.call(this,
+e,a,c);this._prevBlock=d}});b=b.CBC=q;q=(p.pad={}).Pkcs7={pad:function(a,b){for(var c=4*b,c=c-a.sigBytes%c,d=c<<24|c<<16|c<<8|c,l=[],n=0;n<c;n+=4)l.push(d);c=s.create(l,c);a.concat(c)},unpad:function(a){a.sigBytes-=a.words[a.sigBytes-1>>>2]&255}};d.BlockCipher=v.extend({cfg:v.cfg.extend({mode:b,padding:q}),reset:function(){v.reset.call(this);var a=this.cfg,b=a.iv,a=a.mode;if(this._xformMode==this._ENC_XFORM_MODE)var c=a.createEncryptor;else c=a.createDecryptor,this._minBufferSize=1;this._mode=c.call(a,
+this,b&&b.words)},_doProcessBlock:function(a,b){this._mode.processBlock(a,b)},_doFinalize:function(){var a=this.cfg.padding;if(this._xformMode==this._ENC_XFORM_MODE){a.pad(this._data,this.blockSize);var b=this._process(!0)}else b=this._process(!0),a.unpad(b);return b},blockSize:4});var n=d.CipherParams=l.extend({init:function(a){this.mixIn(a)},toString:function(a){return(a||this.formatter).stringify(this)}}),b=(p.format={}).OpenSSL={stringify:function(a){var b=a.ciphertext;a=a.salt;return(a?s.create([1398893684,
+1701076831]).concat(a).concat(b):b).toString(r)},parse:function(a){a=r.parse(a);var b=a.words;if(1398893684==b[0]&&1701076831==b[1]){var c=s.create(b.slice(2,4));b.splice(0,4);a.sigBytes-=16}return n.create({ciphertext:a,salt:c})}},a=d.SerializableCipher=l.extend({cfg:l.extend({format:b}),encrypt:function(a,b,c,d){d=this.cfg.extend(d);var l=a.createEncryptor(c,d);b=l.finalize(b);l=l.cfg;return n.create({ciphertext:b,key:c,iv:l.iv,algorithm:a,mode:l.mode,padding:l.padding,blockSize:a.blockSize,formatter:d.format})},
+decrypt:function(a,b,c,d){d=this.cfg.extend(d);b=this._parse(b,d.format);return a.createDecryptor(c,d).finalize(b.ciphertext)},_parse:function(a,b){return"string"==typeof a?b.parse(a,this):a}}),p=(p.kdf={}).OpenSSL={execute:function(a,b,c,d){d||(d=s.random(8));a=w.create({keySize:b+c}).compute(a,d);c=s.create(a.words.slice(b),4*c);a.sigBytes=4*b;return n.create({key:a,iv:c,salt:d})}},c=d.PasswordBasedCipher=a.extend({cfg:a.cfg.extend({kdf:p}),encrypt:function(b,c,d,l){l=this.cfg.extend(l);d=l.kdf.execute(d,
+b.keySize,b.ivSize);l.iv=d.iv;b=a.encrypt.call(this,b,c,d.key,l);b.mixIn(d);return b},decrypt:function(b,c,d,l){l=this.cfg.extend(l);c=this._parse(c,l.format);d=l.kdf.execute(d,b.keySize,b.ivSize,c.salt);l.iv=d.iv;return a.decrypt.call(this,b,c,d.key,l)}})}();
+(function(){for(var u=CryptoJS,p=u.lib.BlockCipher,d=u.algo,l=[],s=[],t=[],r=[],w=[],v=[],b=[],x=[],q=[],n=[],a=[],c=0;256>c;c++)a[c]=128>c?c<<1:c<<1^283;for(var e=0,j=0,c=0;256>c;c++){var k=j^j<<1^j<<2^j<<3^j<<4,k=k>>>8^k&255^99;l[e]=k;s[k]=e;var z=a[e],F=a[z],G=a[F],y=257*a[k]^16843008*k;t[e]=y<<24|y>>>8;r[e]=y<<16|y>>>16;w[e]=y<<8|y>>>24;v[e]=y;y=16843009*G^65537*F^257*z^16843008*e;b[k]=y<<24|y>>>8;x[k]=y<<16|y>>>16;q[k]=y<<8|y>>>24;n[k]=y;e?(e=z^a[a[a[G^z]]],j^=a[a[j]]):e=j=1}var H=[0,1,2,4,8,
+16,32,64,128,27,54],d=d.AES=p.extend({_doReset:function(){for(var a=this._key,c=a.words,d=a.sigBytes/4,a=4*((this._nRounds=d+6)+1),e=this._keySchedule=[],j=0;j<a;j++)if(j<d)e[j]=c[j];else{var k=e[j-1];j%d?6<d&&4==j%d&&(k=l[k>>>24]<<24|l[k>>>16&255]<<16|l[k>>>8&255]<<8|l[k&255]):(k=k<<8|k>>>24,k=l[k>>>24]<<24|l[k>>>16&255]<<16|l[k>>>8&255]<<8|l[k&255],k^=H[j/d|0]<<24);e[j]=e[j-d]^k}c=this._invKeySchedule=[];for(d=0;d<a;d++)j=a-d,k=d%4?e[j]:e[j-4],c[d]=4>d||4>=j?k:b[l[k>>>24]]^x[l[k>>>16&255]]^q[l[k>>>
+8&255]]^n[l[k&255]]},encryptBlock:function(a,b){this._doCryptBlock(a,b,this._keySchedule,t,r,w,v,l)},decryptBlock:function(a,c){var d=a[c+1];a[c+1]=a[c+3];a[c+3]=d;this._doCryptBlock(a,c,this._invKeySchedule,b,x,q,n,s);d=a[c+1];a[c+1]=a[c+3];a[c+3]=d},_doCryptBlock:function(a,b,c,d,e,j,l,f){for(var m=this._nRounds,g=a[b]^c[0],h=a[b+1]^c[1],k=a[b+2]^c[2],n=a[b+3]^c[3],p=4,r=1;r<m;r++)var q=d[g>>>24]^e[h>>>16&255]^j[k>>>8&255]^l[n&255]^c[p++],s=d[h>>>24]^e[k>>>16&255]^j[n>>>8&255]^l[g&255]^c[p++],t=
+d[k>>>24]^e[n>>>16&255]^j[g>>>8&255]^l[h&255]^c[p++],n=d[n>>>24]^e[g>>>16&255]^j[h>>>8&255]^l[k&255]^c[p++],g=q,h=s,k=t;q=(f[g>>>24]<<24|f[h>>>16&255]<<16|f[k>>>8&255]<<8|f[n&255])^c[p++];s=(f[h>>>24]<<24|f[k>>>16&255]<<16|f[n>>>8&255]<<8|f[g&255])^c[p++];t=(f[k>>>24]<<24|f[n>>>16&255]<<16|f[g>>>8&255]<<8|f[h&255])^c[p++];n=(f[n>>>24]<<24|f[g>>>16&255]<<16|f[h>>>8&255]<<8|f[k&255])^c[p++];a[b]=q;a[b+1]=s;a[b+2]=t;a[b+3]=n},keySize:8});u.AES=p._createHelper(d)})();
+
+
+/*
+ * The MIT License
+ *
+ * (MIT)Copyright (c) 2015 artjomb
+ */
+!function(t){t.enc.Bin={stringify:function(t){for(var e=t.words,r=t.sigBytes,i=[],n=0;r>n;n++)for(var o=e[n>>>2]>>>24-n%4*8&255,s=7;s>=0;s--)i.push((o>>>s&1).toString(2));return i.join("")},parse:function(e){for(var r=[0],i=31,n=0,o=0;o<e.length;o++){var s=e[o];("0"===s||"1"===s)&&(r[r.length-1]+=parseInt(s)<<i,i--,n++,0>i&&(i=31,r.push(0)))}return t.lib.WordArray.create(r,Math.ceil(n/8))}}}(CryptoJS),function(t){var e;e=t.hasOwnProperty("ext")?t.ext:t.ext={},e.bitshift=function(t,e){var r,i,n=0,o=t.words,s=0;if(e>0){for(;e>31;)o.splice(0,1),o.push(0),e-=32,s++;if(0==e)return n;for(var c=o.length-s-1;c>=0;c--)r=o[c],o[c]<<=e,o[c]|=n,n=r>>>32-e}else if(0>e){for(;-31>e;)o.splice(0,0,0),o.length--,e+=32,s++;if(0==e)return n;e=-e,i=(1<<e)-1;for(var c=s;c<o.length;c++)r=o[c]&i,o[c]>>>=e,o[c]|=n,n=r<<32-e}return n},e.neg=function(t){for(var e=t.words,r=0;r<e.length;r++)e[r]=~e[r];return t},e.xor=function(t,e){for(var r=0;r<t.words.length;r++)t.words[r]^=e.words[r];return t},e.bitand=function(t,e){for(var r=t.clone(),i=r.words,n=e.words,o=0;o<i.length;o++)i[o]&=n[o];return r}}(CryptoJS),function(t){var e=t.lib.WordArray,r=window.crypto,i=Int32Array;i&&r&&r.getRandomValues?e.random=function(t){var n=new i(Math.ceil(t/4));return r.getRandomValues(n),new e.init([].map.call(n,function(t){return t}),t)}:console.log("No cryptographically secure randomness source available")}(CryptoJS),CryptoJS.mode.CFBw=function(){function t(t,e,r){for(var i=this,n=i._cipher,o=n.blockSize,s=i._prevBlock,c=n.cfg.segmentSize/32,a=0;o/c>a;a++){s?s=s.slice(c).concat(i._ct):(s=i._iv.slice(0),i._iv=void 0),r||(i._ct=t.slice(e+a*c,e+a*c+c));var f=s.slice(0);n.encryptBlock(f,0);for(var u=0;c>u;u++)t[e+a*c+u]^=f[u];r&&(i._ct=t.slice(e+a*c,e+a*c+c))}i._prevBlock=s}var e=CryptoJS.lib.BlockCipherMode.extend();return e.Encryptor=e.extend({processBlock:function(e,r){t.call(this,e,r,!0)}}),e.Decryptor=e.extend({processBlock:function(e,r){t.call(this,e,r,!1)}}),e}(),function(t){var e;e=t.hasOwnProperty("ext")?t.ext:t.ext={};{var r=(t.lib.Base,t.lib.WordArray);t.algo.AES}e.const_Zero=r.create([0,0,0,0]),e.const_One=r.create([0,0,0,1]),e.const_Rb=r.create([0,0,0,135]),e.const_Rb_Shifted=r.create([2147483648,0,0,67]),e.const_nonMSB=r.create([4294967295,4294967295,2147483647,2147483647]),e.isWordArray=function(t){return t&&"function"==typeof t.clamp&&"function"==typeof t.concat&&"array"==typeof t.words},t.pad.OneZeroPadding={pad:function(t,e){for(var i=4*e,n=i-t.sigBytes%i,o=[],s=0;n>s;s+=4){var c=0;0===s&&(c=2147483648),o.push(c)}var a=r.create(o,n);t.concat(a)},unpad:function(){}},t.pad.NoPadding={pad:function(){},unpad:function(){}},e.leftmostBytes=function(t,e){var r=t.clone();return r.sigBytes=e,r.clamp(),r},e.rightmostBytes=function(t,r){t.clamp();var i=32,n=t.clone(),o=8*(n.sigBytes-r);if(o>=i){var s=Math.floor(o/i);o-=s*i,n.words.splice(0,s),n.sigBytes-=s*i/8}return o>0&&(e.bitshift(n,o),n.sigBytes-=o/8),n},e.popWords=function(t,r){var i=e.leftmostBytes(t,4*r);return t.words=t.words.slice(r),t.sigBytes-=4*r,i},e.shiftBytes=function(t,i){i=i||16;var n=i%4;i-=n;for(var o=r.create(),s=0;i>s;s+=4)o.words.push(t.words.shift()),t.sigBytes-=4,o.sigBytes+=4;return n>0&&(o.words.push(t.words[0]),o.sigBytes+=n,e.bitshift(t,8*n),t.sigBytes-=n),o},e.xorendBytes=function(t,r){return e.leftmostBytes(t,t.sigBytes-r.sigBytes).concat(e.xor(e.rightmostBytes(t,r.sigBytes),r))},e.dbl=function(t){var r=e.msb(t);return e.bitshift(t,1),1===r&&e.xor(t,e.const_Rb),t},e.inv=function(t){var r=1&t.words[4];return e.bitshift(t,-1),1===r&&e.xor(t,e.const_Rb_Shifted),t},e.equals=function(t,e){if(!e||!e.words||t.sigBytes!==e.sigBytes)return!1;t.clamp(),e.clamp();for(var r=0;r<t.words.length;r++)if(t.words[r]!==e.words[r])return!1;return!0},e.msb=function(t){return t.words[0]>>>31}}(CryptoJS),CryptoJS.mode.CFBb=function(){function t(t,e,o){var s,c,a,f=this,u=f._cipher,h=32*u.blockSize,l=f._prevBlock,p=u.cfg.segmentSize,d=[];for(s=31;p>s;s+=32)d.push(4294967295);for(d.push((1<<p)-1<<32-p),s=d.length;s<t.length;s++)d.push(0);d=r.create(d);var _,y=d.clone(),g=y.clone();for(i(g,32*-e),s=0;h/p>s;s++){if(l){for(l=r.create(l),i(l,p),l=l.words,previousCiphertextSegment=f._ct;previousCiphertextSegment.length<h/32;)previousCiphertextSegment.push(0);previousCiphertextSegment=r.create(previousCiphertextSegment),i(previousCiphertextSegment,-h+p);for(var c=0;c<l.length;c++)l[c]|=previousCiphertextSegment.words[c]}else l=f._iv.slice(0),f._iv=void 0;a=32*e+s*p;var v=r.create(t.slice(0));i(v,a),o||(f._ct=v.words.slice(0,Math.ceil(p/32)));var B=l.slice(0);for(u.encryptBlock(B,0),c=0;c<Math.ceil(p/32);c++)v.words[c]^=B[c];for(c=0;c<v.words.length;c++)v.words[c]&=d.words[c];for(o&&(f._ct=v.words.slice(0,Math.ceil(p/32))),_=n(g.clone()),c=0;c<t.length;c++)t[c]&=_.words[c];for(i(v,-a),c=0;c<t.length;c++)t[c]|=v.words[c];i(y,-p),i(g,-p)}f._prevBlock=l}var e=CryptoJS.lib.BlockCipherMode.extend(),r=CryptoJS.lib.WordArray,i=CryptoJS.ext.bitshift,n=CryptoJS.ext.neg;return e.Encryptor=e.extend({processBlock:function(e,r){t.call(this,e,r,!0)}}),e.Decryptor=e.extend({processBlock:function(e,r){t.call(this,e,r,!1)}}),e}(),function(t){function e(e,r){var o=n.createEncryptor(e,{iv:i.create(),padding:t.pad.NoPadding}),s=o.finalize(r);return s}var r=t.lib.Base,i=t.lib.WordArray,n=t.algo.AES,o=t.ext,s=t.pad.OneZeroPadding,c=t.algo.CMAC=r.extend({init:function(t){var r=e(t,o.const_Zero),i=r.clone();if(o.dbl(i),this._isTwo()){var n=r.clone();o.inv(n)}else{var n=i.clone();o.dbl(n)}this._K1=i,this._K2=n,this._K=t,this._const_Bsize=16,this.reset()},reset:function(){this._x=o.const_Zero.clone(),this._counter=0,this._buffer=i.create()},update:function(r){if(!r)return this;var i=this._buffer,n=this._const_Bsize;for("string"==typeof r&&(r=t.enc.Utf8.parse(r)),i.concat(r);i.sigBytes>n;){var s=o.shiftBytes(i,n);o.xor(this._x,s),this._x.clamp(),this._x=e(this._K,this._x),this._counter++}return this},finalize:function(t){this.update(t);var r=this._buffer,i=this._const_Bsize,n=r.clone();return r.sigBytes===i?o.xor(n,this._K1):(s.pad(n,i/4),o.xor(n,this._K2)),o.xor(n,this._x),this.reset(),e(this._K,n)},_isTwo:function(){return!1}});t.CMAC=function(t,e){return c.create(t).finalize(e)},t.algo.OMAC1=c,t.algo.OMAC2=c.extend({_isTwo:function(){return!0}})}(CryptoJS),function(t){{var e=t.lib.Base,r=t.lib.WordArray,i=(t.algo.AES,t.ext),n=t.pad.OneZeroPadding,o=t.algo.CMAC,s=t.algo.S2V=e.extend({init:function(t){this._blockSize=16,this._cmacAD=o.create(t),this._cmacPT=o.create(t),this.reset()},reset:function(){this._buffer=r.create(),this._cmacAD.reset(),this._cmacPT.reset(),this._d=this._cmacAD.finalize(i.const_Zero),this._empty=!0,this._ptStarted=!1},updateAAD:function(e){return this._ptStarted?this:e?("string"==typeof e&&(e=t.enc.Utf8.parse(e)),this._d=i.xor(i.dbl(this._d),this._cmacAD.finalize(e)),this._empty=!1,this):this},update:function(e){if(!e)return this;this._ptStarted=!0;var r=this._buffer,n=this._blockSize,o=n/4,s=this._cmacPT;for("string"==typeof e&&(e=t.enc.Utf8.parse(e)),r.concat(e);r.sigBytes>=2*n;){this._empty=!1;var c=i.popWords(r,o);s.update(c)}return this},finalize:function(t){this.update(t);var e=this._blockSize,r=this._buffer;if(this._empty&&0===r.sigBytes)return this._cmacAD.finalize(i.const_One);var o;return r.sigBytes>=e?o=i.xorendBytes(r,this._d):(n.pad(r,e),o=i.xor(i.dbl(this._d),r)),this._cmacPT.finalize(o)}});t.SIV=e.extend({init:function(t){var e=t.sigBytes/2;this._s2vKey=i.shiftBytes(t,e),this._ctrKey=t},encrypt:function(e,r){!r&&e&&(r=e,e=[]);var n=s.create(this._s2vKey);Array.prototype.forEach.call(e,function(t){n.updateAAD(t)});var o=n.finalize(r),c=i.bitand(o,i.const_nonMSB),a=t.AES.encrypt(r,this._ctrKey,{iv:c,mode:t.mode.CTR,padding:t.pad.NoPadding});return o.concat(a.ciphertext)},decrypt:function(e,r){!r&&e&&(r=e,e=[]);var n=i.shiftBytes(r,16),o=i.bitand(n,i.const_nonMSB),c=t.AES.decrypt({ciphertext:r},this._ctrKey,{iv:o,mode:t.mode.CTR,padding:t.pad.NoPadding}),a=s.create(this._s2vKey);Array.prototype.forEach.call(e,function(t){a.updateAAD(t)});var f=a.finalize(c);return i.equals(n,f)?c:!1}})}}(CryptoJS),function(t){{var e=t.lib.Base,r=t.lib.WordArray,i=t.algo.AES,n=t.ext,o=t.algo.CMAC,s=r.create([0,0,0,0]),c=r.create([0,0,0,1]),a=r.create([0,0,0,2]),f=16;t.EAX=e.extend({init:function(t,e){var r;if(e&&e.splitKey){var i=Math.floor(t.sigBytes/2);r=n.shiftBytes(t,i)}else r=t.clone();this._ctrKey=t,this._mac=o.create(r),this._tagLen=e&&e.tagLength||f,this.reset()},reset:function(){this._mac.update(c),this._ctr&&this._ctr.reset()},updateAAD:function(t){return this._mac.update(t),this},initCrypt:function(e,o){var c=this;return c._tag=c._mac.finalize(),c._isEnc=e,c._mac.update(s),o=c._mac.finalize(o),n.xor(c._tag,o),c._ctr=i.createEncryptor(c._ctrKey,{iv:o,mode:t.mode.CTR,padding:t.pad.NoPadding}),c._buf=r.create(),c._mac.update(a),c},update:function(e){"string"==typeof e&&(e=t.enc.Utf8.parse(e));var i=this,o=i._buf,s=i._isEnc;o.concat(e);var c=s?o.sigBytes:Math.max(o.sigBytes-i._tagLen,0),a=c>0?n.shiftBytes(o,c):r.create(),f=i._ctr.process(a);return i._mac.update(s?f:a),f},finalize:function(t){var e=this,i=t?e.update(t):r.create(),o=e._mac,s=e._ctr.finalize();if(e._isEnc){var c=o.finalize(s);return n.xor(e._tag,c),e.reset(),i.concat(s).concat(e._tag)}var c=o.finalize();return n.xor(e._tag,c),e.reset(),n.equals(e._tag,e._buf)?i.concat(s):!1},encrypt:function(t,e,r){var i=this;return r&&Array.prototype.forEach.call(r,function(t){i.updateAAD(t)}),i.initCrypt(!0,e),i.finalize(t)},decrypt:function(t,e,r){var i=this;return r&&Array.prototype.forEach.call(r,function(t){i.updateAAD(t)}),i.initCrypt(!1,e),i.finalize(t)}})}}(CryptoJS); \ No newline at end of file
diff --git a/lib/pbf.3.0.5.min.js b/lib/pbf.3.0.5.min.js
new file mode 100644
index 0000000..023bf38
--- /dev/null
+++ b/lib/pbf.3.0.5.min.js
@@ -0,0 +1 @@
+!function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var i;i="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,i.Pbf=t()}}(function(){return function t(i,e,r){function s(o,h){if(!e[o]){if(!i[o]){var a="function"==typeof require&&require;if(!h&&a)return a(o,!0);if(n)return n(o,!0);var u=new Error("Cannot find module '"+o+"'");throw u.code="MODULE_NOT_FOUND",u}var f=e[o]={exports:{}};i[o][0].call(f.exports,function(t){var e=i[o][1][t];return s(e?e:t)},f,f.exports,t,i,e,r)}return e[o].exports}for(var n="function"==typeof require&&require,o=0;o<r.length;o++)s(r[o]);return s}({1:[function(t,i,e){"use strict";function r(t){this.buf=ArrayBuffer.isView&&ArrayBuffer.isView(t)?t:new Uint8Array(t||0),this.pos=0,this.type=0,this.length=this.buf.length}function s(t,i,e){var r,s,n=e.buf;if(s=n[e.pos++],r=(112&s)>>4,s<128)return o(t,r,i);if(s=n[e.pos++],r|=(127&s)<<3,s<128)return o(t,r,i);if(s=n[e.pos++],r|=(127&s)<<10,s<128)return o(t,r,i);if(s=n[e.pos++],r|=(127&s)<<17,s<128)return o(t,r,i);if(s=n[e.pos++],r|=(127&s)<<24,s<128)return o(t,r,i);if(s=n[e.pos++],r|=(1&s)<<31,s<128)return o(t,r,i);throw new Error("Expected varint not more than 10 bytes")}function n(t){return t.type===r.Bytes?t.readVarint()+t.pos:t.pos+1}function o(t,i,e){return e?4294967296*i+(t>>>0):4294967296*(i>>>0)+(t>>>0)}function h(t,i){var e,r;if(t>=0?(e=t%4294967296|0,r=t/4294967296|0):(e=~(-t%4294967296),r=~(-t/4294967296),4294967295^e?e=e+1|0:(e=0,r=r+1|0)),t>=0x10000000000000000||t<-0x10000000000000000)throw new Error("Given varint doesn't fit into 10 bytes");i.realloc(10),a(e,r,i),u(r,i)}function a(t,i,e){e.buf[e.pos++]=127&t|128,t>>>=7,e.buf[e.pos++]=127&t|128,t>>>=7,e.buf[e.pos++]=127&t|128,t>>>=7,e.buf[e.pos++]=127&t|128,t>>>=7,e.buf[e.pos]=127&t}function u(t,i){var e=(7&t)<<4;i.buf[i.pos++]|=e|((t>>>=3)?128:0),t&&(i.buf[i.pos++]=127&t|((t>>>=7)?128:0),t&&(i.buf[i.pos++]=127&t|((t>>>=7)?128:0),t&&(i.buf[i.pos++]=127&t|((t>>>=7)?128:0),t&&(i.buf[i.pos++]=127&t|((t>>>=7)?128:0),t&&(i.buf[i.pos++]=127&t)))))}function f(t,i,e){var r=i<=16383?1:i<=2097151?2:i<=268435455?3:Math.ceil(Math.log(i)/(7*Math.LN2));e.realloc(r);for(var s=e.pos-1;s>=t;s--)e.buf[s+r]=e.buf[s]}function d(t,i){for(var e=0;e<t.length;e++)i.writeVarint(t[e])}function p(t,i){for(var e=0;e<t.length;e++)i.writeSVarint(t[e])}function c(t,i){for(var e=0;e<t.length;e++)i.writeFloat(t[e])}function l(t,i){for(var e=0;e<t.length;e++)i.writeDouble(t[e])}function w(t,i){for(var e=0;e<t.length;e++)i.writeBoolean(t[e])}function F(t,i){for(var e=0;e<t.length;e++)i.writeFixed32(t[e])}function b(t,i){for(var e=0;e<t.length;e++)i.writeSFixed32(t[e])}function v(t,i){for(var e=0;e<t.length;e++)i.writeFixed64(t[e])}function g(t,i){for(var e=0;e<t.length;e++)i.writeSFixed64(t[e])}function x(t,i){return(t[i]|t[i+1]<<8|t[i+2]<<16)+16777216*t[i+3]}function V(t,i,e){t[e]=i,t[e+1]=i>>>8,t[e+2]=i>>>16,t[e+3]=i>>>24}function y(t,i){return(t[i]|t[i+1]<<8|t[i+2]<<16)+(t[i+3]<<24)}function M(t,i,e){for(var r="",s=i;s<e;){var n=t[s],o=null,h=n>239?4:n>223?3:n>191?2:1;if(s+h>e)break;var a,u,f;1===h?n<128&&(o=n):2===h?(a=t[s+1],128===(192&a)&&(o=(31&n)<<6|63&a,o<=127&&(o=null))):3===h?(a=t[s+1],u=t[s+2],128===(192&a)&&128===(192&u)&&(o=(15&n)<<12|(63&a)<<6|63&u,(o<=2047||o>=55296&&o<=57343)&&(o=null))):4===h&&(a=t[s+1],u=t[s+2],f=t[s+3],128===(192&a)&&128===(192&u)&&128===(192&f)&&(o=(15&n)<<18|(63&a)<<12|(63&u)<<6|63&f,(o<=65535||o>=1114112)&&(o=null))),null===o?(o=65533,h=1):o>65535&&(o-=65536,r+=String.fromCharCode(o>>>10&1023|55296),o=56320|1023&o),r+=String.fromCharCode(o),s+=h}return r}function S(t,i,e){for(var r,s,n=0;n<i.length;n++){if(r=i.charCodeAt(n),r>55295&&r<57344){if(!s){r>56319||n+1===i.length?(t[e++]=239,t[e++]=191,t[e++]=189):s=r;continue}if(r<56320){t[e++]=239,t[e++]=191,t[e++]=189,s=r;continue}r=s-55296<<10|r-56320|65536,s=null}else s&&(t[e++]=239,t[e++]=191,t[e++]=189,s=null);r<128?t[e++]=r:(r<2048?t[e++]=r>>6|192:(r<65536?t[e++]=r>>12|224:(t[e++]=r>>18|240,t[e++]=r>>12&63|128),t[e++]=r>>6&63|128),t[e++]=63&r|128)}return e}i.exports=r;var B=t("ieee754");r.Varint=0,r.Fixed64=1,r.Bytes=2,r.Fixed32=5;var k=4294967296,P=1/k;r.prototype={destroy:function(){this.buf=null},readFields:function(t,i,e){for(e=e||this.length;this.pos<e;){var r=this.readVarint(),s=r>>3,n=this.pos;this.type=7&r,t(s,i,this),this.pos===n&&this.skip(r)}return i},readMessage:function(t,i){return this.readFields(t,i,this.readVarint()+this.pos)},readFixed32:function(){var t=x(this.buf,this.pos);return this.pos+=4,t},readSFixed32:function(){var t=y(this.buf,this.pos);return this.pos+=4,t},readFixed64:function(){var t=x(this.buf,this.pos)+x(this.buf,this.pos+4)*k;return this.pos+=8,t},readSFixed64:function(){var t=x(this.buf,this.pos)+y(this.buf,this.pos+4)*k;return this.pos+=8,t},readFloat:function(){var t=B.read(this.buf,this.pos,!0,23,4);return this.pos+=4,t},readDouble:function(){var t=B.read(this.buf,this.pos,!0,52,8);return this.pos+=8,t},readVarint:function(t){var i,e,r=this.buf;return e=r[this.pos++],i=127&e,e<128?i:(e=r[this.pos++],i|=(127&e)<<7,e<128?i:(e=r[this.pos++],i|=(127&e)<<14,e<128?i:(e=r[this.pos++],i|=(127&e)<<21,e<128?i:(e=r[this.pos],i|=(15&e)<<28,s(i,t,this)))))},readVarint64:function(){return this.readVarint(!0)},readSVarint:function(){var t=this.readVarint();return t%2===1?(t+1)/-2:t/2},readBoolean:function(){return Boolean(this.readVarint())},readString:function(){var t=this.readVarint()+this.pos,i=M(this.buf,this.pos,t);return this.pos=t,i},readBytes:function(){var t=this.readVarint()+this.pos,i=this.buf.subarray(this.pos,t);return this.pos=t,i},readPackedVarint:function(t,i){var e=n(this);for(t=t||[];this.pos<e;)t.push(this.readVarint(i));return t},readPackedSVarint:function(t){var i=n(this);for(t=t||[];this.pos<i;)t.push(this.readSVarint());return t},readPackedBoolean:function(t){var i=n(this);for(t=t||[];this.pos<i;)t.push(this.readBoolean());return t},readPackedFloat:function(t){var i=n(this);for(t=t||[];this.pos<i;)t.push(this.readFloat());return t},readPackedDouble:function(t){var i=n(this);for(t=t||[];this.pos<i;)t.push(this.readDouble());return t},readPackedFixed32:function(t){var i=n(this);for(t=t||[];this.pos<i;)t.push(this.readFixed32());return t},readPackedSFixed32:function(t){var i=n(this);for(t=t||[];this.pos<i;)t.push(this.readSFixed32());return t},readPackedFixed64:function(t){var i=n(this);for(t=t||[];this.pos<i;)t.push(this.readFixed64());return t},readPackedSFixed64:function(t){var i=n(this);for(t=t||[];this.pos<i;)t.push(this.readSFixed64());return t},skip:function(t){var i=7&t;if(i===r.Varint)for(;this.buf[this.pos++]>127;);else if(i===r.Bytes)this.pos=this.readVarint()+this.pos;else if(i===r.Fixed32)this.pos+=4;else{if(i!==r.Fixed64)throw new Error("Unimplemented type: "+i);this.pos+=8}},writeTag:function(t,i){this.writeVarint(t<<3|i)},realloc:function(t){for(var i=this.length||16;i<this.pos+t;)i*=2;if(i!==this.length){var e=new Uint8Array(i);e.set(this.buf),this.buf=e,this.length=i}},finish:function(){return this.length=this.pos,this.pos=0,this.buf.subarray(0,this.length)},writeFixed32:function(t){this.realloc(4),V(this.buf,t,this.pos),this.pos+=4},writeSFixed32:function(t){this.realloc(4),V(this.buf,t,this.pos),this.pos+=4},writeFixed64:function(t){this.realloc(8),V(this.buf,t&-1,this.pos),V(this.buf,Math.floor(t*P),this.pos+4),this.pos+=8},writeSFixed64:function(t){this.realloc(8),V(this.buf,t&-1,this.pos),V(this.buf,Math.floor(t*P),this.pos+4),this.pos+=8},writeVarint:function(t){return t=+t||0,t>268435455||t<0?void h(t,this):(this.realloc(4),this.buf[this.pos++]=127&t|(t>127?128:0),void(t<=127||(this.buf[this.pos++]=127&(t>>>=7)|(t>127?128:0),t<=127||(this.buf[this.pos++]=127&(t>>>=7)|(t>127?128:0),t<=127||(this.buf[this.pos++]=t>>>7&127)))))},writeSVarint:function(t){this.writeVarint(t<0?2*-t-1:2*t)},writeBoolean:function(t){this.writeVarint(Boolean(t))},writeString:function(t){t=String(t),this.realloc(4*t.length),this.pos++;var i=this.pos;this.pos=S(this.buf,t,this.pos);var e=this.pos-i;e>=128&&f(i,e,this),this.pos=i-1,this.writeVarint(e),this.pos+=e},writeFloat:function(t){this.realloc(4),B.write(this.buf,t,this.pos,!0,23,4),this.pos+=4},writeDouble:function(t){this.realloc(8),B.write(this.buf,t,this.pos,!0,52,8),this.pos+=8},writeBytes:function(t){var i=t.length;this.writeVarint(i),this.realloc(i);for(var e=0;e<i;e++)this.buf[this.pos++]=t[e]},writeRawMessage:function(t,i){this.pos++;var e=this.pos;t(i,this);var r=this.pos-e;r>=128&&f(e,r,this),this.pos=e-1,this.writeVarint(r),this.pos+=r},writeMessage:function(t,i,e){this.writeTag(t,r.Bytes),this.writeRawMessage(i,e)},writePackedVarint:function(t,i){this.writeMessage(t,d,i)},writePackedSVarint:function(t,i){this.writeMessage(t,p,i)},writePackedBoolean:function(t,i){this.writeMessage(t,w,i)},writePackedFloat:function(t,i){this.writeMessage(t,c,i)},writePackedDouble:function(t,i){this.writeMessage(t,l,i)},writePackedFixed32:function(t,i){this.writeMessage(t,F,i)},writePackedSFixed32:function(t,i){this.writeMessage(t,b,i)},writePackedFixed64:function(t,i){this.writeMessage(t,v,i)},writePackedSFixed64:function(t,i){this.writeMessage(t,g,i)},writeBytesField:function(t,i){this.writeTag(t,r.Bytes),this.writeBytes(i)},writeFixed32Field:function(t,i){this.writeTag(t,r.Fixed32),this.writeFixed32(i)},writeSFixed32Field:function(t,i){this.writeTag(t,r.Fixed32),this.writeSFixed32(i)},writeFixed64Field:function(t,i){this.writeTag(t,r.Fixed64),this.writeFixed64(i)},writeSFixed64Field:function(t,i){this.writeTag(t,r.Fixed64),this.writeSFixed64(i)},writeVarintField:function(t,i){this.writeTag(t,r.Varint),this.writeVarint(i)},writeSVarintField:function(t,i){this.writeTag(t,r.Varint),this.writeSVarint(i)},writeStringField:function(t,i){this.writeTag(t,r.Bytes),this.writeString(i)},writeFloatField:function(t,i){this.writeTag(t,r.Fixed32),this.writeFloat(i)},writeDoubleField:function(t,i){this.writeTag(t,r.Fixed64),this.writeDouble(i)},writeBooleanField:function(t,i){this.writeVarintField(t,Boolean(i))}}},{ieee754:2}],2:[function(t,i,e){e.read=function(t,i,e,r,s){var n,o,h=8*s-r-1,a=(1<<h)-1,u=a>>1,f=-7,d=e?s-1:0,p=e?-1:1,c=t[i+d];for(d+=p,n=c&(1<<-f)-1,c>>=-f,f+=h;f>0;n=256*n+t[i+d],d+=p,f-=8);for(o=n&(1<<-f)-1,n>>=-f,f+=r;f>0;o=256*o+t[i+d],d+=p,f-=8);if(0===n)n=1-u;else{if(n===a)return o?NaN:(c?-1:1)*(1/0);o+=Math.pow(2,r),n-=u}return(c?-1:1)*o*Math.pow(2,n-r)},e.write=function(t,i,e,r,s,n){var o,h,a,u=8*n-s-1,f=(1<<u)-1,d=f>>1,p=23===s?Math.pow(2,-24)-Math.pow(2,-77):0,c=r?0:n-1,l=r?1:-1,w=i<0||0===i&&1/i<0?1:0;for(i=Math.abs(i),isNaN(i)||i===1/0?(h=isNaN(i)?1:0,o=f):(o=Math.floor(Math.log(i)/Math.LN2),i*(a=Math.pow(2,-o))<1&&(o--,a*=2),i+=o+d>=1?p/a:p*Math.pow(2,1-d),i*a>=2&&(o++,a/=2),o+d>=f?(h=0,o=f):o+d>=1?(h=(i*a-1)*Math.pow(2,s),o+=d):(h=i*Math.pow(2,d-1)*Math.pow(2,s),o=0));s>=8;t[e+c]=255&h,c+=l,h/=256,s-=8);for(o=o<<s|h,u+=s;u>0;t[e+c]=255&o,c+=l,o/=256,u-=8);t[e+c-l]|=128*w}},{}]},{},[1])(1)}); \ No newline at end of file
diff --git a/license_protocol.proto b/license_protocol.proto
new file mode 100644
index 0000000..9079594
--- /dev/null
+++ b/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;
+}
diff --git a/manifest.json b/manifest.json
new file mode 100644
index 0000000..08b928f
--- /dev/null
+++ b/manifest.json
@@ -0,0 +1,29 @@
+{
+ "manifest_version": 2,
+ "name": "Widivine Decryptor",
+ "short_name": "WidevineDecryptor",
+ "description": "Decrypts and logs media keys from websites that use Widivine DRM",
+ "version": "1.0.0",
+ "permissions":
+ [
+
+ ],
+ "icons":
+ {
+
+ },
+ "browser_action": {
+
+ },
+
+ "content_scripts":
+ [
+ {
+ "matches": ["https://*/*"],
+ "js": ["content_script.js"],
+ "css": [],
+ "run_at": "document_start"
+ }
+ ],
+ "web_accessible_resources": ["content_key_decryption.js", "eme_interception.js", "lib/*", "protobuf-generated/*"]
+} \ No newline at end of file
diff --git a/protobuf-generated/license_protocol.proto.js b/protobuf-generated/license_protocol.proto.js
new file mode 100644
index 0000000..f98b25e
--- /dev/null
+++ b/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": {}
+ }
+};
+
+// License.KeyContainer.KeyControl ========================================
+
+License.KeyContainer.KeyControl = {};
+
+License.KeyContainer.KeyControl.read = function (pbf, end) {
+ return pbf.readFields(License.KeyContainer.KeyControl._readField, {key_control_block: null, iv: null}, end);
+};
+License.KeyContainer.KeyControl._readField = function (tag, obj, pbf) {
+ if (tag === 1) obj.key_control_block = pbf.readBytes();
+ else if (tag === 2) obj.iv = pbf.readBytes();
+};
+License.KeyContainer.KeyControl.write = function (obj, pbf) {
+ if (obj.key_control_block) pbf.writeBytesField(1, obj.key_control_block);
+ if (obj.iv) pbf.writeBytesField(2, obj.iv);
+};
+
+// License.KeyContainer.OutputProtection ========================================
+
+License.KeyContainer.OutputProtection = {};
+
+License.KeyContainer.OutputProtection.read = function (pbf, end) {
+ return pbf.readFields(License.KeyContainer.OutputProtection._readField, {hdcp: {"value":0,"options":{}}, cgms_flags: {"value":42,"options":{}}}, end);
+};
+License.KeyContainer.OutputProtection._readField = function (tag, obj, pbf) {
+ if (tag === 1) obj.hdcp = pbf.readVarint();
+ else if (tag === 2) obj.cgms_flags = pbf.readVarint();
+};
+License.KeyContainer.OutputProtection.write = function (obj, pbf) {
+ if (obj.hdcp != undefined && obj.hdcp !== {"value":0,"options":{}}) pbf.writeVarintField(1, obj.hdcp);
+ if (obj.cgms_flags != undefined && obj.cgms_flags !== {"value":42,"options":{}}) pbf.writeVarintField(2, obj.cgms_flags);
+};
+
+License.KeyContainer.OutputProtection.HDCP = {
+ "HDCP_NONE": {
+ "value": 0,
+ "options": {}
+ },
+ "HDCP_V1": {
+ "value": 1,
+ "options": {}
+ },
+ "HDCP_V2": {
+ "value": 2,
+ "options": {}
+ },
+ "HDCP_V2_1": {
+ "value": 3,
+ "options": {}
+ },
+ "HDCP_V2_2": {
+ "value": 4,
+ "options": {}
+ },
+ "HDCP_NO_DIGITAL_OUTPUT": {
+ "value": 255,
+ "options": {}
+ }
+};
+
+License.KeyContainer.OutputProtection.CGMS = {
+ "CGMS_NONE": {
+ "value": 42,
+ "options": {}
+ },
+ "COPY_FREE": {
+ "value": 0,
+ "options": {}
+ },
+ "COPY_ONCE": {
+ "value": 2,
+ "options": {}
+ },
+ "COPY_NEVER": {
+ "value": 3,
+ "options": {}
+ }
+};
+
+// License.KeyContainer.VideoResolutionConstraint ========================================
+
+License.KeyContainer.VideoResolutionConstraint = {};
+
+License.KeyContainer.VideoResolutionConstraint.read = function (pbf, end) {
+ return pbf.readFields(License.KeyContainer.VideoResolutionConstraint._readField, {min_resolution_pixels: 0, max_resolution_pixels: 0, required_protection: null}, end);
+};
+License.KeyContainer.VideoResolutionConstraint._readField = function (tag, obj, pbf) {
+ if (tag === 1) obj.min_resolution_pixels = pbf.readVarint();
+ else if (tag === 2) obj.max_resolution_pixels = pbf.readVarint();
+ else if (tag === 3) obj.required_protection = License.KeyContainer.OutputProtection.read(pbf, pbf.readVarint() + pbf.pos);
+};
+License.KeyContainer.VideoResolutionConstraint.write = function (obj, pbf) {
+ if (obj.min_resolution_pixels) pbf.writeVarintField(1, obj.min_resolution_pixels);
+ if (obj.max_resolution_pixels) pbf.writeVarintField(2, obj.max_resolution_pixels);
+ if (obj.required_protection) pbf.writeMessage(3, License.KeyContainer.OutputProtection.write, obj.required_protection);
+};
+
+// License.KeyContainer.OperatorSessionKeyPermissions ========================================
+
+License.KeyContainer.OperatorSessionKeyPermissions = {};
+
+License.KeyContainer.OperatorSessionKeyPermissions.read = function (pbf, end) {
+ return pbf.readFields(License.KeyContainer.OperatorSessionKeyPermissions._readField, {allow_encrypt: false, allow_decrypt: false, allow_sign: false, allow_signature_verify: false}, end);
+};
+License.KeyContainer.OperatorSessionKeyPermissions._readField = function (tag, obj, pbf) {
+ if (tag === 1) obj.allow_encrypt = pbf.readBoolean();
+ else if (tag === 2) obj.allow_decrypt = pbf.readBoolean();
+ else if (tag === 3) obj.allow_sign = pbf.readBoolean();
+ else if (tag === 4) obj.allow_signature_verify = pbf.readBoolean();
+};
+License.KeyContainer.OperatorSessionKeyPermissions.write = function (obj, pbf) {
+ if (obj.allow_encrypt) pbf.writeBooleanField(1, obj.allow_encrypt);
+ if (obj.allow_decrypt) pbf.writeBooleanField(2, obj.allow_decrypt);
+ if (obj.allow_sign) pbf.writeBooleanField(3, obj.allow_sign);
+ if (obj.allow_signature_verify) pbf.writeBooleanField(4, obj.allow_signature_verify);
+};
+
+// LicenseRequest ========================================
+
+var LicenseRequest = self.LicenseRequest = {};
+
+LicenseRequest.read = function (pbf, end) {
+ return pbf.readFields(LicenseRequest._readField, {client_id: null, content_id: null, type: 0, request_time: 0, key_control_nonce_deprecated: null, protocol_version: {"value":20,"options":{}}, key_control_nonce: 0, encrypted_client_id: null}, end);
+};
+LicenseRequest._readField = function (tag, obj, pbf) {
+ if (tag === 1) obj.client_id = ClientIdentification.read(pbf, pbf.readVarint() + pbf.pos);
+ else if (tag === 2) obj.content_id = LicenseRequest.ContentIdentification.read(pbf, pbf.readVarint() + pbf.pos);
+ else if (tag === 3) obj.type = pbf.readVarint();
+ else if (tag === 4) obj.request_time = pbf.readVarint(true);
+ else if (tag === 5) obj.key_control_nonce_deprecated = pbf.readBytes();
+ else if (tag === 6) obj.protocol_version = pbf.readVarint();
+ else if (tag === 7) obj.key_control_nonce = pbf.readVarint();
+ else if (tag === 8) obj.encrypted_client_id = EncryptedClientIdentification.read(pbf, pbf.readVarint() + pbf.pos);
+};
+LicenseRequest.write = function (obj, pbf) {
+ if (obj.client_id) pbf.writeMessage(1, ClientIdentification.write, obj.client_id);
+ if (obj.content_id) pbf.writeMessage(2, LicenseRequest.ContentIdentification.write, obj.content_id);
+ if (obj.type) pbf.writeVarintField(3, obj.type);
+ if (obj.request_time) pbf.writeVarintField(4, obj.request_time);
+ if (obj.key_control_nonce_deprecated) pbf.writeBytesField(5, obj.key_control_nonce_deprecated);
+ if (obj.protocol_version != undefined && obj.protocol_version !== {"value":20,"options":{}}) pbf.writeVarintField(6, obj.protocol_version);
+ if (obj.key_control_nonce) pbf.writeVarintField(7, obj.key_control_nonce);
+ if (obj.encrypted_client_id) pbf.writeMessage(8, EncryptedClientIdentification.write, obj.encrypted_client_id);
+};
+
+LicenseRequest.RequestType = {
+ "NEW": {
+ "value": 1,
+ "options": {}
+ },
+ "RENEWAL": {
+ "value": 2,
+ "options": {}
+ },
+ "RELEASE": {
+ "value": 3,
+ "options": {}
+ }
+};
+
+// LicenseRequest.ContentIdentification ========================================
+
+LicenseRequest.ContentIdentification = {};
+
+LicenseRequest.ContentIdentification.read = function (pbf, end) {
+ return pbf.readFields(LicenseRequest.ContentIdentification._readField, {cenc_id: null, webm_id: null, license: null}, end);
+};
+LicenseRequest.ContentIdentification._readField = function (tag, obj, pbf) {
+ if (tag === 1) obj.cenc_id = LicenseRequest.ContentIdentification.CENC.read(pbf, pbf.readVarint() + pbf.pos);
+ else if (tag === 2) obj.webm_id = LicenseRequest.ContentIdentification.WebM.read(pbf, pbf.readVarint() + pbf.pos);
+ else if (tag === 3) obj.license = LicenseRequest.ContentIdentification.ExistingLicense.read(pbf, pbf.readVarint() + pbf.pos);
+};
+LicenseRequest.ContentIdentification.write = function (obj, pbf) {
+ if (obj.cenc_id) pbf.writeMessage(1, LicenseRequest.ContentIdentification.CENC.write, obj.cenc_id);
+ if (obj.webm_id) pbf.writeMessage(2, LicenseRequest.ContentIdentification.WebM.write, obj.webm_id);
+ if (obj.license) pbf.writeMessage(3, LicenseRequest.ContentIdentification.ExistingLicense.write, obj.license);
+};
+
+// LicenseRequest.ContentIdentification.CENC ========================================
+
+LicenseRequest.ContentIdentification.CENC = {};
+
+LicenseRequest.ContentIdentification.CENC.read = function (pbf, end) {
+ return pbf.readFields(LicenseRequest.ContentIdentification.CENC._readField, {pssh: [], license_type: 0, request_id: null}, end);
+};
+LicenseRequest.ContentIdentification.CENC._readField = function (tag, obj, pbf) {
+ if (tag === 1) obj.pssh.push(pbf.readBytes());
+ else if (tag === 2) obj.license_type = pbf.readVarint();
+ else if (tag === 3) obj.request_id = pbf.readBytes();
+};
+LicenseRequest.ContentIdentification.CENC.write = function (obj, pbf) {
+ if (obj.pssh) for (var i = 0; i < obj.pssh.length; i++) pbf.writeBytesField(1, obj.pssh[i]);
+ if (obj.license_type) pbf.writeVarintField(2, obj.license_type);
+ if (obj.request_id) pbf.writeBytesField(3, obj.request_id);
+};
+
+// LicenseRequest.ContentIdentification.WebM ========================================
+
+LicenseRequest.ContentIdentification.WebM = {};
+
+LicenseRequest.ContentIdentification.WebM.read = function (pbf, end) {
+ return pbf.readFields(LicenseRequest.ContentIdentification.WebM._readField, {header: null, license_type: 0, request_id: null}, end);
+};
+LicenseRequest.ContentIdentification.WebM._readField = function (tag, obj, pbf) {
+ if (tag === 1) obj.header = pbf.readBytes();
+ else if (tag === 2) obj.license_type = pbf.readVarint();
+ else if (tag === 3) obj.request_id = pbf.readBytes();
+};
+LicenseRequest.ContentIdentification.WebM.write = function (obj, pbf) {
+ if (obj.header) pbf.writeBytesField(1, obj.header);
+ if (obj.license_type) pbf.writeVarintField(2, obj.license_type);
+ if (obj.request_id) pbf.writeBytesField(3, obj.request_id);
+};
+
+// LicenseRequest.ContentIdentification.ExistingLicense ========================================
+
+LicenseRequest.ContentIdentification.ExistingLicense = {};
+
+LicenseRequest.ContentIdentification.ExistingLicense.read = function (pbf, end) {
+ return pbf.readFields(LicenseRequest.ContentIdentification.ExistingLicense._readField, {license_id: null, seconds_since_started: 0, seconds_since_last_played: 0, session_usage_table_entry: null}, end);
+};
+LicenseRequest.ContentIdentification.ExistingLicense._readField = function (tag, obj, pbf) {
+ if (tag === 1) obj.license_id = LicenseIdentification.read(pbf, pbf.readVarint() + pbf.pos);
+ else if (tag === 2) obj.seconds_since_started = pbf.readVarint(true);
+ else if (tag === 3) obj.seconds_since_last_played = pbf.readVarint(true);
+ else if (tag === 4) obj.session_usage_table_entry = pbf.readBytes();
+};
+LicenseRequest.ContentIdentification.ExistingLicense.write = function (obj, pbf) {
+ if (obj.license_id) pbf.writeMessage(1, LicenseIdentification.write, obj.license_id);
+ if (obj.seconds_since_started) pbf.writeVarintField(2, obj.seconds_since_started);
+ if (obj.seconds_since_last_played) pbf.writeVarintField(3, obj.seconds_since_last_played);
+ if (obj.session_usage_table_entry) pbf.writeBytesField(4, obj.session_usage_table_entry);
+};
+
+// LicenseError ========================================
+
+var LicenseError = self.LicenseError = {};
+
+LicenseError.read = function (pbf, end) {
+ return pbf.readFields(LicenseError._readField, {error_code: 0}, end);
+};
+LicenseError._readField = function (tag, obj, pbf) {
+ if (tag === 1) obj.error_code = pbf.readVarint();
+};
+LicenseError.write = function (obj, pbf) {
+ if (obj.error_code) pbf.writeVarintField(1, obj.error_code);
+};
+
+LicenseError.Error = {
+ "INVALID_DEVICE_CERTIFICATE": {
+ "value": 1,
+ "options": {}
+ },
+ "REVOKED_DEVICE_CERTIFICATE": {
+ "value": 2,
+ "options": {}
+ },
+ "SERVICE_UNAVAILABLE": {
+ "value": 3,
+ "options": {}
+ }
+};
+
+// RemoteAttestation ========================================
+
+var RemoteAttestation = self.RemoteAttestation = {};
+
+RemoteAttestation.read = function (pbf, end) {
+ return pbf.readFields(RemoteAttestation._readField, {certificate: null, salt: null, signature: null}, end);
+};
+RemoteAttestation._readField = function (tag, obj, pbf) {
+ if (tag === 1) obj.certificate = EncryptedClientIdentification.read(pbf, pbf.readVarint() + pbf.pos);
+ else if (tag === 2) obj.salt = pbf.readBytes();
+ else if (tag === 3) obj.signature = pbf.readBytes();
+};
+RemoteAttestation.write = function (obj, pbf) {
+ if (obj.certificate) pbf.writeMessage(1, EncryptedClientIdentification.write, obj.certificate);
+ if (obj.salt) pbf.writeBytesField(2, obj.salt);
+ if (obj.signature) pbf.writeBytesField(3, obj.signature);
+};
+
+// SignedMessage ========================================
+
+var SignedMessage = self.SignedMessage = {};
+
+SignedMessage.read = function (pbf, end) {
+ return pbf.readFields(SignedMessage._readField, {type: 0, msg: null, signature: null, session_key: null, remote_attestation: null}, end);
+};
+SignedMessage._readField = function (tag, obj, pbf) {
+ if (tag === 1) obj.type = pbf.readVarint();
+ else if (tag === 2) obj.msg = pbf.readBytes();
+ else if (tag === 3) obj.signature = pbf.readBytes();
+ else if (tag === 4) obj.session_key = pbf.readBytes();
+ else if (tag === 5) obj.remote_attestation = RemoteAttestation.read(pbf, pbf.readVarint() + pbf.pos);
+};
+SignedMessage.write = function (obj, pbf) {
+ if (obj.type) pbf.writeVarintField(1, obj.type);
+ if (obj.msg) pbf.writeBytesField(2, obj.msg);
+ if (obj.signature) pbf.writeBytesField(3, obj.signature);
+ if (obj.session_key) pbf.writeBytesField(4, obj.session_key);
+ if (obj.remote_attestation) pbf.writeMessage(5, RemoteAttestation.write, obj.remote_attestation);
+};
+
+SignedMessage.MessageType = {
+ "LICENSE_REQUEST": {
+ "value": 1,
+ "options": {}
+ },
+ "LICENSE": {
+ "value": 2,
+ "options": {}
+ },
+ "ERROR_RESPONSE": {
+ "value": 3,
+ "options": {}
+ },
+ "SERVICE_CERTIFICATE_REQUEST": {
+ "value": 4,
+ "options": {}
+ },
+ "SERVICE_CERTIFICATE": {
+ "value": 5,
+ "options": {}
+ }
+};
+
+// ProvisioningOptions ========================================
+
+var ProvisioningOptions = self.ProvisioningOptions = {};
+
+ProvisioningOptions.read = function (pbf, end) {
+ return pbf.readFields(ProvisioningOptions._readField, {certificate_type: 0, certificate_authority: ""}, end);
+};
+ProvisioningOptions._readField = function (tag, obj, pbf) {
+ if (tag === 1) obj.certificate_type = pbf.readVarint();
+ else if (tag === 2) obj.certificate_authority = pbf.readString();
+};
+ProvisioningOptions.write = function (obj, pbf) {
+ if (obj.certificate_type) pbf.writeVarintField(1, obj.certificate_type);
+ if (obj.certificate_authority) pbf.writeStringField(2, obj.certificate_authority);
+};
+
+ProvisioningOptions.CertificateType = {
+ "WIDEVINE_DRM": {
+ "value": 0,
+ "options": {}
+ },
+ "X509": {
+ "value": 1,
+ "options": {}
+ }
+};
+
+// ProvisioningRequest ========================================
+
+var ProvisioningRequest = self.ProvisioningRequest = {};
+
+ProvisioningRequest.read = function (pbf, end) {
+ return pbf.readFields(ProvisioningRequest._readField, {client_id: null, nonce: null, options: null, stable_id: null}, end);
+};
+ProvisioningRequest._readField = function (tag, obj, pbf) {
+ if (tag === 1) obj.client_id = ClientIdentification.read(pbf, pbf.readVarint() + pbf.pos);
+ else if (tag === 2) obj.nonce = pbf.readBytes();
+ else if (tag === 3) obj.options = ProvisioningOptions.read(pbf, pbf.readVarint() + pbf.pos);
+ else if (tag === 4) obj.stable_id = pbf.readBytes();
+};
+ProvisioningRequest.write = function (obj, pbf) {
+ if (obj.client_id) pbf.writeMessage(1, ClientIdentification.write, obj.client_id);
+ if (obj.nonce) pbf.writeBytesField(2, obj.nonce);
+ if (obj.options) pbf.writeMessage(3, ProvisioningOptions.write, obj.options);
+ if (obj.stable_id) pbf.writeBytesField(4, obj.stable_id);
+};
+
+// ProvisioningResponse ========================================
+
+var ProvisioningResponse = self.ProvisioningResponse = {};
+
+ProvisioningResponse.read = function (pbf, end) {
+ return pbf.readFields(ProvisioningResponse._readField, {device_rsa_key: null, device_rsa_key_iv: null, device_certificate: null, nonce: null}, end);
+};
+ProvisioningResponse._readField = function (tag, obj, pbf) {
+ if (tag === 1) obj.device_rsa_key = pbf.readBytes();
+ else if (tag === 2) obj.device_rsa_key_iv = pbf.readBytes();
+ else if (tag === 3) obj.device_certificate = pbf.readBytes();
+ else if (tag === 4) obj.nonce = pbf.readBytes();
+};
+ProvisioningResponse.write = function (obj, pbf) {
+ if (obj.device_rsa_key) pbf.writeBytesField(1, obj.device_rsa_key);
+ if (obj.device_rsa_key_iv) pbf.writeBytesField(2, obj.device_rsa_key_iv);
+ if (obj.device_certificate) pbf.writeBytesField(3, obj.device_certificate);
+ if (obj.nonce) pbf.writeBytesField(4, obj.nonce);
+};
+
+// SignedProvisioningMessage ========================================
+
+var SignedProvisioningMessage = self.SignedProvisioningMessage = {};
+
+SignedProvisioningMessage.read = function (pbf, end) {
+ return pbf.readFields(SignedProvisioningMessage._readField, {message: null, signature: null}, end);
+};
+SignedProvisioningMessage._readField = function (tag, obj, pbf) {
+ if (tag === 1) obj.message = pbf.readBytes();
+ else if (tag === 2) obj.signature = pbf.readBytes();
+};
+SignedProvisioningMessage.write = function (obj, pbf) {
+ if (obj.message) pbf.writeBytesField(1, obj.message);
+ if (obj.signature) pbf.writeBytesField(2, obj.signature);
+};
+
+// ClientIdentification ========================================
+
+var ClientIdentification = self.ClientIdentification = {};
+
+ClientIdentification.read = function (pbf, end) {
+ return pbf.readFields(ClientIdentification._readField, {type: {"value":0,"options":{}}, token: null, client_info: [], provider_client_token: null, license_counter: 0, client_capabilities: null}, end);
+};
+ClientIdentification._readField = function (tag, obj, pbf) {
+ if (tag === 1) obj.type = pbf.readVarint();
+ else if (tag === 2) obj.token = pbf.readBytes();
+ else if (tag === 3) obj.client_info.push(ClientIdentification.NameValue.read(pbf, pbf.readVarint() + pbf.pos));
+ else if (tag === 4) obj.provider_client_token = pbf.readBytes();
+ else if (tag === 5) obj.license_counter = pbf.readVarint();
+ else if (tag === 6) obj.client_capabilities = ClientIdentification.ClientCapabilities.read(pbf, pbf.readVarint() + pbf.pos);
+};
+ClientIdentification.write = function (obj, pbf) {
+ if (obj.type != undefined && obj.type !== {"value":0,"options":{}}) pbf.writeVarintField(1, obj.type);
+ if (obj.token) pbf.writeBytesField(2, obj.token);
+ if (obj.client_info) for (var i = 0; i < obj.client_info.length; i++) pbf.writeMessage(3, ClientIdentification.NameValue.write, obj.client_info[i]);
+ if (obj.provider_client_token) pbf.writeBytesField(4, obj.provider_client_token);
+ if (obj.license_counter) pbf.writeVarintField(5, obj.license_counter);
+ if (obj.client_capabilities) pbf.writeMessage(6, ClientIdentification.ClientCapabilities.write, obj.client_capabilities);
+};
+
+ClientIdentification.TokenType = {
+ "KEYBOX": {
+ "value": 0,
+ "options": {}
+ },
+ "DEVICE_CERTIFICATE": {
+ "value": 1,
+ "options": {}
+ },
+ "REMOTE_ATTESTATION_CERTIFICATE": {
+ "value": 2,
+ "options": {}
+ }
+};
+
+// ClientIdentification.NameValue ========================================
+
+ClientIdentification.NameValue = {};
+
+ClientIdentification.NameValue.read = function (pbf, end) {
+ return pbf.readFields(ClientIdentification.NameValue._readField, {name: "", value: ""}, end);
+};
+ClientIdentification.NameValue._readField = function (tag, obj, pbf) {
+ if (tag === 1) obj.name = pbf.readString();
+ else if (tag === 2) obj.value = pbf.readString();
+};
+ClientIdentification.NameValue.write = function (obj, pbf) {
+ if (obj.name) pbf.writeStringField(1, obj.name);
+ if (obj.value) pbf.writeStringField(2, obj.value);
+};
+
+// ClientIdentification.ClientCapabilities ========================================
+
+ClientIdentification.ClientCapabilities = {};
+
+ClientIdentification.ClientCapabilities.read = function (pbf, end) {
+ return pbf.readFields(ClientIdentification.ClientCapabilities._readField, {client_token: false, session_token: false, video_resolution_constraints: false, max_hdcp_version: {"value":0,"options":{}}, oem_crypto_api_version: 0, anti_rollback_usage_table: false}, end);
+};
+ClientIdentification.ClientCapabilities._readField = function (tag, obj, pbf) {
+ if (tag === 1) obj.client_token = pbf.readBoolean();
+ else if (tag === 2) obj.session_token = pbf.readBoolean();
+ else if (tag === 3) obj.video_resolution_constraints = pbf.readBoolean();
+ else if (tag === 4) obj.max_hdcp_version = pbf.readVarint();
+ else if (tag === 5) obj.oem_crypto_api_version = pbf.readVarint();
+ else if (tag === 6) obj.anti_rollback_usage_table = pbf.readBoolean();
+};
+ClientIdentification.ClientCapabilities.write = function (obj, pbf) {
+ if (obj.client_token) pbf.writeBooleanField(1, obj.client_token);
+ if (obj.session_token) pbf.writeBooleanField(2, obj.session_token);
+ if (obj.video_resolution_constraints) pbf.writeBooleanField(3, obj.video_resolution_constraints);
+ if (obj.max_hdcp_version != undefined && obj.max_hdcp_version !== {"value":0,"options":{}}) pbf.writeVarintField(4, obj.max_hdcp_version);
+ if (obj.oem_crypto_api_version) pbf.writeVarintField(5, obj.oem_crypto_api_version);
+ if (obj.anti_rollback_usage_table) pbf.writeBooleanField(6, obj.anti_rollback_usage_table);
+};
+
+ClientIdentification.ClientCapabilities.HdcpVersion = {
+ "HDCP_NONE": {
+ "value": 0,
+ "options": {}
+ },
+ "HDCP_V1": {
+ "value": 1,
+ "options": {}
+ },
+ "HDCP_V2": {
+ "value": 2,
+ "options": {}
+ },
+ "HDCP_V2_1": {
+ "value": 3,
+ "options": {}
+ },
+ "HDCP_V2_2": {
+ "value": 4,
+ "options": {}
+ },
+ "HDCP_NO_DIGITAL_OUTPUT": {
+ "value": 255,
+ "options": {}
+ }
+};
+
+// EncryptedClientIdentification ========================================
+
+var EncryptedClientIdentification = self.EncryptedClientIdentification = {};
+
+EncryptedClientIdentification.read = function (pbf, end) {
+ return pbf.readFields(EncryptedClientIdentification._readField, {service_id: "", service_certificate_serial_number: null, encrypted_client_id: null, encrypted_client_id_iv: null, encrypted_privacy_key: null}, end);
+};
+EncryptedClientIdentification._readField = function (tag, obj, pbf) {
+ if (tag === 1) obj.service_id = pbf.readString();
+ else if (tag === 2) obj.service_certificate_serial_number = pbf.readBytes();
+ else if (tag === 3) obj.encrypted_client_id = pbf.readBytes();
+ else if (tag === 4) obj.encrypted_client_id_iv = pbf.readBytes();
+ else if (tag === 5) obj.encrypted_privacy_key = pbf.readBytes();
+};
+EncryptedClientIdentification.write = function (obj, pbf) {
+ if (obj.service_id) pbf.writeStringField(1, obj.service_id);
+ if (obj.service_certificate_serial_number) pbf.writeBytesField(2, obj.service_certificate_serial_number);
+ if (obj.encrypted_client_id) pbf.writeBytesField(3, obj.encrypted_client_id);
+ if (obj.encrypted_client_id_iv) pbf.writeBytesField(4, obj.encrypted_client_id_iv);
+ if (obj.encrypted_privacy_key) pbf.writeBytesField(5, obj.encrypted_privacy_key);
+};
+
+// DeviceCertificate ========================================
+
+var DeviceCertificate = self.DeviceCertificate = {};
+
+DeviceCertificate.read = function (pbf, end) {
+ return pbf.readFields(DeviceCertificate._readField, {type: 0, serial_number: null, creation_time_seconds: 0, public_key: null, system_id: 0, test_device_deprecated: false, service_id: ""}, end);
+};
+DeviceCertificate._readField = function (tag, obj, pbf) {
+ if (tag === 1) obj.type = pbf.readVarint();
+ else if (tag === 2) obj.serial_number = pbf.readBytes();
+ else if (tag === 3) obj.creation_time_seconds = pbf.readVarint();
+ else if (tag === 4) obj.public_key = pbf.readBytes();
+ else if (tag === 5) obj.system_id = pbf.readVarint();
+ else if (tag === 6) obj.test_device_deprecated = pbf.readBoolean();
+ else if (tag === 7) obj.service_id = pbf.readString();
+};
+DeviceCertificate.write = function (obj, pbf) {
+ if (obj.type) pbf.writeVarintField(1, obj.type);
+ if (obj.serial_number) pbf.writeBytesField(2, obj.serial_number);
+ if (obj.creation_time_seconds) pbf.writeVarintField(3, obj.creation_time_seconds);
+ if (obj.public_key) pbf.writeBytesField(4, obj.public_key);
+ if (obj.system_id) pbf.writeVarintField(5, obj.system_id);
+ if (obj.test_device_deprecated) pbf.writeBooleanField(6, obj.test_device_deprecated);
+ if (obj.service_id) pbf.writeStringField(7, obj.service_id);
+};
+
+DeviceCertificate.CertificateType = {
+ "ROOT": {
+ "value": 0,
+ "options": {}
+ },
+ "INTERMEDIATE": {
+ "value": 1,
+ "options": {}
+ },
+ "USER_DEVICE": {
+ "value": 2,
+ "options": {}
+ },
+ "SERVICE": {
+ "value": 3,
+ "options": {}
+ }
+};
+
+// SignedDeviceCertificate ========================================
+
+var SignedDeviceCertificate = self.SignedDeviceCertificate = {};
+
+SignedDeviceCertificate.read = function (pbf, end) {
+ return pbf.readFields(SignedDeviceCertificate._readField, {device_certificate: null, signature: null, signer: null}, end);
+};
+SignedDeviceCertificate._readField = function (tag, obj, pbf) {
+ if (tag === 1) obj.device_certificate = pbf.readBytes();
+ else if (tag === 2) obj.signature = pbf.readBytes();
+ else if (tag === 3) obj.signer = SignedDeviceCertificate.read(pbf, pbf.readVarint() + pbf.pos);
+};
+SignedDeviceCertificate.write = function (obj, pbf) {
+ if (obj.device_certificate) pbf.writeBytesField(1, obj.device_certificate);
+ if (obj.signature) pbf.writeBytesField(2, obj.signature);
+ if (obj.signer) pbf.writeMessage(3, SignedDeviceCertificate.write, obj.signer);
+};
+
+// ProvisionedDeviceInfo ========================================
+
+var ProvisionedDeviceInfo = self.ProvisionedDeviceInfo = {};
+
+ProvisionedDeviceInfo.read = function (pbf, end) {
+ return pbf.readFields(ProvisionedDeviceInfo._readField, {system_id: 0, soc: "", manufacturer: "", model: "", device_type: "", model_year: 0, security_level: {"value":0,"options":{}}, test_device: false}, end);
+};
+ProvisionedDeviceInfo._readField = function (tag, obj, pbf) {
+ if (tag === 1) obj.system_id = pbf.readVarint();
+ else if (tag === 2) obj.soc = pbf.readString();
+ else if (tag === 3) obj.manufacturer = pbf.readString();
+ else if (tag === 4) obj.model = pbf.readString();
+ else if (tag === 5) obj.device_type = pbf.readString();
+ else if (tag === 6) obj.model_year = pbf.readVarint();
+ else if (tag === 7) obj.security_level = pbf.readVarint();
+ else if (tag === 8) obj.test_device = pbf.readBoolean();
+};
+ProvisionedDeviceInfo.write = function (obj, pbf) {
+ if (obj.system_id) pbf.writeVarintField(1, obj.system_id);
+ if (obj.soc) pbf.writeStringField(2, obj.soc);
+ if (obj.manufacturer) pbf.writeStringField(3, obj.manufacturer);
+ if (obj.model) pbf.writeStringField(4, obj.model);
+ if (obj.device_type) pbf.writeStringField(5, obj.device_type);
+ if (obj.model_year) pbf.writeVarintField(6, obj.model_year);
+ if (obj.security_level != undefined && obj.security_level !== {"value":0,"options":{}}) pbf.writeVarintField(7, obj.security_level);
+ if (obj.test_device) pbf.writeBooleanField(8, obj.test_device);
+};
+
+ProvisionedDeviceInfo.WvSecurityLevel = {
+ "LEVEL_UNSPECIFIED": {
+ "value": 0,
+ "options": {}
+ },
+ "LEVEL_1": {
+ "value": 1,
+ "options": {}
+ },
+ "LEVEL_2": {
+ "value": 2,
+ "options": {}
+ },
+ "LEVEL_3": {
+ "value": 3,
+ "options": {}
+ }
+};
+
+// DeviceCertificateStatus ========================================
+
+var DeviceCertificateStatus = self.DeviceCertificateStatus = {};
+
+DeviceCertificateStatus.read = function (pbf, end) {
+ return pbf.readFields(DeviceCertificateStatus._readField, {serial_number: null, status: {"value":0,"options":{}}, device_info: null}, end);
+};
+DeviceCertificateStatus._readField = function (tag, obj, pbf) {
+ if (tag === 1) obj.serial_number = pbf.readBytes();
+ else if (tag === 2) obj.status = pbf.readVarint();
+ else if (tag === 4) obj.device_info = ProvisionedDeviceInfo.read(pbf, pbf.readVarint() + pbf.pos);
+};
+DeviceCertificateStatus.write = function (obj, pbf) {
+ if (obj.serial_number) pbf.writeBytesField(1, obj.serial_number);
+ if (obj.status != undefined && obj.status !== {"value":0,"options":{}}) pbf.writeVarintField(2, obj.status);
+ if (obj.device_info) pbf.writeMessage(4, ProvisionedDeviceInfo.write, obj.device_info);
+};
+
+DeviceCertificateStatus.CertificateStatus = {
+ "VALID": {
+ "value": 0,
+ "options": {}
+ },
+ "REVOKED": {
+ "value": 1,
+ "options": {}
+ }
+};
+
+// DeviceCertificateStatusList ========================================
+
+var DeviceCertificateStatusList = self.DeviceCertificateStatusList = {};
+
+DeviceCertificateStatusList.read = function (pbf, end) {
+ return pbf.readFields(DeviceCertificateStatusList._readField, {creation_time_seconds: 0, certificate_status: []}, end);
+};
+DeviceCertificateStatusList._readField = function (tag, obj, pbf) {
+ if (tag === 1) obj.creation_time_seconds = pbf.readVarint();
+ else if (tag === 2) obj.certificate_status.push(DeviceCertificateStatus.read(pbf, pbf.readVarint() + pbf.pos));
+};
+DeviceCertificateStatusList.write = function (obj, pbf) {
+ if (obj.creation_time_seconds) pbf.writeVarintField(1, obj.creation_time_seconds);
+ if (obj.certificate_status) for (var i = 0; i < obj.certificate_status.length; i++) pbf.writeMessage(2, DeviceCertificateStatus.write, obj.certificate_status[i]);
+};
+
+// SignedCertificateStatusList ========================================
+
+var SignedCertificateStatusList = self.SignedCertificateStatusList = {};
+
+SignedCertificateStatusList.read = function (pbf, end) {
+ return pbf.readFields(SignedCertificateStatusList._readField, {certificate_status_list: null, signature: null}, end);
+};
+SignedCertificateStatusList._readField = function (tag, obj, pbf) {
+ if (tag === 1) obj.certificate_status_list = pbf.readBytes();
+ else if (tag === 2) obj.signature = pbf.readBytes();
+};
+SignedCertificateStatusList.write = function (obj, pbf) {
+ if (obj.certificate_status_list) pbf.writeBytesField(1, obj.certificate_status_list);
+ if (obj.signature) pbf.writeBytesField(2, obj.signature);
+};