Re: JWK import/export as ECMAScript objects, rather than ArrayBuffer

On Fri, Mar 21, 2014 at 7:50 AM, Richard Barnes <rlb@ipv.sx> wrote:

> On Tue, Mar 11, 2014 at 9:08 PM, Mark Watson <watsonm@netflix.com> wrote:
>
>>
>>
>>
>> On Tue, Mar 11, 2014 at 6:00 PM, Ryan Sleevi <sleevi@google.com> wrote:
>>
>>>
>>> On Mar 11, 2014 5:44 PM, "Mark Watson" <watsonm@netflix.com> wrote:
>>> >
>>> > Hi Ryan,
>>> >
>>> > This looks good with the following comments:
>>> >
>>> > (1) Why not add this capability, whilst keeping the existing JWK
>>> import / export ? For example add a new format "jwk-obj". This would keep
>>> things simple for the other use-case where the JWK is just being written to
>>> / read from a wire protocol. Supporting both is hardly arduous since UAs
>>> must support the serialization / de-serialization for wrap / unwrap anyway.
>>> >
>>>
>>> We should prioritize the common case. Dealing with ArrayBuffers for JSON
>>> or JS Objects is not the common case for any other Web API.
>>>
>> We might disagree about which is the common case. Since we already have
>> the actual JWKs (and several browsers have implemented these and they are
>> being used) it seems sensible to add functionality here. There is no
>> additional work to add rather than replace, so there is no need to
>> prioritize.
>>
>
> It seems abundantly silly to have two options here, when there's a trivial
> conversion between them.
>
> Object -> json.stringify() -> TextEncoder -> ABV
> ABV -> TextDecoder -> json.parse() -> Object
>
> Whichever one we choose here, a small JS shim can emulate the other.  So
> we should really just choose one.
>

This is fine if you have TextEncoder. It doesn't appear on caniuse.com.
Does anyone have information about browser support of this ?

...Mark



