Re: Proposal for key wrap/unwrap (ISSUE-35)

On Tue, Oct 30, 2012 at 1:38 AM, Mark Watson <watsonm@netflix.com> wrote:
> All,
>
> Here is a proposal in response to ISSUE-35 [1], using JWK (with extensions)
> as the format for the key and associated parameters before wrapping.
> Wrapping and unwrapping are considered aspects of key export and import
> (respectively). To export/import without wrapping the user just specifies
> the wrap algorithm as null.
>
> 1) Add "wrap" and "unwrap" to KeyUsage enum:
>
> "10. Key interface
>
> enum KeyUsage {
>   "encrypt",
>   "decrypt",
>   "sign",
>   "verify",
>   "wrap",
>   "unwrap",
> };"
>
> 2) Add wrapped attribute to KeyImporter and KeyExporter. Specify the
> character encoding of JSON Web Keys (we assume that all key export
> operations result in an ArrayBufferView):
>
> "15. KeyImporter interface
>
> enum KeyFormat {
>   // An unformatted sequence of bytes. Intended for secret keys.
>   "raw",
>   // The BER encoding of the RSAPublicKey structure from RFC 3447.
>   // Only usable with RSA keys.
>   "pkcs1-public",
>   // The BER encoding of the RSAPrivateKey structure from RFC 3447.
>   // Only usable with RSA keys.
>   "pkcs1-private",
>   // The BER encoding of the PrivateKeyInfo structure from RFC 5208.
>   "pkcs8",
>   // The key is represented as UTF-8 encoded JSON according to the JSON Web
> Key format.
>   "jwk"
> };
>
> interface KeyImporter : KeyOperation {
>   void import();
>
>   readonly attribute KeyFormat format;
>   readonly attribute boolean wrapped;
> };
>
> interface KeyExporter : KeyOperation {
>   void export();
>
>   readonly attribute KeyFormat format;
>   readonly attribute boolean wrapped;
> };
>
> 15.1. JSON Web Key
>
> Implementations SHOULD support additional JSON Web Key use values of:
>   • wrap (key wrapping)

Is there a reason you didn't consider this a MUST?

Note for readers - JWK currently only defines "sign" and "encrypt",
and so there are a very different set of usages here.

> Implementations SHOULD support additional JSON Web Key members of:
>   • extractable (boolean indicating extractability)
>   • startdate (key start date in seconds since the epoch)
>   • enddate (key end date in seconds since the epoch)

Can you explain the difference between wrap and extractable here? In
practical security considerations, they're better considered equal.

