Authors: Jon Choukroun, Simon Gornall, Michael Hashe, Andy Pace, Marcos Cáceres.
Supporting end-to-end encryption on the web currently requires making significant compromises. Implementations may execute cryptographic operations in the client, potentially exposing a user’s private key to JavaScript, which makes keys vulnerable to exfiltration.
Alternative implementations may seek to protect the keys by executing cryptographic operations in other environments, such as the server, which forfeits the guarantees expected from end-to-end encryption.
Finally, implementations may preserve the security and ownership of private keys, which can complicate the user experience (e.g., by requiring the installation of browser extensions).
We identified 4 requirements that informed the design of this proposal:
The rest of this document illustrates how end-to-end encryption on the web can be supported using minimal extensions to the Web Cryptography API to support executing cryptographic operations (encrypt, decrypt, sign, and verify) using keys from a user’s secure key store.

We propose a mechanism through which a web application can request that a cryptographic operation be executed on a given data chunk, using the following process:
The web application first acquires an instance of a “remote” CryptoKey by calling getRemoteKey(), detailed below.
Note: This is analogous to calling generateKey() to instantiate a local CryptoKey.
CryptoKey as an argument to methods in the .subtle namespace, along with the data to sign, decrypt, etc.CryptoKey and data to the platform. This may be the default key store, an entity managing one or more key stores, or some other process running on the operating system.
RemoteKeyParams DictionaryWe propose defining a kind of “remote” key that is usable with CryptoKey ‘s [[algorithm]] internal slot. That is, the key material exists outside the browser on the platform or elsewhere. This may be a secure key store, password manager, USB or BlueTooth device, etc.
When creating a remote instance of a CryptoKey, the browser sets its attributes accordingly:
extractable attribute is always false.[[algorithm]] internal slot, we define aRemoteKeyParams dictionary with the following members:// The [[algorithm]] internal slot takes a RemoteKeyParams
dictionary RemoteKeyParams : Algorithm {
DOMString userIdentifier;
DOMString? keyId;
DOMHighResTimeStamp? expiresAt;
}
name member (of Algorithm): "remote" - indicates the key material is “remote”.userIdentifier: used by the key store when searching for the key (e.g., an email address).keyId? : may be returned from getRemoteKey() to be used for subsequent key lookups.expiresAt: - a timestamp indicating when the returned key handle will no longer be honored.SubtleCrypto InterfaceThe intention of this proposal is to leverage the existing SubtleCrypto namespace. A remote instance of the CryptoKey can be passed as the key argument to existing cryptographic methods without requiring an API change.
To support getting a CryptoKey handle to remote key material, we propose a new method, an alternative to the existing generateKey() function. This method takes a RemoteKeyParams dictionary (see above) and an array of key usages. Because remote keys are never extractable, we omit the extractable boolean parameter from the method signature:
Promise<CryptoKey> getRemoteKey(
RemoteKeyParams params, sequence<KeyUsage> keyUsages
);
We propose the following implementation of getRemoteKey():
getRemoteKey() receives RemoteKeyParams and keyUsages parameters, otherwise returns a rejected Promise with some error.NotFoundError.CryptoKey instance, setting the identifier returned from the platform as the keyId member, and the expiration time as the expiresAt member - if provided.CryptoKey object.Getting a CryptoKey handle, to be later used to sign an email message body.
const keyHandle = await window.crypto.subtle.getRemoteKey(
{
name: "remote",
userIdentifier: "alice@example.com"
},
["sign"]
);
console.log(keyHandle.algorithm.keyId) // a platform-unique identifier
console.log(keyHandle.algorithm.expiresAt) // a timestamp 3 hours from now
console.log(keyHandle.extractable) // always "false"
console.log(keyHandle.usages) // "sign"
Using the previously retrieved key handle to sign an email message.
// Dummy function that assembles message contents into a bytes array
// @type ArrayBuffer
const message = assembleMessage();
// @type ArrayBuffer
const signature = await window.crypto.subtle.sign(
{ name: "RSASSA-PKCS1-v1_5" },
keyHandle,
message
);
This proposal focuses on obtaining access to key handles, but does not dictate the access control and user consent checks that the platform should enforce. Instead we will list concerns and possible mitigations (where possible) that a platform SHOULD implement to better protect these keys.