Proposed Refactoring of JOSE to Align Encryption and Key WrappingBBNrlb@ipv.sx
Security
JOSE Working GroupInternet-DraftThe discussions around key wrapping in the JOSE working group have raised new requirements for wrapped keys, namely: (1) Wrapping keys other than symmetric keys, (2) cryptographically binding attributes to keys, and (3) allowing the use of AEAD cryptographic algorithms for key wrapping (other than AES-KW). This document proposes a refactoring of the JOSE document set that provides a cleaner conceptual structure for JWS / JWE and transparent support for wrapped keys, all with a relatively minor impact on the compact form of JWS and JWE objects.The goal of a JOSE object is to provide the recipient with the result of a cryptographic operation (encryption or signature) and instructions for how to process that result. These instructutions are in two main parts: (1) A cryptographic algorithm to be applied, and (2) the key to be used with that algorithm (in wrapped form). The current structure of the JWE and JWS headers scatters these two information elements across several different header parameters. For example, if an object has been encrypted with the “direct” mode of key agreement, then the recipient must reconstruct the algorithm from the “enc”, “epu”, and “epv” parameters. If a developer wants to validate that a JOSE algorithm has all required parameters for a given algorithm, he must search through all header fields.It would help clarify the structure of JOSE objects if algorithm parameters and key parameters could be grouped together. We propose to make two changes to the JWE and JWS headers:Re-define the “alg” parameter to contain all necessary parameters to describe the encryption algorithm.Define a new “key” parameter to contain all necessary paramters to describe the CMK.This restructuring also simplifies key wrapping, by using JWE for key wrapping. Since the encrypted CMK is just another encrypted object, and because the wrapped key is encapsulated in the “key” object, it can re-use the “alg” and “enc” fields – making the encrypted key just another JWE. The following example shows how the proposed refactoring changes the implicit grouping of key-related fields in JWE into an explicit grouping.This example JWE has three fields, “enc”, “alg”, and “kid”. One of these fields relates to how the content of the JWE itself is encrypted; the other two relate to how the key is encrypted. The proposed syntax groups the key-related fields together into a JSON object. In fact, this new object can be thought of a second JWE, encrypting the key instead of the content.This change has a minimal impact on the JWE and JWS compact serializations, both in terms of representation and processing. In the example above, the proposed header has the same information content as the original header, and is only 8 octets longer. For JWE, the proposed form even offers a simplification in processing, since the encrypted key can be processed with the same code as the encrypted content – both are JWEs.The proposed form also allows for a much cleaner JSON representation than is currently allowed. The following figure illustrates the current and proposed JSON serializations. The proposed serialization is much more developer friendly, since it exposes header information directly, instead of in a base64-encoded string.The remainder of this document describes the rational for the proposed changes in more detail, and provides an outline of the detailed changes that would be needed to implement the proposal.Enable key wrapping with JWEAvoid having more than one format for the same key wrapped with the same algorithmEnable key wrapping with all AEAD algorithms, for JWE as well as external wrapped keysEnable password-based encryption for JWEJWE/JWS: Define required “alg” and “key” parameters
“alg”: JWA algorithm object“key”: Key identifier or JWE containing encrypted keyJWE/JWS: Define a revised JSON serialization and mapping to the compact serializationJWA: Refactor to describe algorithm objects for JWE/JWS
Move algorithm parameters from JWE to JWADefine validation rules per algorithm (which parameters are required)Add password-based encryption parametersJWK: Define a procedure for using JWE to wrap keys
OPTIONAL: Define a compact serialization for keys Details of these proposals are described below.Costs:
Few (<30) bytes of extra header overheadBreaks compatibility with old codeBenefits:
Clean architecture covering JWE, JWS, wrapped JWKCode re-use bewteen JWE processing and wrapped key processingEasier validation rules for algorithm parametersKey wrapping based on JWE in all casesSupport for new use cases in compact serializationKey management for MACIn this section, we consider two examples of how the proposed serialization offers cleaner syntax and more flexibility than the current structure. First, we look at a JWE encrypted with AES-GCM, using a key wrapped with AES-KW. This allows us to directly compare the two formats. Second, we consider the same JWE, but using a key that is also protected with AES-GCM. This case is easily supported in the proposed syntax, but impossible to support in the current syntax. In both cases, the parties have a pre-shared key with identifier “preSharedKey”.To create an an object encrypted with AES-GCM and AES-KW, we need to specify the following parameters:Algorithm name (A128GCM)Key wrapping algorithm (AES-KW)Key wrapping key ID (“preSharedKey”)96-bit initialization vector Wrapped keyCiphertextIntegrity check valueIn the current compact form, the header encodes the first three of these, and the remainder are appended as base64url-encoded components.In the proposed compact form, the header would have the same information, but arranged so that the key-wrapping parameters (“alg”, “kid”) are gathered under a “key” parameter. The remaining components are appended in the same way.The proposed compact form of this JWE object is 189 octets long, while the current form is 178. So the overhead imposed by the refactoring is 11 octets, or 6%.The difference is clearer with regard to the JSON representations. The current JSON form is just as opaque as the compact form. By contrast, the proposed JSON form provides the header information without base64-encoding, making it more easily accessible. The UTF-8 serialization of the proposed JSON syntax is also 27% shorter than the serialization of the current JSON syntax (209 vs. 287 octets).The proposed syntax uses JWE to wrap the keys used for JWE. This allows key wrapping to take advantage of all of the algorithms available for content encryption. So while key wrapping with AES-GCM is impossible with current JWE specification, the proposed modification makes it possible, in both JSON and compact form.The JSON syntax shows how the parameters are laid out. The “alg” parameter at the top level specifies the parameters for the content encryption. The “key” parameter at the top level contains the wrapped key as a JWE, with its own “alg” parameter specifying independent parameters for GCM. The “data” attribute of the “key” object (key.data) contains the wrapped key, and the “mac” attribute (key.mac) contains the GCM integrity check value over the key. The “data” and “mac” attributes at the top level contain the encrypted content and its integrity check value.A recipient would process this JWE in the same manner as a normal JWE. First, the recipient decrypts the key using the GCM parameters inside the “key” parameter and the key identified by “preSharedKey”. Second, the recipient decrypts the content using the top-level GCM parameters and the unwrapped key.Even though this object uses parameters that cannot be accounted for in the current JWE compact encoding, it can still be rendered in the compact encoding. The parameters that the compact encoding accommodates are moved out of the header, including the top-level IV, the encrypted key, the ciphertext, and the integrity check value. The other binary parameters (the key wrapping IV and integrity check value) remain in the header.This compact encoding is larger than the one in the previous example (264 vs. 209 octets), but 25% shorter than the base64url-encoded serialization of the JSON form (264 vs. 352 octets).In the course of the key wrapping discussions in JOSE, a few new requirements have arisen:Wrap private and symmetric keys Wrap keys with attributes attached.Wrap keys using general AEAD algorithms (e.g., AES-GCM)Encrypt keys and JWEs with password-based encryptionA design goal in meeting these requirements should be to duplicate functionality as little as possible. For example, it would violate this goal to have JWE define one way of encrypting a symmetric key with no attributes, and for a JWK key wrapping specification to define another. In addition, we would like to avoid the need for obscure algorithms, in particular RSA-KEM.With the above requirements and goals in mind, compare the algorithm requirements of JWE and wrapped JWK:It makes one wonder whether it might make sense to use the same framework for both of these.As a starting point for considering whether we can align JWE and wrapped JWKs, let’s compare a JWE performing direct encryption with AES-GCM with a notional wrapped key using AES-KW derived from the JWE wrapped key format:Here I’ve assigned some names in JSON to fields that don’t have names in JWE. I’ve labeled the JWE Initialization Vector as “iv”, the JWE Ciphertext as “data”, and the JWE Integrity Value as “mac”.Now, suppose we say that a missing “alg” parameter means that “alg” is assumed to be direct. This isn’t too unreasonable, since “alg” indicates a key wrapping algorithm, and there’s no key wrapping going on here. Then we can omit the alg parameter:The two objects start to look very similar now. They each have three critical sections:A specification of the encryption algorithm and any necessary parameters. (AES-KW requires no parameters, of course.)An identifier for the key being usedThe ciphertext output of the algorithm
We can make the syntax match this conceptual model if we just put the parameters in an object together with the algorithm name. For symmetry, we’ll call the algorithm parameter “alg” in both cases; we’ll rename “kid” to “key” for reasons that will be apparent in a moment.So with a few minor simplifications to the JWE header format, we’ve created a structure that applies naturally to both general encryption (JWE) and key encryption specifically (JWK Key Wrap). The only difference is the “mac” field, which can be optional, since AES-KW has an internal integrity check, much like AES-CCM.Now, how do we deal with JWEs that use a wrapped CMK in this framework? Well, the whole point of the last section was to make JWEs the same as wrapped keys – so we allow the “key” attribute to be a JWE containing the wrapped CMK. For example, if we combine the two objects from the previous section, we can have a JWE representing an encryption with AES-GCM under a key protected with AES-KW.This gives a simple, self-similar structure for wrapped keys. The self-similarity allows us to transparently account for more advanced use cases. For example, if someone wanted to use a CMK that came along with a bunch of attributes, they could use RSA to wrap another symmetric key, and use AES-GCM to wrap the CMK.The example looks kind of ridiculous, but it’s not entirely far-fetched. You could imagine it happening, for example, if a system were distributing content decryption keys with attributes, and a recipient patched one into a JWE to decrypt it with a JWE library.The decryption process can handle the self-similarity naturally with recursion. If the “key” value in the JWE you’re processing is a key you know (e.g., a “kid” for a key you have), then you use that key. Otherwise, you process the “key” value as a JWE and use the decrypted content to decrypt the JWE you started with. In other words, you recursively process encrypted keys until you hit a key you know, then work your way back up the stack decrypting as you go.Obviously, this sort of recursion could be dangerous, since you could end up with many levels of recursion, resulting in memory overflows, etc. However, it doesn’t seem likely that many levels will be necessary in practice, so we could specify that implementations should impose limits on the number of levels of recursion that they will allow.To see how the format outlined above compares to JWE, let’s look at how the normal JWE fields map to the fields in the example object above.As you can see, there’s no new information here, just the same fields rearranged so that there’s less need for special handling of IVs or Encrypted Keys – they’re just a natural part of the format.One last thing: We’ve said that the “data” value corresponding to the JWE Encrypted Key contains a JWK representing the CMK. In order for that value not to impose additional overhead for symmetric keys (by wrapping a JSON-encoded key instead of the raw key octets), it will be necessary to have a compact format for JWKs. Such a format should produce a raw octet string for symmetric keys with now attributes, and may produce an optimized JSON format for other types of keys. A few candidate algorithms are discussed in the detailed considerations below.So far, we’ve been talking about a JSON format, equivalent to the current JSON serialization. How does one take one of these and serialize it to something like the JWE compact encoding? The obvious answer is to keep the other fields the same, and just change the header to accommodate the revised JSON form. So the example we just considered would serialize as a normal JWE, but with the above JSON object as the header, and JWE Initialization Vector, JWE Encrypted Key, and JWE Ciphertext removed (since they’ll be binary components afterward).To see the impact on header size, let’s compare this header with the comparable normal JWE header:The overhead of this proposed change is thus 17 octets of header, which comes to 20 octets after base64url encoding. So while this does change the header structure – so it will break compatibility with existing implementations – it doesn’t change the overall structure of a JWE, and doesn’t add a dramatic amount of overhead.In fact, one could argue that this makes the compact form even more flexible. The current compact form has no way to accommodate, for example, the double-wrapped case above. In the revised header structure, the double-wrapped key would still work: The wrapped CMK would be removed to a binary part, and the secondary wrapped key would remain in the header. It would be gigantic, but it would work.The same story makes sense for JWS, with a couple of revisions. First, JWS signatures with asymmetric keys don’t need wrapped keys, so their “key” values would just be a JWK for the public key. (Other metadata parameters, such as “x5c” could live at the same level as the “alg”, and “key” parameters, or in the JWK.) On the other hand, JWS objects that encode MACs could benefit from having wrapped keys in the “key” field.The second major difference is that for JWS, it is sometimes desirable to store the input to to cryptographic operation (the signed data) in addition to the output (the signature). In the JWE examples above, the “data” field represents the output of the cryptographic operation. So it would be good to define an additional, optional field for JWS, which would contain an octet string for the protected data. If that field were present, the JWS could be process directly; otherwise, it would represent a detached signature.In the above, we’ve described a proposed change to the JWE and JWS headers to move away from a flat bag of parameters, toward a structure with two required fields and two optional fields:REQUIRED: “alg”, containing parameters to describe the algorithm used to process the JWE/JWS objectREQUIRED: “key”, containing the key used to process the JWE/JWS, either directly, in wrapped form, or as an IDOPTIONAL: “data”, containing the ciphertext (JWE) or signed data (JWS)OPTIONAL: “mac”, containing the MAC or signature value for the JWE/JWS objectThis revised structure doesn’t lose any features relative to the current format, since other header fields like “zip” can still be added to the header. And it imposes low length overhead, in the low tens of octets.This simpler structure makes it possible to address all of the requirements above with out significant changes to JWE and JWS. In fact, it goes beyond the design goals – instead of having one way to encrypt keys, we have one way to encrypt anything at all.In the discussion above, we’ve outlined a JSON structure that provides some richer structure than the current JWE header. This requires us to modify somewhat the mapping between a JSON-serialized JWE or JWS object and its compact encoding. In this section, we describe the mapping in detail for JWE. With a minor change to the JWS compact serializatoin (adding fields for IV and encrypted key, which can be left empty), the same translation can be used for both JWE and JWS.Input: JSON object X representing a JWE object in the JSON serializationSet the Encoded JWE Encrypted Key to X.key.data. If X.key is not present, set it to the empty string.Set the Encoded JWE Initialization Vector to X.alg.iv. If X.alg is not present, set it to the empty string.Set the Encoded JWE Ciphertext to X.dataSet the Encoded JWE Integrity Value to X.macDelete the “data” field from X.key, the “iv” field from X.alg, and the “data” and “mac” fields from XBase64url encode the bytes of the UTF-8 representation of the X to create the Encoded JWE Header. Assemble the final representation: The Compact Serialization of this result is the concatenation of the Encoded JWE Header, the Encoded JWE Encrypted Key, the Encoded JWE Initialization Vector, the Encoded JWE Ciphertext, and the Encoded JWE Integrity Value in that order, with the five strings being separated by four period (‘.’) characters.Output: Text string representing a JWE object in the compact serializationThe following JavaScript functions implement this translation, first for the current JSON serialization and second for the proposed serialization (as described above).Input: Text string representing a JWE object in the compact serializationSplit the string on the period (‘.’) character to obtain the Encoded JWE Header, the Encoded JWE Ciphertext, and the Encoded JWE Integrity Value.Base64url decode the Encoded JWE Header and parse it into a JSON object XSet the field X.key.data to the Encoded JWE Encrypted KeySet the field X.alg.iv to the Encoded JWE Initialization VectorSet the field X.data to the Encoded JWE CiphertextSet the field X.mac to the Encoded JWE Integrity ValueOutput: JSON object X representing a JWE object in the JSON serializationThe following JavaScript functions implement this translation, first for the current JSON serialization and second for the proposed serialization (as described above).The refactoring of the JWE and JWS headers described above requires adding one new field to the header (“key”) and redefining one field (“alg”). The new “key” header subsumes the functionality of the “kid” and “jwk” headers; it allows a key to be expressed either directly (as a “jwk” object) or indirectly (as a “kid” string). (We could also just require that either “kid” or “jwk” be present.) With the extension of JWK to wrapped keys, “key” would also support the use of wrapped keys.The re-defined “alg” header would collect all the parameters describing an algorithm into a single object, whose format would be defined in the revised JWA document. For example, an “alg” value for a key agreement algorithm would have a “name” field indicating the key agreement algorithm (as in the current “alg” field), as well as the “epk”, “apu”, and “apv” fields from the current JWE object. An “alg” value for symmetric encryption with AES-GCM would include a “name” field and an “iv” field with an Initialization Vector.The refactoring has no effect on the many JWE and JWS fields that are secondary to cryptographic processing. For example, the “cty” field remains at the top level of the header.In the following tables, we assign each of the current JWS and JWE header fields one of the following dispositions:Key: Subsumed by “key” field (or an alternative instantiation of it)Alg: Subsumed by “alg” field (move to the JWA)Hdr: Remains as a header parameterAt a high level, there are two steps in the key wrap. First, the JSON dictionary for the original JWK is split into “public” and “private” dictionaries, by dictionary key. The private dictionary is marshaled using a marshaling algorithm (see below), then encrypted in a JWE. The public dictionary becomes the “kat” attribute of the JWE.Key unwrapping proceeds in the opposite direction, retrieving the public and private dictionaries from the “kat” and “wk” attributes (unwrapping the private part), then merging the two into a final JWK.The procedures in this section do not presume a given marshaling algorithm. Instead we discuss several possible options below. Each marshaling procedure has the same interface:Inputs:
Key type (“kty”)JSON dictionaryOutput:
Octet string with the marshaled valueBinary flag indicating whether the marshaled value contains JSON contentThe corresponding unmarshaling procedures have corresponding inputs and outputs (octet string + binary and JSON dictionary, respectively). Parameters: Marshaling algorithmInput: JWK object, wrapping algorithm, wrapping key, list of private fieldsConstruct a private object by copying all private fields (listed in the list of private fields) from the input JWK to a new dictionaryConstruct a public object by copying all other fields from the input JWK to a new dictionaryMarshal the private object using the marshaling algorithm, and record the value of the returned “wj” flagCreate a JWE encrypting the marshaled private object with the wrapping algorithm and wrapping keyIf the “wj” flag is set to true, add it to the JWE as the “wj” attributeIf the public object is non-empty, add it to the JWE as the “kat” attributeReturn the JWEOutput: JWE object with optional “kat” and “wj” attributesParameters: Marshaling algorithmInput: JWE object with optional “kat” and “wj” attributesInitialize the unwrapped key to an empty JSON dictionaryIf the “kat” field is present in the JWE, copy its contents into the unwrapped keySet the “wj” flag to false if it is not present in the JWEDecrypt the JWE to obtain the marshalled private dictionaryUnmarshal the private dictionary, using the marshaling algorithm and the “wj” flag Copy all fields from the private dictionary into the unwrapped key dictionary, overwriting existing fields if necessaryReturn the unwrapped key dictionaryOutput: Unwrapped dictionaryThere are several possible compact serializations for JWK, which offer different trade-offs between size and complexity.JWKS0: UTF-8JWKS1: UTF-8, or raw binary for symmetric keys with no attributesJWKS2: UTF-8, or raw binary for one field per key typeJWKS3: UTF-8, with raw binary for a fixed list of fields per key typeJWKS4: UTF-8, with raw binary for an arbitrary list of fields In this section, we describe details for JWKS2 and JWKS3, since JWKS2 doesn’t have significantly more complexity than JWKS1, and JWKS3 is much simpler than JWKS4. For brevity, we list only the encoding procedures. To illustrate the trade-offs of these algorithms, we consider three test cases:A 256-bit AES keyA 256-bit EC private keyA 2048-bit RSA private keyIn addition to the two above algorithms, we also consider the simple wrapping procedure in which the entire JWK is serialized to UTF-8 and wrapped as a JWE.Parameters: Table with one binary field for each key typeInput: JSON dictionary, key typeIf the key type is in the table, and the only field in the dictionary is the binary field for the key type
Set the “wj” flag to falseSet the marshaled JSON to the binary contents of the fieldElse
Set the “wj” flag to trueSet the marshaled JSON to the UTF-8 serialization of the JSON dictionaryOutput: Marshaled JSON, “wj” flagParameters: Table with a list of binary fields for each key typeInput: JSON dictionary, key typeIf the key type is not in the table:
Set the “wj” flag to trueSet the marshaled JSON to the UTF-8 serialization of the JSON dictionaryIf the key type is in the table and there is only one binary field in the list for this key type, and that field is the only field in the JSON dictionary:
Set the “wj” flag to falseSet the marshalled JSON to the binary contents of the fieldElse if the key type is in the table:
Set the “wj” flag to falseCopy all binary fields from the JSON dictionary to a new dictionary, then remove them from the JSON dictionaryIf the JSON dictionary is non-empty, compute its UTF-8 serialization and set the “wj” flag to trueConstruct a list of binary components containing:
The serialized JSON dictionary, if non-emptyThe cached binary field values, in the order of the list of fieldsPrefix each binary component with a two-octet length fieldSet the marshaled JSON to the concatenation of the length-prefixed binary componentsOutput: Marshaled JSON, “wj” flagTest case 1: A 32-bit symmetric key, with the single private field “k”Test case 2: A 256-bit EC private key, with the single private field “d”Test case 3: A 2048-bit RSA private key, with six private fields “d”, “p”, “q”, “dp”, “dq”, “qi”.To characterize the performance of the marshaling algorithms on the test cases, we compute the number of octets used to represent the wrapped key in the JWE (omitting other JWE parameters, which are constant).We consider four cases:Baseline: Length of the UTF-8 serialization of the JWKSimple: Length of the base64url-encoded UTF-8 serialization of the JWKJWKS2: Length of UTF-8 serialization of the public half of the JWK (as would be in the “kat” field), plus the length of the base64url-encoded output of JWKS2 on the private halfJWKS3: Same as JWKS2, but using JWKS3 insteadThe results of this analysis are as follows:As expected, the compact JWKS serializations are more compact in general than the simple approach, because they avoid double-base64 encoding. For test case 2, which involves multiple binary fields in the wrapped key, JWKS2 does a little better than the simple approach because it can handle one field (“d”), and JWKS3 does notably better because it can handle all of the binary fields.The refactoring proposed in this document has several security benefits.First, by using the JWE format for wrapped keys in JWE, JWE can benefit from general AEAD algorithms for key wrapping, for example, AES-GCM as opposed to AES key wrap. These other AEAD algorithms are more widely available than AES key wrap, and offer better security properties in some situations. This benefit is available to the compact serialization as well as the revised JSON format.Second, by using the same format for key encryption and content encryption, code for processing objects in the proposed format will only have to have support one way of decrypting objects. This simplification will reduce the chance for bugs in implementations.Third, the use of consolidated algorithm and key objects allows for simpler validation rules on JOSE objects, again reducing the chance that an improperly-constructed JOSE object will be able to trigger implementation bugs.The current JWE and JWS specifications require header information to be protected under the integrity check provided by the signature, MAC, or AEAD algorithm. This proposal makes the header computation slightly more difficult in the JSON case, since the recipient will have to reconstruct the header by removing some fields from the JSON object. However, no concrete security benefit has been proposed for header integrity, so it may be better to remove header integrity protection in order to allow for cleaner architecture.This memo makes no request of IANA. However, changes to the JOSE specs resulting from this proposal might require adjustments to some IANA registrations.JSON Web Encryption (JWE)JSON Web Encryption (JWE) is a means of representing encrypted content using JavaScript Object Notation (JSON) data structures. Cryptographic algorithms and identifiers for use with this specification are described in the separate JSON Web Algorithms (JWA) specification. Related digital signature and MAC capabilities are described in the separate JSON Web Signature (JWS) specification.JSON Web Signature (JWS)JSON Web Signature (JWS) is a means of representing content secured with digital signatures or Message Authentication Codes (MACs) using JavaScript Object Notation (JSON) data structures. Cryptographic algorithms and identifiers for use with this specification are described in the separate JSON Web Algorithms (JWA) specification. Related encryption capabilities are described in the separate JSON Web Encryption (JWE) specification.JSON Web Algorithms (JWA)The JSON Web Algorithms (JWA) specification enumerates cryptographic algorithms and identifiers to be used with the JSON Web Signature (JWS), JSON Web Encryption (JWE), and JSON Web Key (JWK) specifications.JSON Web Key (JWK)A JSON Web Key (JWK) is a JavaScript Object Notation (JSON) data structure that represents a public key. This specification also defines a JSON Web Key Set (JWK Set) JSON data structure for representing a set of JWKs. Cryptographic algorithms and identifiers for use with this specification are described in the separate JSON Web Algorithms (JWA) specification.