As for startdate/enddate -
  - As previously discussed on the list, I thought (and will make a
separate proposal) to remove the startDate/endDate attributes from the
fundamental Key object (and let them be stored in the extra - since as
both Vijay and I pointed out, they're application-specific notions)
  - If we do continue with it, "the epoch" is, regrettably, undefined.
Whose epoch? Unix epoch of 1970? Windows epoch of 1601?

> Implementations SHOULD expose additional JSON Web Key members as attributes
> contained within the extra attribute of the Key objects.
>
> TODO: Specify encoding of key attributes of potentially arbitrary type into
> extra attribute map

One of the issues I see here is whether the 'extra' attribute map gets
flattened in directly to the JWK (which you seem to be proposing?) or
get stored in a secondary attribute (eg: 'extra' on the JWK). It seems
you're proposing the former, but it would seem the latter is necessary
(eg: what if there is an 'extra' attribute named 'alg' or 'keyid' ?)

> TODO: Specify JWK formats for private and symmetric keys
> TODO: Register the above values with IANA
> TODO: Specify key compatibility between JWK algorithm names with WebCrypto
> algorithm names."
>
> 3) Modify createKeyImporter and createKeyExporter to take optional wrapping
> algorithm and key
>
> "18. Crypto interface
>
> KeyImporter createKeyImporter( KeyFormat format,
> ArrayBufferView key,
> AlgorithmIdentifier? keyAlgorithm,
> AlgorithmIdentifier? wrapAlgorithm,
> Key? wrappingKey,
> bool temporary = true,
> bool extractable = false,
> KeyUsage[] keyUsages = [] );
>
> KeyExporter createKeyExporter( KeyFormat format,
> Key key,
> AlgorithmIdentifier? wrapAlgorithm,
> Key? wrappingKey );
>
> "18.1.8 The createKeyImporter method
>
> The createKeyImporter method returns a new KeyImporter object that will
> import
> new keys of the, potentially wrapped, specified KeyFormat. It must act as
> follows:
>
>   1. If keyAlgorithm is specified, let normalizedKeyAlgorithm be the result
> of processing keyAlgorithm according to
>      the algorithm normalizing rules.
>   2.  If keyAlgorithm is specified and if normalizedKeyAlgorithm does not
> describe a registered algorithm throw a
>      NotSupportedError and terminate the operation.
>   3. If wrapAlgorithm is specified, let normalizedWrapAlgorithm be the
> result of processing wrapAlgorithm according to
>      the algorithm normalizing rules.
>   4.  If wrapAlgorithm is specified and if normalizedWrapAlgorithm does not
> describe a registered algorithm throw a
>      NotSupportedError and terminate the operation.
>   5. Return a new KeyImporter object S with the following characteristics:
>      1. S.result = null
>      2. S.format = format
>      3. S.wrapped = true if wrapAlgorithm is not null, false otherwise
>
> Imported keys SHALL be subject to the extraction and usage constraints of
> the key
> data, if any. The temporary argument SHALL be honored. The extractable
> argument
> SHALL NOT be honored if the key data does not allow extraction of the
> imported key. The
> imported key's usages SHALL consist of the intersection of the keyUsages
> argument
> and the usages specified in the key data of the imported key.
>
> The keyAlgorithm may be null if the key algorithm is specified within the
> key data.
>
> 18.1.9 The createKeyExporter method
>
> The createKeyExporter method returns a new KeyExporter object that will
> export
> existing keys in the specified KeyFormat. It must act as follows:
>
>   1. If the key does not exist throw a KeyNotFoundError and terminate the
>      operation.
>   2. If wrapAlgorithm is specified, let normalizedWrapAlgorithm be the
> result of processing wrapAlgorithm according to
>      the algorithm normalizing rules.
>   3.  If wrapAlgorithm is specified and if normalizedWrapAlgorithm does not
> describe a registered algorithm throw a
>      NotSupportedError and terminate the operation.
>   4. If wrapAlgorithm is specified and wrappingKey is null, or if
> wrapAlgorithm is null and wrappingKey is specified, throw a
> NotSupportedError and terminate the operation
>   4. If the wrappingKey is not null and does not exist throw a
> KeyNotFoundError
>      and terminate the operation.
>   5. Return a new KeyExporter object S with the following characteristics:
>      1. S.result = null
>      2. S.format = format
>      3. S.wrapped = true if wrapAlgorithm is not null, false otherwise"
>
> 4) Add text on import/export for RSAES-PKCS1-v1_5 keys
>
> "24.3. RSAES-PKCS1-v1_5
>
> 24.3.2. Registration
>
> The recognized algorithm name for this algorithm is "RSAES-PKCS1-v1_5".
>
> Operation   | Parameters      | Result
> ================================================
> encrypt     | None            | ArrayBufferView?
> decrypt     | None            | ArrayBufferView?
> generateKey | RsaKeyGenParams | KeyPair?
> importKey   | None            | Key?
> exportKey   | None            | ArrayBufferView?
>
> 24.3.4 Operations
>
> Import Key
>   When importing a key, the resulting KeyImporter shall behave as follows:
>
>     1. Upon invoking import:
>       1. If the key is wrapped, perform the unwrap operation defined for the
> wrapAlgorithm
>       2. Parse and extract the RSA key
>       3. If either operation resulted in an error, raise an error and
> terminate the
>          operation.
>       4. Let result be a Key of the imported public or private key.
>
> Export Key
>   When exporting a key, the resulting KeyExporter shall behave as follows:
>
>     1. Upon invoking export:
>       1. Encode the RSA key into the format
>       2. If the key is to be wrapped, perform the wrap operation defined for
> the wrapAlgorithm
>       3. If either operation resulted in an error, raise an error and
> terminate the
>          operation.
>       4. Let result be an ArrayBufferView of the exported public or private
> key."
>
> 5) Add text on import/export for RSASSA-PKCS1-v1_5 keys
>
> "24.4. RSASSA-PKCS1-v1_5
>
> 24.4.2. Registration
>
> The recognized algorithm name for this algorithm is "RSASSA-PKCS1-v1_5".
>
> Operation   | Parameters      | Result
> ================================================
> sign        | RsaSsaParams    | ArrayBufferView?
> verify      | RsaSsaParams    | boolean?
> generateKey | RsaKeyGenParams | KeyPair?
> importKey   | None            | Key?
> exportKey   | None            | ArrayBufferView?
>
> 24.4.4. Operations
>
> Import Key
>   When importing a key, the resulting KeyImporter shall behave as follows:
>
>     1. Upon invoking import:
>       1. If the key is wrapped, perform the unwrap operation defined for the
> wrapAlgorithm
>       2. Parse and extract the RSA key
>       3. If either operation resulted in an error, raise an error and
> terminate the
>          operation.
>       4. Let result be a Key of the imported public or private key.
>
> Export Key
>   When exporting a key, the resulting KeyExporter shall behave as follows:
>
>     1. Upon invoking export:
>       1. Encode the RSA key into the format
>       2. If the key is to be wrapped, perform the wrap operation defined for
> the wrapAlgorithm
>       3. If the operation resulted in an error, raise an error and terminate
> the
>          operation.
>       4. Let result be an ArrayBufferView of the exported public or private
> key."
>
> 6) Add text on import/export for RSA-PSS keys
>
> "24.5. RSA-PSS
>
> 24.5.2. Registration
>
> The recognized algorithm name for this algorithm is "RSA-PSS".
>
> Operation   | Parameters      | Result
> ================================================
> sign        | RsaPssParams    | ArrayBufferView?
> verify      | RsaPssParams    | boolean?
> generateKey | RsaKeyGenParams | KeyPair?
> importKey   | None            | Key?
> exportKey   | None            | ArrayBufferView?
>
> 24.4.4. Operations
>
> Import Key
>   When importing a key, the resulting KeyImporter shall behave as follows:
>
>     1. Upon invoking import:
>       1. If the key is wrapped, perform the unwrap operation defined for the
> wrapAlgorithm
>       2. Parse and extract the RSA key
>       3. If either operation resulted in an error, raise an error and
> terminate the
>          operation.
>       4. Let result be a Key of the imported public or private key.
>
> Export Key
>   When exporting a key, the resulting KeyExporter shall behave as follows:
>
>     1. Upon invoking export:
>       1. Encode the RSA key into the format, including any wrap operations
> as
>          dictated by the format and wrapAlgorithm.
>       2. If the key is to be wrapped, perform the wrap operation defined for
> the wrapAlgorithm
>       3. If either operation resulted in an error, raise an error and
> terminate the
>          operation.
>       4. Let result be an ArrayBufferView of the exported public or private
> key."
>
> 7) Add text on import/export for RSA-OAEP keys
>
> "24.6. RSA-OAEP
>
> 24.6.2. Registration
>
> The recognized algorithm name for this algorithm is "RSA-OAEP".
>
> Operation   | Parameters      | Result
> ================================================
> encrypt     | RsaOaepParams   | ArrayBufferView?
> decrypt     | RsaOaepParams   | ArrayBufferView?
> generateKey | RsaKeyGenParams | KeyPair?
> importKey   | None            | Key?
> exportKey   | None            | ArrayBufferView?
>
> 24.6.4. Operations
>
> Import Key
>   When importing a key, the resulting KeyImporter shall behave as follows:
>
>     1. Upon invoking import:
>       1. If the key is wrapped, perform the unwrap operation defined for the
> wrapAlgorithm
>       2. Parse and extract the RSA key
>       3. If the operation resulted in an error, raise an error and terminate
> the
>          operation.
>       4. Let result be a Key of the imported public or private key.
>
> Export Key
>   When exporting a key, the resulting KeyExporter shall behave as follows:
>
>     1. Upon invoking export:
>       1. Encode the RSA key into the format
>       2. If the key is to be wrapped, perform the wrap operation defined for
> the wrapAlgorithm
>       3. If the operation resulted in an error, raise an error and terminate
> the
>          operation.
>       4. Let result be an ArrayBufferView of the exported public or private
> key."
>
> 8) Add text on import/export for ECDSA keys
>
> "24.7. ECDSA
>
> 24.7.2. Registration
>
> The recognized algorithm name for this algorithm is "ECDSA".
>
> Operation   | Parameters      | Result
> ================================================
> sign        | EcdsaParams     | ArrayBufferView?
> verify      | EcdsaParams     | boolean?
> generateKey | EcKeyGenParams  | KeyPair?
> importKey   | None            | Key?
> exportKey   | None            | ArrayBufferView?
>
> 24.7.5. Operations
>
> Import Key
>   When importing a key, the resulting KeyImporter shall behave as follows:
>
>     1. Upon invoking import:
>       1. If the key is wrapped, perform the unwrap operation defined for the
> wrapAlgorithm
>       2. Parse and extract the ECC key
>       3. If either operation resulted in an error, raise an error and
> terminate the
>          operation.
>       4. Let result be a Key of the imported public or private key.
>
> Export Key
>   When exporting a key, the resulting KeyExporter shall behave as follows:
>
>     1. Upon invoking export:
>       1. Encode the ECC key into the format
>       2. If the key is to be wrapped, perform the wrap operation defined for
> the wrapAlgorithm
>       3. If either operation resulted in an error, raise an error and
> terminate the
>          operation.
>       4. Let result be an ArrayBufferView of the exported public or private
> key."
>
> 9) Add import/export for AES-CTR keys
>
> "24.9. AES-CTR
>
> 24.9.2. Registration
>
> The recognized algorithm name for this algorithm is "AES-CTR".
>
> Operation   | Parameters      | Result
> ================================================
> encrypt     | AesCtrParams    | ArrayBufferView?
> decrypt     | AesCtrParams    | ArrayBufferView?
> generateKey | AesKeyGenParams | Key?
> importKey   | None            | Key?
> exportKey   | None            | ArrayBufferView?
>
> 24.9.5. Operations
>
> Import Key
>   When importing a key, the resulting KeyImporter shall behave as follows:
>
>     1. Upon invoking import:
>       1. If the key is wrapped, perform the unwrap operation defined for the
> wrapAlgorithm
>       2. Parse and extract the AES key
>       3. If the operation resulted in an error, raise an error and terminate
> the
>          operation.
>       4. Let result be a Key of the imported secret key.
>
> Export Key
>   When exporting a key, the resulting KeyExporter shall behave as follows:
>
>     1. Upon invoking export:
>       1. Encode the AES key into the format
>       2. If the key is to be wrapped, perform the wrap operation defined for
> the wrapAlgorithm
>       3. If the operation resulted in an error, raise an error and terminate
> the
>          operation.
>       4. Let result be an ArrayBufferView of the exported secret key."
>
> 10) Add import/export for AES-CBC keys
>
> "24.10. AES-CBC
>
> 24.10.2. Registration
>
> The recognized algorithm name for this algorithm is "AES-CBC".
>
> Operation   | Parameters      | Result
> ================================================
> encrypt     | AesCbcParams    | ArrayBufferView?
> decrypt     | AesCbcParams    | ArrayBufferView?
> generateKey | AesKeyGenParams | Key?
> importKey   | None            | Key?
> exportKey   | None            | ArrayBufferView?
>
> 24.10.4. Operations
>
> Import Key
>   When importing a key, the resulting KeyImporter shall behave as follows:
>
>     1. Upon invoking import:
>       1. If the key is wrapped, perform the unwrap operation defined for the
> wrapAlgorithm
>       2. Parse and extract the AES key.
>       3. If the operation resulted in an error, raise an error and terminate
> the
>          operation.
>       4. Let result be a Key of the imported secret key.
>
> Export Key
>   When exporting a key, the resulting KeyExporter shall behave as follows:
>
>     1. Upon invoking export:
>       1. Encode the AES key into the format
>       2. If the key is to be wrapped, perform the wrap operation defined for
> the wrapAlgorithm
>       3. If the operation resulted in an error, raise an error and terminate
> the
>          operation.
>       4. Let result be an ArrayBufferView of the exported secret key."
>
> 11) Add import/export for AES-GCM keys
>
> "24.11. AES-GCM
>
> 24.11.2. Registration
>
> The recognized algorithm name for this algorithm is "AES-GCM".
>
> Operation   | Parameters      | Result
> ================================================
> encrypt     | AesGcmParams    | ArrayBufferView?
> decrypt     | AesGcmParams    | ArrayBufferView?
> generateKey | AesKeyGenParams | Key?
> importKey   | None            | Key?
> exportKey   | None            | ArrayBufferView?
>
> 24.11.4. Operations
>
> Import Key
>   When importing a key, the resulting KeyImporter shall behave as follows:
>
>     1. Upon invoking import:
>       1. If the key is wrapped, perform the unwrap operation defined for the
> wrapAlgorithm
>       2. Parse and extract the AES key
>       3. If the operation resulted in an error, raise an error and terminate
> the
>          operation.
>       4. Let result be a Key of the imported secret key.
>
> Export Key
>   When exporting a key, the resulting KeyExporter shall behave as follows:
>
>     1. Upon invoking export:
>       1. Encode the AES key into the format
>       2. If the key is to be wrapped, perform the wrap operation defined for
> the wrapAlgorithm
>       3. If either operation resulted in an error, raise an error and
> terminate the
>          operation.
>       4. Let result be an ArrayBufferView of the exported secret key."
>
> 12) Add import/export for HMAC keys
>
> "24.12. HMAC
>
> 24.12.2. Registration
>
> The recognized algorithm name for this algorithm is "HMAC".
>
> Operation   | Parameters       | Result
> ================================================
> sign        | HmacParams       | ArrayBufferView?
> verify      | HmacParams       | boolean?
> generateKey | HmacKeyGenParams | Key?
> importKey   | None             | Key?
> exportKey   | None             | ArrayBufferView?
>
> 24.12.4. HmacKeyGenParams dictionary
>
> dictionary HmacKeyGenParams : AlgorithmParameters {
>   // The inner hash function to use.
>   AlgorithmIdentifier hash;
> };
>
> 24.12.5. Operations
>
> Import Key
>   When importing a key, the resulting KeyImporter shall behave as follows:
>
>     1. Upon invoking import:
>       1. If the key is wrapped, perform the unwrap operation defined for the
> wrapAlgorithm
>       2. Parse and extract the HMAC key
>       3. If either operation resulted in an error, raise an error and
> terminate the
>          operation.
>       4. Let result be a Key of the imported public or private key.
>
> Export Key
>   When exporting a key, the resulting KeyExporter shall behave as follows:
>
>     1. Upon invoking export:
>       1. Encode the HMAC key into the format
>       2. If the key is to be wrapped, perform the wrap operation defined for
> the wrapAlgorithm
>       3. If either operation resulted in an error, raise an error and
> terminate the
>          operation.
>       4. Let result be an ArrayBufferView of the exported public or private
> key."
>

