W3C home > Mailing lists > Public > public-webcrypto@w3.org > June 2012

Re: Strawman proposal for the low-level API

From: Ryan Sleevi <sleevi@google.com>
Date: Thu, 21 Jun 2012 11:28:22 -0700
Message-ID: <CACvaWvapzaxq1BhsrO01-7b9aeFzfzAfBoMqr-HmaavS9vVmkQ@mail.gmail.com>
To: Håvard Molland <haavardm@opera.com>
Cc: public-webcrypto@w3.org
On Thu, Jun 21, 2012 at 1:34 AM, Håvard Molland <haavardm@opera.com> wrote:

>  How about also incorporating the blob interface from [7]  ? (
> http://www.w3.org/TR/FileAPI/#dfn-Blob). The blob interface represents
> "immutable raw data". Since it doesn't make much sense to edit encrypted
> data, this is a nice fit with encryption APIs.
>
> Without going into details, we could let processData take blob as input
> and allowing the encrypted result to read out as blob. And/or we could let
> a CryptoBlob object inheriting the Blob interface.
>
> This would make it easy to encrypt files chosen by the user and to
> simultaneously stream and encrypt those files out on websockets or other
> interfaces that might use the blob interface in the feature.
>

Hi Håvard,

I considered the Blob interface, but I don't think it's applicable or
appropriate for the operations we're talking about. For example, Blob
requires that the size be known before-hand, and it also requires that
callers can arbitrary slice data off.

Such an interface doesn't make sense in the case of MAC/sign/verification
operations, which typically compress their output by some sort of hash
function, so we're left with the encrypt/decrypt case.

For encryption/decryption, the length may not be known a-priori. It may
also be part of an authenticated data stream, for which the MAC tag appears
as part of the final block. Unless the caller has the entire encrypted
stream beforehand, they won't be able to construct a Blob. Further, unless
they've processed it, they may not reasonably know the length (for example,
there may be additional block padding added to the encrypted data, if using
a block cipher mode).

This is why I defined CryptoStream to use an ArrayBuffer. As far as I've
seen from surveying the API landscape, the choice of ArrayBufferView seems
to be the desired API glue point. Whether this is for media, websockets,
WebRTC, or WebGL, the ArrayBufferView seems to offer a more
agnostic/flexible interface than Blob.

The CryptoStream itself is conceptually similar to the FileReader
interface, which is how consumers are presumably going to be consuming
Blobs anyways.

If you wanted to chain a FileReader (or FileReaderSync) to a CryptoStream,
I think you can still accomplish this within the spec as written.

var webSocket = ...;
var reader = FileReader();
var cryptor = window.crypto.decrypt(...);
reader.onprogress = function(evt /*ProgressEvent*/) {
  cryptor.processData(evt.target.result.slice(lastProcessed, evt.loaded));
  lastProcessed = evt.loaded;
}
cryptor.onprogress = function(evt /*Event*/) {
  webSocket.send(evt.target.result?);
}

var blob = document.forms['uploadData']['fileChooser'].files[0];
reader.readAsBinaryString(blob);


The question of what .result should contain, as Wan-Teh raised, is an
interesting one for this use case. If .result contains only the data since
the last .onprogress call, then just sending .result is fine. If .result
contains the entire data stream, as it does in the FileReader API, then a
lastCryptorProcessed would also be tracked.

However, I think we can only reasonably expose the data as part of a new
interface, not as part of a Blob (or ArrayBufferView), simply because we
need to be able to handle errors and cannot handle seeking and slicing with
the same semantics defined by the FileAPI/TypedArray specifications.

Cheers,
Ryan


> Use case:
>
> 1) The script lets a user choose a file, and wraps the file blob into a
> CryptoBlob object.
> 2) The script sets the CryptoBlob in "encryption"  mode, and sets the key
> and algorithms.
> 3) The script sends the CryptoBlob to the server or another browser using
> websockets or any other api accepting the blob interface.
> 4) The receiver script either store the blob, or  wraps the received
> encrypted blob into a CryptoBlob object in "decryption" mode and decrypts
> it.
>
> As the websockets implementations only see the blob interface, the effect
> is that the blob can be encrypted and streamed out simultaneously. Note
> that this would not be some replacement for TLS, as the data will stay
> encrypted on the receivers side until the receiver knows the key and
> decides to decrypt.
>
> Cheers,
> Håvard
>
>
>
>
>
> On 06/18/2012 07:53 PM, Ryan Sleevi wrote:
>
> 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]
> 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;
> };
>
>  [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 onerror 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.
>
>  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?)
>
>  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.
>
>  Cheers,
> Ryan
>
>  References:
> [1] http://www.w3.org/TR/WebIDL
> [2] http://www.w3.org/2001/06/manual/
> [3] http://lists.w3.org/Archives/Public/public-webcrypto/2012Jun/0050.html
> [4] http://lists.w3.org/Archives/Public/public-webcrypto/2012Jun/0007.html
> [5] http://lists.w3.org/Archives/Public/public-webcrypto/2012May/0070.html
> [6] http://www.khronos.org/registry/typedarray/specs/latest/
> [7] http://www.w3.org/TR/FileAPI/
> [8] http://www.w3.org/TR/2009/WD-websockets-20091222/
>  [9] http://dev.w3.org/2011/webrtc/editor/webrtc.html
>
>
>
Received on Thursday, 21 June 2012 18:28:51 GMT

This archive was generated by hypermail 2.2.0+W3C-0.50 : Thursday, 21 June 2012 18:28:52 GMT