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

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.

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 14:50:48 UTC