There are various issues regarding the copy/paste of text here, but
those are minor and presumably would be fixed during editor
integration. Just noting for future work.


> 13) Add AES Key wrap algorithm
>
> "24.16 AES Key Wrap
>
> 24.16.1 Description
>
> The AES Key Wrap algorithm is described in RFC3394. This specification also
> requires the use of padding according to RFC5649 in order to support wrapped
> keys of arbitrary size.

"requires" is a weak word here. Would you say that it's a correct
interpretation that AES Key Wrap algorithm MUST support RFC 5649?

>
> 24.16.2 Registration
>
> The recognized algorithm name for this algorithm is "AES-KW".
>
> Operation   | Parameters       | Result
> ================================================
> generateKey | AesKeyGenParams  | Key
> importKey   | None             | Key
> exportKey   | None             | ArrayBufferView
> wrap     | None             | ArrayBufferView
> unwrap      | None             | Key

I must admit, I'm not entirely sure I follow the distinction between
the wrap/unwrap operation here for AES-KW, and all of the
import/export additions/changes to the other algorithms. It seems only
one or the other is necessary/appropriate? Have I missed something?

>
> 24.16.3 Operations
>
> Import Key
>   When importing a key, the resulting KeyImporter shall behave as follows:
>
>     1. Upon invoking import:
>       1. If the key is wrapped, perform the unwrap operation defined for the
> wrapAlgorithm
>       2. Parse and extract the AES Key Wrap key
>       3. If the operation resulted in an error, raise an error and terminate
> the
>         operation.
>       4. Let result be a Key of the imported public or private key.
>
> Export Key
>   When exporting a key, the resulting KeyExporter shall behave as follows:
>
>     1. Upon invoking export:
>       1. Encode the AES Key Wrap key into the format
>       2. If the key is to be wrapped, perform the wrap operation defined for
> the wrapAlgorithm
>       3. If the operation resulted in an error, raise an error and terminate
> the
>          operation.
>       4. Let result be an ArrayBufferView of the exported public or private
> key.
>
> Unwrap:
>
> When unwrapping and importing a wrapped key, the following unwrapping steps
> shall be performed first, followed by the steps of the importKey operation
> for the appropriate key algorithm:
>
> 1. If the wrapKey is not an AES Key Wrap key, throw a NotSupporterError and
> terminate the operation
> 2. Apply the RFC5649 unwrapping operation to the provided key, using the
> wrapKey
> 1. If the result of the unwrapping operation is a failure of the data
> integrity check, raise an error and terminate the operation
> 2. Otherwise, if the key data specifies a key algorithm
> 1. if the keyAlgorithm parameter to the import operation is not null and the
> key algorithm specified by the key data is not compatible with the
> keyAlgorithm parameter, raise an error and terminate the operation.

