Harry Halpin,
<hhalpin@w3.org&g
t;
Providing secret cryptographic codes that the government couldn't spy on, was in fact a munition and this big war that we fought in the 1990s to try and make cryptography available to everyone, which we largely won, actually, which we largely won and it’s in every browser – now perhaps being backdoored and subverted in different kinds of ways...
Julian Assange
There are many things Web Crypto will not fix!
The Web Crypto API is not actually part of the Web Security Model. It adds new capacities to WebApps that were not (at all safely) available before.
Cryptography does not equal security, but is one part of an evolving eco-system. For examples of security beyond crypto, think traffic analysis!
Great blog post by Matasano Security.
What do you mean, "Javascript cryptography"? Secure delivery of Javascript to browsers is a chicken-egg problem: If you don't trust the network to deliver a password, or, worse, don't trust the server not to keep user secrets, you can't trust them to deliver security code...WebCrypto API is cross-site scripting (XSS) and cross-site request forgery (CSRF) over HTTP is not going to be fixed by the Crypto API. We're going to have to assume that you do this right and take necessary precautions (CSP+upcoming CertTrans+HSTS is a start, albeit hard). Of course, use TLS 1.2 (see IETF RFC 6176
Browser Javascript is hostile to cryptography. The malleability of the Javascript runtime...The problem with running crypto code in Javascript is that practically any function that the crypto depends on could be overridden silently by any piece of content used to build the hosting page.Again, we're going to assume you built your WebApp correctly. We realize this is hard. Is it impossible? To thwart a determined attacker, it very might be...but then they also might trojan your desktop machine as well or seize it.
What systems programming functionality does Javascript lack? A secure random number generator...secure erase (Javascript is usually garbage collected, so secrets are lurking in memory potentially long after they're needed) and functions with known timing characteristics... a secure keystoreFunctionality for cryptography is the problem we are trying to solve! In particular, a secure (pseudo)random number generator and functions with known timing chraacteristics. Secure erase is something that we'll leave to the general design of JS at ECMA...
Check back in 10 years when the majority of people aren't running browsers from 2008.The world is moving fast. Just because someone uses a system with a known security flaw does not mean we should not develop systems that address the flaw. The Web is only going to have as good security as folks put into writing, editing, coding, and reviewing specifications and open-source code. Please help!
We agree its dangerous to roll your own crypto. Crypto.cat is a great project as its terrible to use OTR messaging (BTW, also ever look at the Pidgin code), yet we need to provide the primitives to let use-cases like multi-party OTR be done securely in Crypto.cat! Right now they have no choice but to use a plug-in.
In the words of a certain security hacker, We know the browser the good stuff in it - let me get at that!
Things we can do:
Bruce Schneir states: Patrick Ball did a great job:
CryptoCat is one of a whole class of applications that rely on what's called "host-based security". The most famous tool in this group is Hushmail, an encrypted e-mail service that takes the same approach. Unfortunately, these tools are subject to a well-known attack. I'll detail it below, but the short version is if you use one of these applications, your security depends entirely the security of the host. This means that in practice, CryptoCat is no more secure than Yahoo chat, and Hushmail is no more secure than Gmail. More generally, your security in a host-based encryption system is no better than having no crypto at all.
Multi-channel WebApp communication across multiple origins as a loosely-coupled design architecture should allow both authentication and even integrity.
Very useful for authentication across multiple hosts (like signing OAuth2 tokens to verify the client rather than using symmetric shared secrets). Think cookie-snatching. Now think cookie-snatching with verifiable signatures!
Originally came out of an idea to improve authentication on identity on the Web at the public W3C Identity in the Browser Workshop in May 2011.
Three Deliverables (so far!):
Check them all out using the W3C Mercurial Repo
Luckily, the W3C Working Group does not have to write new cryptographic libraries, but rely on writing Javascript wrappers to expose routines from well-verified libraries built on top such as NSS (Chrome, Mozilla...) or OS-specific libraries (Microsoft Cryptographic API, Microsoft Next-Generation Cryptographic API)
Web Cryptography API
Likely, there will be a re-scoping of the Working Group to include hardware tokens, smart-cards, and strong authentication later in the year.
Javascript's native Math.random does not generate cryptographically strong pseudo-random numbers. This was proposed by Adam Barth to WHATWG, now going into WebCrypto API.
[NoInterfaceObject]
interface RandomSource {
ArrayBufferView getRandomValues(ArrayBufferView array);
};
The RandomSource interface represents an interface to a cryptographically strong pseudo-random number generator seeded with truly random values.
Implementations should generate cryptographically random values using well-established cryptographic pseudo-random number generators seeded with high-quality entropy, such as from an operating-system entropy source (e.g., "/dev/urandom"). Provides no lower-bound on the information theoretic entropy present in cryptographically random values, but implementations should make a best effort to provide as much entropy as practicable.
Historically, in each environment (window) Javascript is synchronous - which presents a huge problem for computationally expensive operations such as key generation, tackled by API by "Promises" style API design.
The Key object represents an opaque reference to keying material that is managed by the user agent.
enum KeyType {
"secret",
"public",
"private"
};
enum KeyUsage {
"encrypt",
"decrypt",
"sign",
"verify",
"derive"
};
interface Key {
readonly attribute KeyType type;
readonly attribute bool extractable;
readonly attribute Algorithm algorithm;
readonly attribute KeyUsage[] keyUsage;
};
type
"secret"
, while keys used as
part of asymmetric algorithms composed of public/private keypairs will be either
"public"
or "private"
.
extractable
algorithm
Algorithm
used to generate the key.
keyUsage
Array
of KeyUsages
that
indicate what CryptoOperations may be used with this
key.
Private key material is stored using a "structured clone" algorithm (thus, in IndexedDB currently, although this may change in the future. This is due to privacy reasons, rather than the original plan of using an entirely different KeyStorage that would have a different lifespan than cookies.
When a user agent is required to obtain a structured clone of a Key object, it must run the following steps.
It is important that the underlying cryptographic key material
not be exposed to a JavaScript implementation. Such a situation may arise if an implementation
fails to implement the structured clone algorithm correctly, such as by allowing a Key
object
to be serialized as part of a structured clone implementation, but then deserializing it as
a DOMString
, rather than as a Key
object.
CryptoOperation
object must have a list
of pending data. Each item in the list represents data that should be transformed by the
cryptographic operation. The list functions as a queue that observes first-in, first-out ordering. That is,
the order in which items are added shall reflect the order in which items are removed.
When a CryptoOperation is said to process data, the user agent must execute the following steps:
If there are no items in the list of pending data, the algorithm is complete.
Perform the underlying cryptographic algorithm, using bytes as the input data.
If the cryptographic operation fails, proceed to the error steps below:
Update the internal state to "error"
.
Queue a task to
fire a simple event named
onerror
at the
CryptoOperation
.
Let output be the result of the underlying cryptographic algorithm.
enum KeyFormat {
// An unformatted sequence of bytes. Intended for secret keys.
"raw",
// The DER encoding of the PrivateKeyInfo structure from RFC 5208.
"pkcs8",
// The DER encoding of the SubjectPublicKeyInfo structure from RFC 5280.
"spki",
// The key is represented as JSON according to the JSON Web Key format.
"jwk",
};
interface Crypto {
CryptoOperation encrypt(AlgorithmIdentifier algorithm,
Key key,
optional ArrayBufferView? buffer = null);
CryptoOperation decrypt(AlgorithmIdentifier algorithm,
Key key,
optional ArrayBufferView? buffer = null);
CryptoOperation sign(AlgorithmIdentifier algorithm,
Key key,
optional ArrayBufferView? buffer = null);
CryptoOperation verify(AlgorithmIdentifier algorithm,
Key key,
ArrayBufferView signature,
optional ArrayBufferView? buffer = null);
CryptoOperation digest(AlgorithmIdentifier algorithm,
optional ArrayBufferView? buffer = null);
// TBD: ISSUE-36
KeyOperation generateKey(AlgorithmIdentifier algorithm,
bool extractable = false,
KeyUsage[] keyUsages = []);
KeyOperation deriveKey(AlgorithmIdentifier algorithm,
Key baseKey,
AlgorithmIdentifier? derivedKeyType,
bool extractable = false,
KeyUsage[] keyUsages = []);
// TBD: ISSUE-35
KeyOperation importKey(KeyFormat format,
ArrayBufferView keyData,
AlgorithmIdentifier? algorithm,
bool extractable = false,
KeyUsage[] keyUsages = []);
KeyOperation exportKey(KeyFormat format, Key key);
};
CONTROVERSY: Should we allow "broken" crypto in for the sake of bakcwards compatibility (SSH)?
The Algorithm object is a dictionary object which is used to specify an algorithm and any additional parameters required to fully specify the desired operation.
typedef (Algorithm or DOMString) AlgorithmIdentifier;
dictionary AlgorithmParameters {
};
dictionary Algorithm {
DOMString name;
AlgorithmParameters params;
};
OPEN ISSUE: Should algorithms permit short-names (string identifiers as in JOSE that specify an entire ciphersuite) as equivalent to specifying Algorithm dictionaries, or should Algorithm dictionaries be the only accepted form?
Each registered algorithm MUST have a canonical name for which applications can refer to the algorithm. The canonical name MUST contain only ASCII characters and MUST NOT equal any other canonical name or algorithm alias when every character in both names are converted to lower case.
Each registered algorithm MUST define the operations that it supports.
Each registered algorithm MUST define the expected
contents of the params
member of
the Algorithm object for every
supported operation.
Each registered algorithm MUST define the normalization
rules for the contents of the params
member of the Algorithm object for every
supported operation.
Each registered algorithm MUST define the contents
of the result
attribute of the
CryptoOperation object for every
supported operation.
Encryption and decryption ordering to the RSAES-PKCS1-v1_5 algorithm specified in [RFC3447].
dictionary RsaSsaParams : AlgorithmParameters {
// The hash algorithm to use
AlgorithmIdentifier hash;
};
Signing and verification using the RSASSA-PSS algorithm specified in [RFC3447].
dictionary RsaPssParams : AlgorithmParameters {
// The hash function to apply to the message
AlgorithmIdentifier hash;
// The mask generation function
AlgorithmIdentifier mgf;
// The desired length of the random salt
unsigned long saltLength;
};
Encryption and decryption ordering to the RSAES-OAEP algorithm specified in [RFC3447].
dictionary RsaOaepParams : AlgorithmParameters {
// The hash function to apply to the message
AlgorithmIdentifier hash;
// The mask generation function
AlgorithmIdentifier mgf;
// The optional label/application data to associate with the message
ArrayBufferView? label;
};
Signing and verification using the ECDSA algorithm specified in [X9.62].
dictionary EcdsaParams : AlgorithmParameters {
// The hash algorithm to use
AlgorithmIdentifier hash;
};
Details on some named curves
enum NamedCurve {
// NIST recommended curve P-256, also known as secp256r1.
"P-256",
// NIST recommended curve P-384, also known as secp384r1.
"P-384",
// NIST recommended curve P-521, also known as secp521r1.
"P-521"
Using Elliptic Curve Diffie-Hellman (ECDH) for key generation and key agreement, as specified by X9.63.
typedef Uint8Array ECPoint;
dictionary EcdhKeyDeriveParams : AlgorithmParameters {
// The peer's EC public key.
ECPoint public;
};
dictionary AesCtrParams : AlgorithmParameters {
// The initial value of the counter block. counter MUST be 16 bytes
// (the AES block size). The counter bits are the rightmost length
// bits of the counter block. The rest of the counter block is for
// the nonce. The counter bits are incremented using the standard
// incrementing function specified in NIST SP 800-38A Appendix B.1:
// the counter bits are interpreted as a big-endian integer and
// incremented by one.
ArrayBuffer counter;
// The length, in bits, of the rightmost part of the counter block
// that is incremented.
[EnforceRange] octet length;
};
Exposed the same way AES-GCM!
dictionary AesCbcParams : AlgorithmParameters {
// The initialization vector. MUST be 16 bytes.
ArrayBufferView iv;
};
dictionary AesGcmParams : AlgorithmParameters {
// The initialization vector to use. May be up to 2^56 bytes long.
ArrayBufferView? iv;
// The additional authentication data to include.
ArrayBufferView? additionalData;
// The desired length of the authentication tag. May be 0 - 128.
[EnforceRange] octet? tagLength = 0;
};
Exposed the same way AES-GCM!
dictionary HmacParams : AlgorithmParameters {
// The inner hash function to use.
AlgorithmIdentifier hash;
};
This describes using Diffie-Hellman for key generation and key agreement, as specified by PKCS #3.
dictionary DhKeyGenParams : AlgorithmParameters {
// The prime p.
BigInteger prime;
// The base g.
BigInteger generator;
};
As specified by [FIPS 180-4]
"SHA-1"
"SHA-224"
"SHA-256"
"SHA-384"
"SHA-512"
Key derivation algorithm defined in Section 5.8.1 of NIST SP 800-56A [SP800-56A].
dictionary ConcatParams : AlgorithmParameters {
// The digest method to use to derive the keying material.
AlgorithmIdentifier hash;
// A bit string corresponding to the AlgorithmId field of the OtherInfo parameter.
// The AlgorithmId indicates how the derived keying material will be parsed and for which
// algorithm(s) the derived secret keying material will be used.
ArrayBufferView algorithmId;
// A bit string that corresponds to the PartyUInfo field of the OtherInfo parameter.
ArrayBufferView partyUInfo;
// A bit string that corresponds to the PartyVInfo field of the OtherInfo parameter.
ArrayBufferView partyVInfo;
// An optional bit string that corresponds to the SuppPubInfo field of the OtherInfo parameter.
ArrayBufferView? publicInfo;
// An optional bit string that corresponds to the SuppPrivInfo field of the OtherInfo parameter.
ArrayBufferView? privateInfo;
};
dictionary Pbkdf2Params : AlgorithmParameters {
ArrayBufferView salt;
[Clamp] unsigned long iterations;
AlgorithmIdentifier prf;
ArrayBufferView? password;
};
Generate a signing key pair, sign some data
// Algorithm Object
var algorithmKeyGen = {
name: "RSASSA-PKCS1-v1_5",
// RsaKeyGenParams
params: {
modulusLength: 2048,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]), // Equivalent to 65537
}
};
var algorithmSign = {
name: "RSASSA-PKCS1-v1_5",
// RsaSsaParams
params: {
hash: {
name: "SHA-256",
}
}
};
var keyGen = window.crypto.generateKey(algorithmKeyGen,
false, // extractable
["sign"]);
keyGen.oncomplete = function(event) {
// Because we are not supplying data to .sign(), a multi-part
// CryptoOperation will be returned, which requires us to call .process()
// and .finish().
var signer = window.crypt.sign(algorithmSign, event.target.result.privateKey);
signer.oncomplete = function(event) {
console.log("The signature is: " + event.target.result);
}
signer.onerror = function(event) {
console.error("Unable to sign");
}
var dataPart1 = convertPlainTextToArrayBufferView("hello,");
var dataPart2 = convertPlainTextToArrayBufferView(" world!");
// TODO: create example utility function that converts text -> ArrayBufferView
signer.process(dataPart1);
signer.process(dataPart2);
signer.finish();
};
keyGen.onerror = function(event) {
console.error("Unable to generate a key.");
};
var clearDataArrayBufferView = convertPlainTextToArrayBufferView("Plain Text Data");
// TODO: create example utility function that converts text -> ArrayBufferView
var aesAlgorithmKeyGen = {
name: "AES-CBC",
// AesKeyGenParams
params: {
length: 128
}
};
var aesAlgorithmEncrypt = {
name: "AES-GCM",
// AesCbcParams
params: {
iv: window.crypto.getRandomValues(new Uint8Array(16))
}
};
// Create a keygenerator to produce a one-time-use AES key to encrypt some data
var cryptoKeyGen = window.crypto.generateKey(aesAlgorithmKeyGen,
false, // extractable
["encrypt"]);
cryptoKeyGen.oncomplete = function(event) {
// A new, random AES key has been generated.
var aesKey = event.target.result;
// Unlike the signing example, which showed multi-part encryption, here we
// will perform the entire AES operation in a single call.
var aesOp = window.crypto.encrypt(aesAlgorithmEncrypt, aesKey, clearDataArrayBufferView);
aesOp.oncomplete = function(event) {
// The clearData has been encrypted.
var ciphertext = event.target.result; // ArrayBufferView
};
aesOp.onerror = function(event) {
console.error("Unable to AES encrypt.");
};
};
And hopefully YOU for reviewing the specification!