Re: Strawman proposal for the low-level API

----- Original Message -----
> From: "Ryan Sleevi" <sleevi@google.com>
> To: public-webcrypto@w3.org
> Sent: Monday, June 18, 2012 12:53:03 PM
> Subject: 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. 

This is great, thanks for taking the time.

> 
> 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.
> 
I think so. ArrayBuffers seem a natural fit for this API.

> [interface]
> interface CryptoStream : EventTarget {
>   void processData(ArrayBuffer buffer);
>   void processData(DOMString data);

The flexibility of accepting either a string or ArrayBuffer is a good idea, with an internal, seamless conversion.

>   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;
> };
> 
> [NoInterfaceObject]
> 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
> UnsupportedAlgorithmError.
> - 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 onMGF1-SHA1error with an
> appropriate
> error
> - 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
> error.
> 
> 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
> called.
> 
> 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.

The final result of any of these operations would have all result data passed into the oncomplete event handler, correct? 

> 
> 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 '
> +
> e.target.result); };
> stream.onerror = function(evt) { window.alert('Signing caused an
> error: ' +
> e.error); };
> 
> var filereader = FileReader();
> reader.onload = function(evt) {
> stream.processData(evt.target.result);
> stream.complete(); }
> filereader.readAsArrayBuffer(someFile);
> 
> 
> 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
> implied?)

Since this is a low-level API, perhaps we imply a sensible default, with the ability to override for properties like saltLength?

> 
> 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.
> 

I like what you have here. I think this interface is elegant in the central concept of the CryptoStream being able to handle any operation possible for the algorithm. This interface is simpler to work with than my proposal. 

Like Wan-Teh said in the meeting this week,  we should figure out how key generation works, the structure of the key handle, or,  extracted key data properties look like.

With the Algorithm and its AlgorithmParams are we headed down the path of maintaining a cipher suite for this API?

Thanks again for putting this together, I think we should begin nailing down the hand wavy 'Keys' for this proposal.

  
Regards,

David

Received on Wednesday, 20 June 2012 19:42:50 UTC