NetflixWebCryptoUseCase
SUMMARY
Netflix would like to implement a fast, lightweight, secure application protocol for communication between a Netflix client implemented in Javascript, running in a browser or other HTML/CSS/JS-based platform and Netflix servers (running on a cloud computing platform). Authentication may be bootstrapped from some pre-provisioned credential bound to the device. This secure protocol is then used for functions such as authorization to play movies, device registration, reporting of usage statistics, etc.
DETAILS
Key Store and Key Naming
This document assumes the availability of key storage in the browser or browser framework. This “key store” must not allow Javascript code to access the raw bytes of a key (public keys being a notable exception) but instead marshals requests for cryptographic operations using the stored keys. In most cases, it will be desirable to have a per-origin key store. The key named “MySecretKey” used by an app running from “foo.com” may not be accessed by an app running from “bar.com”. Further, “bar.com” may have its own key named “MySecretKey” without conflicting with the key of the same name for domain “foo.com”.
Each key has a unique local identifier used to refer to it over the Javascript API. In this document we assume this is simply a string value chosen by the calling code. Additionally, the key store allows keys to be added, deleted, etc.
At a minimum the key store should allow the following:
Function | Description |
exists(keyName) | returns a boolean notifying the caller if such named key exists |
length(keyName) | returns the length of the key |
delete(keyName) | deletes the named key |
generate(keyName, size) | generate the named key with the given size |
For the sake of simplicity, this document references keys by names in calls to functions like encrypt(). An actual API definition may want to have more flexibility, supporting a first class KeyHandle object, for example.
Key Deployment
This document assumes that every cryptographic key is one of the following types:
- Pre-provisioned keys. These keys are pre-shared or field-provisioned keys which are put into a key store by a device manufacturer, by an individual user, an enterprise IT staff or some other entity. These keys already exist prior to the running of apps that use them. Note that the distinction between pre-shared and field-provisioned keys is fuzzy enough in some cases to simply lump them all together into “pre-provisioned”: a manufacturer may burn “pre-shared” keys into a device’s ROM, but later decide to field provision a new set of keys via firmware update. In this case, the app writer doesn’t really care whether the keys were “pre-shared” or “field-provisioned”; all that matter is that the keys are “pre-provisioned”.
- Session keys. These keys are exchanged, derived or otherwise created at runtime by the application using them.
The APIs in this document are agnostic to the type of key deployment used. In other words, a cryptographic operation using a pre-provisioned key looks just like a cryptographic operation using a session key.
Continuity of Device / Key Identification
When pre-provisioned symmetric keys are used, some kind of globally unique identifier must also be provided so that services can look up the key in their own server-side database. For public-private key pairs, the same approach could be used, or a certificate could be used.
As noted in the above description of pre-provisioned keys, keys may change at the whim of a device manufacturer, network operator, etc. We have seen multiple examples of keys changing due to firmware updates, creation of short-lived secondary credentials (IPSEC with pre-shared keys leveraged to affect an X.509 certificate enrollment, where resultant certs were only valid for a number of hours), swapping of cryptographic hardware tokens, etc. It can be expected that services which use such keys are notified of the new keys before they change. However, when keys change in such a manner, we must be able to identify a device across such key changes to maintain continuity of services.
To achieve this continuity, at present, we use a per-device, per-domain unique identifier which is constant. This approach may be fine for devices, but for a browser the constant nature of this identifier may not be possible. In this case, so long as there is a “key store ID” or some such per-browser per-domain identifier that does not change unless a user reinstalls their browser, we achieve the same goal.
To create commonality in both the device & browser contexts, we propose a common API to access such an identifier.
Message Security
Each message sent to and received from Netflix servers is authenticated, integrity protected and (optionally) encrypted using a lightweight arbitrary security message envelope, referred to here as MsgSec.
Security Prior to Bootstrapping Session Keys & Tokens
Before a device or browser has a session key and a session token, the MsgSec protocol wrapper will rely on either pre-provisioned keys or runtime generated keys which are associated to a domain specific unique identifier.
Example Request
The following table describes a MsgSec wrapped request message from the client when using pre-provisioned or runtime generated keys. (The packaging of this data into an over-the-wire format is beyond the scope of this document.)
Element | Example Value | Description |
MsgType | “request” | “request” (or) “response” |
ID | “client-12345” | unique identifier for the client browser or device |
Msg | “jIPx3TCNguQ=” | Base-64 encoded encrypted message |
MAC | “0D2Yeh/jbX2GXYAKHrLBGQ==” | Base-64 encoded MAC |
The “ID” field may be a domain specific GUID, an actual hardware identifier or some other device and/or browser identifier which is guaranteed unique. The “Msg” field is an arbitrary message which may be encrypted. The “MAC” is a message authentication code.
In this example, the WebCrypto APIs are used to:
- Generate the “MAC” message authentication code on the request using a pre-provisioned or runtime generated, client MAC key.
- Encrypt the “Msg” using a pre-provisioned or runtime generated symmetric key.
To support creation of the above message, we might do something like this:
// In this example, we use the following webcrypto APIs: // webcrypto.encrypt(msg, keyName, cipherAlgorithm) // webcrypto.mac(msg, keyName, macAlgorithm) // wrap a request using pre-shared or runtime generated keys // msg: the message to wrap // id: the client ID function wrapMsg(msg, id) { // encrypt the msg var encMsg = webcrypto.encrypt(msg, “ClientEncryptionKey”, “aes-128-cbc”); // create a partial message that we will MAC // make it “name=value” pairs with comma delimiters for simplicity var partialMsg = “MsgType=request,ID=”+id+“,Msg=”+encMsg; // create the MAC var mac = webcrypto.mac(partialMsg, “ClientMACKey”, “HmacSha256”); // create complete message with MAC var completeMsg = partialMsg+”,MAC=”+mac; // return the encrypted & MAC’d message to the caller return completeMsg; }
Example Response
The following table describes a MsgSec wrapped response message from the server when using pre-provisioned or runtime generated keys.
Element | Example Value | Description |
MsgType | “response” | “request” (or) “response” |
Msg | “jIPx3TCNguQ=” | Base-64 encoded encrypted message |
MAC | “0D2Yeh/jbX2GXYAKHrLBGQ==” | Base-64 encoded MAC |
In this example, the WebCrypto APIs are used to:
- Verify the “MAC” using a pre-provisioned or runtime generated server MAC key.
- Decrypt the “Msg” using a pre-provisioned or runtime generated server symmetric key.
To support unwrapping this response from the server, we might do something like this:
// In this example, we use the following webcrypto APIs: // webcrypto.decrypt(encMsg, keyName, cipherAlgorithm) // webcrypto.mac(msg, keyName, macAlgorithm) // unwrap a response using pre-shared or runtime generated keys // msg: the encrypted message to unwrap function unwrapMsg(msg) { // parse name/value pairs here // output vars: msgType, msg, mac // create a partial message to MAC var partialMsg = “MsgType=”+msgType+”,Msg=”+msg // mac the partial msg var genMac = webcrypto.mac(partialMsg, “ServerHMACKey”, “HmacSha256”); // compare the two MACs if(genMac != mac) { error(); } // decrypt the msg var decMsg = webcrypto.decrypt(msg, “ServerEncryptionKey”, “aes-128-cbc”); // return our decrypted, MAC-verified msg return decMsg; }
Using Session Keys & Tokens
After provisioning session keys & a session token (using either pre-provisioned keys or runtime generated keys) we can use these credentials to provide essentially the same security wrapper as above. The following is a request from client to server:
Example Request
The following table describes a MsgSec wrapped request message from the client when using session keys.
Element | Example Value | Description |
MsgType | “request” | “request” (or) “response” |
ID | “client-12345” | unique identifier for the client browser or device |
Msg | “jIPx3TCNguQ=” | Base-64 encoded encrypted message |
MAC | “0D2Yeh/jbX2GXYAKHrLBGQ==” | Base-64 encoded MAC |
Notice that this request is identical to the pre-shared / generated keys example shown above. The example Javascript code is identical to that shown above (using the same two WebCrypto API calls) except that the key names are now the relevant identifiers for the session keys instead of the pre-shared or generated keys.
Example Response
The following table describes a MsgSec wrapped response message from the server when using session keys.
Element | Example Value | Description |
MsgType | “response” | “request” (or) “response” |
Msg | “jIPx3TCNguQ=” | Base-64 encoded encrypted message |
MAC | “0D2Yeh/jbX2GXYAKHrLBGQ==” | Base-64 encoded MAC |
Again, this example response is identical to the previous example response using pre-shared / generated keys, except that the names of the keys would change to use the relevant identifiers for the session keys.
Key Exchange
In order to create session keys, both client and server must have a pre-defined key exchange mechanism. We propose two possible mechanisms:
- Diffie-Hellman
- Key Unwrapping
Note in the following mechanisms that device authentication is not specifically addressed. It assumed that device authentication -- to prevent MiM attacks in the case of Diffie-Hellman, for example -- is handled by some other mechanism. In our case, we will use HMAC, as described above, to accomplish device authentication and avoid such MiM attacks.
Key Unwrapping Key Exchange
Key unwrapping is the process of a new key being provisioned by encrypting the new key with a known key. The “wrapped key” is the newly created key that is sent to a receiver. The “wrapping key” can be a pre-shared symmetric key, a symmetric session key, or an RSA or ECC public key. In our examples, we concentrate on pre-shared symmetric & symmetric session keys, but these examples can easily be extended to include the public/private key cases.
Pre-Shared Symmetric Key Exchange
In the following example, Alice & Bob exchange a session key, using pre-shared key, Kab.
Item | Description |
Ks | session key |
WrappedKs | wrapped session key |
E(plaintext, key) | ‘plaintext’ encrypted with ‘key’ |
Kab | symmetric encryption key shared between Alice & Bob |
D(ciphertext, key) | ‘ciphertext’ decrypted with ‘key’ |
The following code example shows the “client” / Alice side of the above key unwrapping key exchange:
// In this example, we use the following webcrypto API: // webcrypto.unwrapKey(wrappedKey, wrappingKeyName, unwrappedKeyName) // wrappedKey: the encrypted / wrapped key we received // wrappingKeyName: the name of the pre-shared symmetric key used // to decrypt the wrappedKey // unwrappedKeyName: the name we give to the unwrapped key that we // received, stored in our key store with the given name // get a wrapped key (WrappedKs) from our server var WrappedKs = ...; // unwrap the key using pre-shared key “Kab”, giving it the name “Ks” webcrypto.unwrapKey(wrappedKs, “Kab”, “Ks”);
At this point, we can now reference “Ks” for use in cryptographic operations requiring a symmetric key.
The following code example shows the “server” / Bob side of the above key unwrapping key exchange:
// In this example, we use the following webcrypto API: // webcrypto.generateSymmetricKey(numBytes, keyName) // webcrypto.exportWrappedKey(exportKeyName, wrappingKeyName, cipherAlgorithm) // generate a 128 bit key to be wrapped webcrypto.generateSymmetricKey(/*key size in bytes*/16, /*key name*/"Ks"); // encrypt the key using Kab var wrappedKs = webcrypto.exportWrappedKey("Ks", “Kab”, “aes-128-cbc”); // now send wrappedKs to Alice
Symmetric Session Key Exchange
We can easily modify the above example to use an “old” session key (KsOld) instead of a pre-shared key (Kab) to wrap our new session key (Ks). This allows us to renew session keys at some set interval. The flows & sample code are the same as the above except that we would replace ‘Kab’ with ‘KsOld’.
Diffie-Hellman Key Exchange
(See the Wikipedia article for background.) For clarity, the basic 2-party Diffie-Hellman exchange is shown below:
Variable | Description |
p | prime |
g | generator |
a | Alice’s private value |
A | Alice’s public value |
b | Bob’s private value |
B | Bob’s public value |
ss | Shared secret |
To support Diffie-Hellman key exchange using WebCrypto, we might do something like this:
// In this example, we use the following webcrypto APIs: // DiffieHellman object ctor // DiffieHellman(p, g) // // (member function) generate() internally creates ‘a’ & returns ‘A’ // ‘a’ is never visible in Javascript // generate() // // (member function) computeSS() takes ‘B’ & calculates ‘ss’ // computeSS(B) // example usage of above APIs to create ‘ss’ var dh = new DiffieHellman(p, g); var A = dh.generate(); // we now send ‘p’, ‘g’, and ‘A’ to the server which responds with ‘B’ // after receiving ‘B’ we generate ‘ss’ which stays inside our dh object dh.computeSS(B);
At this point, we have created a shared secret which is inaccessible to Javascript, but we can’t yet do anything useful with it. In order to transform the shared secret into something usable we need to use a key derivation algorithm (RFC 2631? or something simpler?) to compress or expand the keying material ‘ss’ to keying data which is the appropriate size for some other algorithm.
This key derivation operation must be a first-class function supported by WebCrypto, because we do not want ‘ss’ to be accessible by Javascript. We might do something like this:
// In this example, we use the following webcrypto APIs: // // (DiffieHellman member function) // compress or expand ‘ss’ keying material to create a new symmetric key // saving the resultant key in the key store with the given name // transformSS(keySize, seed, keyName); // keySize: size of the resultant key in bits // seed: key uniquifier serving the purpose of RFC 2631 “OtherInfo” // keyName: the name of the new key, stored in the key store // example usage of transform() to create an AES key & an HMAC key // ‘dh’ is the DiffieHellman object from the last code snippet above // which already contains a computed ‘ss’ value. dh.transformSS(128, “aes-128-session-key”, “MyEncryptionKey”); dh.transformSS(256, “hmac-sha256-session-key”, “MyHMACKey”);
Since a Diffie-Hellman exchange normally creates a large amount of keying material (1024 bits is common) using the same shared secret to create several much smaller keys is acceptable.