NetflixWebCryptoUseCase

From W3C Wiki

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.