Strawman proposal for the low-level API

Hi all,

   While I'm still in the process of learning WebIDL [1] and the W3C Manual
of Style [2], I wanted to take a quick shot at drafting a strawman
low-level API for discussion. We've discussed quite a bit about key
management [3] and key discovery [4], not to mention early discussions
about algorithm discovery [5], but I think it might be good to move to the
point where we can talk a bit about how these might be all put together.

First, a bit of the IDL definition, to set the stage. This is also using
using ArrayBuffer from TypedArray [6], which I'm not sure if it's
altogether appropriate, but it's been incorporated by reference into
FileAPI [7], so it seems alright to use here.

interface CryptoStream : EventTarget {
  void processData(ArrayBuffer buffer);
  void processData(DOMString data);
  void complete();

  readonly attribute (DOMString or ArrayBuffer)? result;

  attribute [TreatNonCallableAsNull] Function? onerror;
  attribute [TreatNonCallableAsNull] Function? onprogress;
  attribute [TreatNonCallableAsNull] Function? oncomplete;

dictionary AlgorithmParams {

dictionary Algorithm {
  DOMString name;
  AlgorithmParams? params;

interface Crypto {
  CryptoStream encrypt(Algorithm algorithm, Key key);
  CryptoStream decrypt(Algorithm algorithm, Key key);

  // Also handles MACs
  CryptoStream sign(Algorithm algorithm, Key key);
  CryptoStream verify(Algorithm algorithm, Key key, ArrayBuffer signature);

  CryptoStream digest(Algorithm algorithm);

  // This interface TBD. See discussion below.
  bool supports(Algorithm algorithm, optional Key key);

  // Interfaces for key derivation/generation TBD.

As you can see, CryptoStream is used for all of the actual crypto
operations. That's because, in looking at the operations, I think all of
them will work on a series of calls to provide input, and the result of
which is either: error, some data output, or operation complete.

The real challenge, I think, lies in the AlgorithmParams structure, which
is where all of the algorithm-specific magic happens. My belief is that we
can/should be able to define this API independent of any specific
AlgorithmParams - that is, we can define the generic state machine, error
handling, discovery. Then, as a supplemental work (still within the scope
of the primary goal), we define and enumerate how exactly specific
algorithms are implemented within this state machine.

To show how different AlgorithmParams might be implemented, here's some
varies definitions:

// For the 'RSA-PSS' algorithm.
dictionary RsaPssParams : AlgorithmParams {
  // The hashing function to apply to the message (eg: SHA1).
  AlgorithmParams hash;
  // The mask generation function (eg: MGF1-SHA1)
   AlgorithmParams mgf;
  // The desired length of the random salt.
  unsigned long saltLength;

// For the 'RSA-OAEP' algorithm.
dictionary RsaOaepParams : AlgorithmParams {
  // The hash function to apply to the message (eg: SHA1).
   AlgorithmParams hash;
  // The mask generation function (eg: MGF1-SHA1).
   AlgorithmParams mgf;
  // The optional label/application data to associate with the signature.
  DOMString? label = null;

// For the 'AES-GCM' algorithm.
dictionary AesGcmParams : AlgorithmParams {
  ArrayBufferView? iv;
  ArrayBufferView? additional;
  unsigned long tagLength;

// For the 'AES-CCM' algorithm.
dictionary AesCcmParams : AlgorithmParams {
  ArrayBufferView? nonce;
  ArrayBufferView? additional;
  unsigned long macLength;

// For the 'HMAC' algorithm.
dictionary HmacParams : AlgorithmParams {
  // The hash function to use (eg: SHA1).
  AlgorithmParams hash;

The API behaviour is this:
- If encrypt/decrypt/sign/verify/digest is called with an unsupported
algorithm, throw InvalidAlgorithmError.
- If " is called with an invalid key, throw InvalidKeyError.
- If " is called with an invalid key/algorithm combination, throw
- Otherwise, return a CryptoStream.

For encrypt/decrypt
- The caller calls processData() as data is available.
- If the data can be en/decrypted, it will raise an onprogress event (event
type TBD).
  - If new (plaintext, ciphertext) data is available, .result will be
updated. [This is similar to the FileStream API behaviour]
- If the data cannot be en/decrypted, raise the onerror with an appropriate
- The caller calls .complete() once all data has been processed.
  - If the final block validates (eg: no padding errors), call onprocess
then oncomplete.
  - If the final block does not validate, call onerror with an appropriate

For authenticated encryption modes, for example, the .result may not
contain any data until .complete has been called (with the result data).

For sign/verify, it behaves similarly.
- The caller calls processData() as data is available.
- [No onprogress is called/needs to be called?]
- The caller calls .complete() once all data has been processed
- For sign, once .complete() is called, the signature is generated, and
either onprogress+oncomplete or onerror is called. If successful, the
resultant signature is in .result.
- For verify, once .complete() is called, the signature is compared, and
either onprogress+oncomplete or onerror is called. If the signatures
successfully matched, .result will contain the input signature (eg: the
constant-time comparison happens within the library). If the signatures
don't match, .result will be null and the error handler will have been

Finally, for digesting, it behaves like .sign/.verify in that no data is
available until .complete() is called, and once .compete() is called, the
resultant digest is in .result.

What I haven't fully worked out is how key derivation/agreement will work -
particularly if the result of some result of key agreement results in
multiple keys (eg: how SSL/TLS key derivation works in PKCS#11). This is
somewhat dependent on how we treat keys.

Note that I left the Key type unspecified. It's not clear if this will be
something like (Key or DOMString), indicating some either/or of handle /
id, if it might be a dictionary type (with different naming specifiers,
such as 'id' or 'uuid'), or if it will be a concrete type obtained via some
other call (eg: .queryKeys()). I think that will be borne out over the next
week or two as we continue to discuss key management/lifecycle.

For a pseudo-code example:

var stream = window.crypto.sign({ name: 'RSA-PSS', params: { hash: { name:
'sha1' }, mgf: { name: 'mgf-sha1' }, saltLength: 32 }}, key);
stream.oncomplete = function(evt) { window.alert('The signature is ' +; };
stream.onerror = function(evt) { window.alert('Signing caused an error: ' +
e.error); };

var filereader = FileReader();
reader.onload = function(evt) { stream.processData(;
stream.complete(); }

The FileAPI is probably not the best example of why the iterative API
(.processData() + .complete()) is used, since FileReader has the
FileReader.result containing all of the processed data, but it's similar
than demonstrating a streaming operation that may be using WebSockets [8]
or PeerConnection [9].

Note that I think during the process of algorithm specification, we can
probably get away with also defining well-known shorthand. eg:
'RSA-PSS-SHA256' would mean that the hash is SHA-256, the mgf is
MGF1-SHA256, and only the saltLength needs to be specified (or should it be

Anyways, hopefully this straw-man is able to spark some discussion, and
hopefully if it's not fatally flawed, I'll be able to finish adopting it to
the W3C template for proper and ongoing discussions.



Received on Monday, 18 June 2012 17:53:33 UTC