Can you clarify a bit on "key algorithm specified by the key data". It
sounds like you mean translating the JWK alg (aka a JWA) into a Web
Crypto alg, and then comparing them. Is that correct? What if the
keyAlgorithm parameters specifies parameters that cannot be encoded
within JWA? Are those comparable/compatible? Is there some
normalization that needs to occur before comparison?

If we do go with this, I would almost suggest dropping the comparison
entirely - letting the algorithm be fully dictated by the
unwrapper/importer.

> 2. Otherwise, if the keyAlgorithm parameter is null, set the key algorithm
> to be the key algorithm specified by the key data
> 3. Continue the key import operation using the output of the unwrap
> operation according to the requirements of the key algorithm
>
> Wrap
>
> When wrapping and exporting a key, the resulting key exporter shall behave
> according to the specification of the key algorithm with the following
> additional requirements for the wrapping step:
>
> 1. If the wrapKey is not an AES Key Wrap key, throw a NotSupporterError and
> terminate the operation
> 2. Apply the RFC5649 wrapping operation to the output of the key export
> operation
> 3. Let the result be an ArrayBufferView of the exported, wrapped, key"
>
> 14) Provide an example of wrapped key import
>
> "26.4 Wrapped key import
>
> // The key type, exportability, start and end time are specified in the JWK
> structure.
> // It is assumed JWK is extended to support private and symmetric keys.
> // For example, the following could describe an AES CTR encryption key (note
> this uses values
> // not specified in JWK which so far only addresses public keys. However,
> JWK can be extended
> // on a 'specification required' basis through registration with IANA).
> //
> // keyJwk = { "use" : "enc", "alg" : "A256", "exportable" : false, "key" :
> "56giug87f298gf9724gf9==" }
> //
> // for the purposes of this example, wrappedKeyJwk = AESKeyWrap( keyJwk,
> wrappingKey )
>
> var keyImporter = window.crypto.createKeyImporter(  "jwk",
> wrappedKeyJwk,
> "AES-CTR",
> "AES-KW",
> wrappingKey,
> true, // temporary
> true, // extractable
> [ "encrypt" ] );
>  keyImporter.onComplete = function(event) {
>   var key = event.target.result;
>   console.log("Imported key ID: " + key.id + "; algorithm " +
> key.algorithm.name + "; exportable " + key.exportable );
> };
> keyImporter.import();
>
> In the above example, the final output should indicate "exportable false",
> because the exportable attribute in the wrapped JWK structure is set to
> false."

If you've wrapped a key into JWK, I would strongly suggest that you
just exported it :)

For example, using a known/fixed-by-an-attacker Key Wrapping Key in
order to export the un-extractable/un-exportable key would allow me to
easily recover the original key text.

>
> [1] http://www.w3.org/2012/webcrypto/track/issues/35

Received on Tuesday, 30 October 2012 09:05:11 UTC