>
> As to which we should choose, I'm pretty strongly with Ryan here that we
> should return an object.  ECMAScript objects are what get returned from any
> other web API that returns a structured object.  The only exceptions we see
> to this are when some binary format is used for backward compatibility;
> that applies for "spki", "pkcs8", but not to "jwk".
>
> As far as implementation, I would be pretty shocked to see implementations
> making these object in any way other than (1) building and object and (2)
> calling json.stringify().  So from an implementation complexity point of
> view, the only change here is to remove the call to json.stringify().
>
>  If we support parallel formats, it should be because we believe the
>>> ArrayBuffer case will be as significant a use case as with Object.
>>>
>> No, it's sufficient that they are both significant and useful. They don't
>> need parity.
>>
>>>  In either event, if we do support both, I believe the ArrayBuffer case
>>> should be separately named (eg: JWKS) - precisely because JWK will
>>> naturally yield or be manipulated as Objects, not octets, within JS.
>>>
>> The J in JWK stands for JSON. JSON is a wire format. Seems obvious that
>> format "jwk" should produce a JWK, not a Javascript representation of one.
>>
>>> You keep saying "written to or read from a wire protocol", so I'd simply
>>> ask that you back that assertion up with script demonstrating this use case.
>>>
>> This is what our application does today. I'll see if I can extract the
>> relevant code.
>>
>
> As noted above, even if your application does this, it's a small shim to
> make an object-based API look like an ArrayBuffer-based one.
>
> I would also point out that there are potentially pretty big down-sides to
> providing a pre-serialized JWK, in contexts where that JWK fits into a
> larger protocol.  For example, if the overall protocols uses UTF-16 (e.g.,
> based on sending ECMAScript strings without re-encoding), then the
> pre-serialized JWK can't just be pasted into the protocol, since it's in
> UTF-8.  But there are also enough protocols that use UTF-8 that we can't
> just switch to UTF-16.  Better to just give the application the object and
> let it serialize it into whatever form works.
>
> --Richard
>
>
>
>
>>
>> ...Mark
>>
>>> The most likely candidates - XHR and WebSockets - are naturally inclined
>>> towards JSON and DOMString-ified JSON. Equally true for structured
>>> inter-origin postMessage (which may not be suitable as a Key object if the
>>> source is replaying a message from the server to the peer). These are all
>>> cases served by a natural object - especially when it reduces repeated
>>> conversions / copies.
>>>
>>> Equally, if we believe that really, the use case is only for importing
>>> an ArrayBuffer, we could let the "jwk" importKey handle both cases
>>> transparently, while always yielding an object for export key.
>>>
>>> TL:DR; What is a precise use case or demonstrable snippet where
>>> ArrayBuffer would be more preferable or natural compared to an Object?
>>>
>>> > (2) WebIDL appears to be a little ambiguous with respect to unions of
>>> Dictionaries. Section 14.2.25 says "If types includes a dictionary type,
>>> then return the result of converting V to that dictionary type.
>>> > ", which pointedly neglects to say which dictionary type in types, if
>>> there is more than one.
>>> >
>>>
>>> Sure. We could just as well deal with it as we deal with algorithm -
>>> object and coercion.
>>>
>>> I agree this has to be solved meaningfully before adding to the spec.
>>>
>>> > (3) We need a
>>> > JwkKeyDictionary subclass for symmetric keys:
>>> >
>>> > JwkSecretKeyDictionary : JwkKeyDictionary {
>>> >   DOMString k;
>>> > };
>>> >
>>> > (4) usages in JwkKeyDictionary should be "key_ops"
>>> >
>>> > (5) Do we want to include the "use" member ? I thought we checked that
>>> for consistency ?
>>> >
>>>
>>> All oversights of mine, as a result of basing on JWA without reading all
>>> of our additions/registrations.
>>>
>>> Answer is yes, yes, and yes.
>>>
>>> > ...Mark
>>> >
>>> >
>>> > On Tue, Mar 11, 2014 at 5:22 PM, Ryan Sleevi <sleevi@google.com>
>>> wrote:
>>> >>
>>> >> The motivation:
>>> >>
>>> >> * Provide a means of easy interchange with Web Sockets (eg: by
>>> allowing Text Frame, rather than imposing Binary Frame, as done by
>>> send(ArrayBuffer) )
>>> >> * Provide a means of easy interchange of JWK with postMessage (eg: by
>>> not requiring a Key object itself be posted)
>>> >> * Provide a means of easy integration with larger JSON-backed messages
>>> >>
>>> >> That is, presume a structure
>>> >> {
>>> >>   'larger_message': 'something',
>>> >>   'jwk': [{
>>> >>     'kid': 'foo',
>>> >>     'alg': 'RSA',
>>> >>     'kty': 'RSA1_5',
>>> >>     'n': '....',
>>> >>     'e': '....'
>>> >>   }]
>>> >> }
>>> >>
>>> >> Under the current API, one has two options - depending on UA support
>>> for http://encoding.spec.whatwg.org/#api
>>> >>
>>> >> With Encoding support:
>>> >>
>>> >> // Makes 3 additional copies of message.jwk
>>> >> // 1 for the .stringify
>>> >> // 1 for the TextEncoder
>>> >> // 1 for the importKey (cloning the ArrayBuffer)
>>> >> // By definition, this copies *all* fields of message.jwk, including
>>> those not used by importKey (eg: 'kid')
>>> >>
>>> >> message = JSON.parse(message);
>>> >> jwkBuf = (new
>>> TextEncoder("utf-8")).encode(JSON.stringify(message.jwk));
>>> >> window.crypto.subtle.importKey("jwk", jwkBuf, { name:
>>> "RSAES-PKCS1-v1_5" }, [ "encrypt", "decrypt"] );
>>> >>
>>> >> Without Encoding support:
>>> >> It's necessary to do something like strToUTF8Arr (
>>> https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding)
>>> >>
>>> >> Under the proposed API, one simply does
>>> >>
>>> >> // Makes 1 additional copy of message.jwk
>>> >> // 1 for the importKey (cloning message.jwk)
>>> >> // By definition, this *does not* copy all fields of message.jwk -
>>> only those fields used for the import (eg: 'kid' is NOT copied)
>>> >> window.crypto.subtle.importKey("jwk", message.jwk, { name:
>>> "RSAES-PKCS1-v1_5" }, [ "encrypt", "decrypt" ]);
>>> >>
>>> >>
>>> >> Types used:
>>> >>
>>> >> dictionary JwkKeyDictionary {
>>> >>   DOMString kty;
>>> >>   DOMString alg;
>>> >>   boolean ext;
>>> >>   DOMString[] usages;
>>> >> };
>>> >>
>>> >> dictionary JwkEcKeyDictionary : JwkKeyDictionary {
>>> >>   DOMString crv;
>>> >>   DOMString x;
>>> >>   DOMString y;
>>> >>   DOMString d;
>>> >> };
>>> >>
>>> >> dictionary JwkRsaOtherPrimeDictionary {
>>> >>   DOMString r;
>>> >>   DOMString d;
>>> >>   DOMString t;
>>> >> };
>>> >>
>>> >> dictionary JwkRsaKeyDictionary : JwkKeyDictionary {
>>> >>   DOMString n;
>>> >>   DOMString e;
>>> >>   DOMString d;
>>> >>   DOMString p;
>>> >>   DOMString dp;
>>> >>   DOMString dq;
>>> >>   DOMString qi;
>>> >>   JwkRsaOtherPrimeDictionary[] oth;
>>> >> };
>>> >>
>>> >>
>>> >>
>>> >> Changes to signatures:
>>> >> Old:
>>> >> Promise<any> importKey(KeyFormat format, CryptoOperationData keyData,
>>> AlgorithmIdentifier? algorithm, boolean extractable, KeyUsage[] keyUsages);
>>> >> Promise<any> exportKey(KeyFormat format, Key key);
>>> >>
>>> >> New:
>>> >> Promise<any> importKey(KeyFormat format, (CryptoOperationData or
>>> JwkRsaKeyDictionary or JwkEcKeyDictionary), AlgorithmIdentifier algorithm,
>>> boolean extractable, KeyUsage[] keyUsages);
>>> >> Promise<any> exportKey(KeyFormat format, Key key);
>>> >>
>>> >>
>>> >> Changes to algorithms:
>>> >> Wrap Key ( 14.3.1 /
>>> https://dvcs.w3.org/hg/webcrypto-api/raw-file/3f7df730b2c7/spec/Overview.html#SubtleCrypto-method-wrapKey)
>>> >>
>>> >> 12.
>>> >> * If format is "spki":
>>> >>   - Let bytes be the result of performing the export key operation
>>> specified the algorithm attribute of key using key and format.
>>> >> * If format is "pkcs8"
>>> >>   - Let bytes be the result of performing the export key operation
>>> specified the algorithm attribute of key using key and format.
>>> >> * If format is "jwk"
>>> >>   - Let object be the result of performing the export key operation
>>> specified by the algorithm attribute of key using key and format.
>>> >>   - Let stringifiedJwk be the result of invoking the JSON.stringify
>>> method specified in Section 15.12.3 of [ECMA-252], with /object/ as /value/.
>>> >>   - Let bytes be the UTF-8 encoding of stringifiedJwk
>>> >>
>>> >>
>>> >> From the algorithm-specific import key sections eg: using
>>> https://dvcs.w3.org/hg/webcrypto-api/raw-file/3f7df730b2c7/spec/Overview.html#rsassa-pkcs1-operationsas an example
>>> >>
>>> >> 4. If format is "jwk"
>>> >>   1. If /keyData/ is not an instance of a JwkRsaKeyDictionary, return
>>> an error ...
>>> >>   2. Let /jwk/ be /keyData/
>>> >>
>>> >> From the algorithm-specific export key sections - eg: using again
>>> RSASSA-PKCS1
>>> >>
>>> >> 4. If format is "jwk"
>>> >>   * Let /jwk/ be a new ECMAScript object created as if by the
>>> expression ({})
>>> >>   * _Set the property "n" of /jwk/_ to the _base64url-encoded_
>>> modulus of the RSA public key represented by /key/, as specified by Section
>>> 6.3.1 of [JWA]
>>> >>   * _Set the property "e" of /jwk/_ to the _base64url-encoded_ big
>>> integer exponent representation of the RSA public key represented by /key/,
>>> as specified by Section 6.3.1 of [JWA]
>>> >>   ...
>>> >>   * Let /result/ be /jwk/
>>> >>
>>> >> Terminology:
>>> >> When this specification says Set the property /name/ of /object/ to
>>> /value/, call the [[DefineOwnProperty]] internal method of /object/ with
>>> property name /name/, the Property Descriptor { [[Writable]]: true,
>>> [[Enumerable]]: true, [[Configurable]]: true, [[Value]]: /value/ }, and the
>>> Boolean flag false.
>>> >>
>>> >>
>>> >>
>>> >
>>> >
>>>
>>
>>
>

Received on Friday, 21 March 2014 17:22:40 UTC