JWTs? JWKs? ‘kid’s? ‘x5t’s? Oh my!

Содержание

There are no shortage of acronyms in the security space, and shifting towards centralised-security, rather than perimeter-based-security, has added even more. As I have been playing with solutions around centralised identity services, such as Oracle’s Identity Cloud Service, I have found myself spending more and more time in IETF RFCs in order to understand these concepts. While there is a lot of value in the standards documents, they assume a lot of knowledge and I often found myself wishing for a slightly more approachable, high level description of the elements I was dealing with. While there is something tempting about being part of the secret ‘We read the security RFCs’ club, I resisted this, and took it upon myself to provide this higher level overview of these important concepts.

JWTs – JSON Web Tokens

Unlike the other acronyms explored in this post, JWTs have a lot of excellent resources available online, including the fantastic jwt.io, which, in addition to a solid overview of the concept, has a very handy JWT Debugger. There is little value in repeating what is written there, but it is important to start with an understanding of JWTs in order to understand the role that the other security acronyms and terms have to play.

JWTs essentially encode any sets of identity claims into a payload, provide some header data about how it is to be signed, then calculate a signature using one of several algorithms and append that signature to the header and claims.

This whole process is based upon a cluster of acronyms, and while the specifics of them are pretty unimportant for understanding the concept, I will provide them here for reference, and you can explore the RFCs if you feel it relevant.

  • JWT – JSON Web Token – The resulting signed Identity Assertion – RFC 7519
  • JWA – JSON Web Algorithm – A set of supported algorithms for signing a JWT – RFC 7518
  • JWS – JSON Web Signature – Officially a superset to describe JSON data structures secured with digital signatures, but the relevant part is that the spec defines the how to describe the process used for signing – RFC 7515
  • JWK JSON Web Key A mechanism for representing keys for validating JWTs RFC 7517

Most commonly, the algorithms used for signing JWTs are based upon public/private key cryptography. This facilitates a centralised identity provider, who holds the private key, to issue signed tokens which can be validated by remote services using the public key, without them needing to communicate back to the identity provider.

JWTSJWKS_PubPrivateJWT

While it is also possible to utilise a ‘shared secret’ system for signing JWTs using symmetrical keys, this requires a strong trust relationship between services, and in theory could be useful if both services are acting in an identity provider role, but this scenario is fairly specialised, and is certainly a far less flexible pattern.

JWKs – JSON Web Keys

A JSON Web Key provides a mechanism for distributing the public keys that can be used to verify JWTs. As a result, identity providers will typically provide an API endpoint to allow clients to retrieve a JWK, which they can then use to verify tokens issued by that identity provider.

Where JWTs allow you to define pretty much whatever identity information you would like in the payload, a JWK has a very specific structure. Typically, as it relates to JWT verification, a JWK will look like this (though technically this is a JWK Set, containing a single JWK entry):

JWKs were intended to allow clients to select an appropriate key for whatever JWTs are presented to them, and so most of these attributes are used to describe how to select the appropriate key. RFC 7517 describes all of these attributes in detail, but here is the quick form:

  • kty – Key Type – Identifies the family of algorithms used with this key
  • alg – Algorithm – Identifies the specific algorithm
  • use – Usage – ‘sig’ for signing keys, ‘enc’ for encryption keys
  • x5t – X.509 Certificate Thumbprint – Used to identify specific certificates
  • kid – Key Identifier – Acts as an ‘alias’ for the key
  • x5c – X.509 Certificate Chain – Chain of certificates used for verification. The first entry in the array is always the cert to use for token verification. The other certificates can be used to verify this first certificate.

In many cases, token issuers will only use a single cert to sign their JWTs, which means that there will only be a single JWK, and the certificate to use to validate JWT will simply be able to be accessed at:

It may be worth noting that the certificates distributed as a component of a JWK are base64 encoded certificate values. I have found that multiple libraries expect to see certificates represented as this value prefixed by “—BEGIN CERTIFICATE—” and suffixed with “—END CERTIFICATE—“.

Alternatively, an ‘x5u’ attribute can be used which provides a link to retrieve a remote certificate.

Certificates, despite being the most common way to distribute the keys used to validate JWTs, are not actually mandatory. Instead, if a certificate is not supplied, alternative values to describe the key are defined on a per-algorithm basis, in section 6 of RFC 7518. For instance, if the key represents an RSA public is used, an attribute ‘n’ (for the modulus) and ‘e’ (for the exponent) can be provided in lieu or alongside a certificate. If both are supplied, then the first x5c entry must be consistent with the supplied values. Certificates will be usually be easier to work with than the key primitives, but most languages should support this in one way or another.

In cases with more complex multi-key requirements, such as where your service supports multiple identity providers and stores their signing keys in a local JWK structure, some process might be needed in order to select the appropriate certificate. This is where the header data of the JWT comes into play. When describing JWTs before, I mentioned that the header is used to store information about how the JWT is signed, and it uses many of the same fields as a JWK. An example JWT header could be:

As such in order to select the appropriate certificate to use to verify this JWT, the JWK keys set could be traversed, looking for a key with the ‘kid’ (Key Identifier) that matches “my_key_alias” and also supports the RS256 algorithm, then grabbing x5c[0] from that JSON object. The following sections outline in more detail the mechanisms that the JWK standard offers for identifying the appropriate key to select in order to perform verification of a JWT.

‘kid’s and ‘x5t’s – Helpful ways to identify a key

‘kid’s and ‘x5t’s can be used to uniquely identify a key. The ‘kid’ or Key Identifier is an arbitrary alias for a key, allowing identity providers to provide a simple name to identify their signing key, and then repeat that identifier in the tokens they issue. As this is arbitrary, it is somewhat prone to collision (for instance, if multiple providers simply called their key ‘SIGNING_KEY’), and so ‘x5t’s, or X.509 Certificate Thumbprints provide a more reliable way to identify a key, while working in a similar way (identifying a certificate in a JWK, and indicating the key to use to validate in a JWT header)

The total information on how x5t values work that I have found is encapsulated in section 4.1.7 of RFC 7515, here.

The “x5t” (X.509 certificate SHA-1 thumbprint) Header Parameter is a base64url-encoded SHA-1 thumbprint (a.k.a. digest) of the DER encoding of the X.509 certificate [RFC5280] corresponding to the key used to digitally sign the JWS.

If you have spent much time dealing with certificates, this reference to ‘SHA-1 thumbprint’ will probably be pretty familiar. A typical output of a Java keytool or OpenSSL command will include this value. For instance, running –list on a java keystore:

You could also calculate it yourself if you have a certificate file and a library that supports SHA1 (or you could code the algorithm yourself, you madman), but using one of the above tools or similar is probably both more common and simpler.

Turning this SHA1 fingerprint into an x5t is explained in that single line descriptor too. It simply needs to be base64 and url encoded. The fingerprint is presented by keytool and OpenSSL as a colon-separated octet string, and so needs a little pre-processing to encode. I used something like the following Node.js snippet:

This converts the colon-separated octet string to a byte array (using a Node.js Buffer) by parsing each couplet as a hexadecimal, then using native Buffer base64 encoding functionality before removing the padding, and replacing any ‘+’ and ‘/’ characters.

Edit: Previously this section used native Javascript URI encoding, as opposed to the specific approach described in RFC7515 Appendix C, which defines that base64url-encoding is not the same as base64, URL-encoded. Sorry for any confusion.

This provides a compact string format to represent the certificate hash, and as it is simply an encoded form of that hash, is a collision resistant mechanism for identifying the signing key. While it is not mandated by any standard, I would advise prioritising this over ‘kid’ as a certificate identifier, though falling back on ‘kid’ is fine.

‘alg’s and ‘kty’s – Telling you how to verify a token

JWTs can include an ‘alg’ attribute in the header, which a service should respect when verifying a supplied token (unless the ‘alg’ is ‘none’ in which case you should throw the token away and write a sternly worded email to the issuer about the importance of security). This is valuable for selecting an appropriate certificate to use to validate a token, as oddly enough, certificates designed to work with Elliptic Curve algorithms don’t tend to work for RSA algorithms and vice versa.

Unfortunately, ‘alg’, like most fields is an optional attribute in JWKs, so the comparison for “alg”:”RS256″ may need to instead look at the family of algorithms, denoted by ‘kty’, attempting to match ‘RSA’ as the parent algorithm of RS256. A mapping of common ‘alg’ parameters to ‘kty’ parents is given in the following table.

RS256 RSA
RS384
RS512
ES256 EC
ES384
ES512
HS256 oct (according to RFC 7518, though seeing HMAC here would not be surprising)
HS384
HS512

This works because shorter hashes can use the same keys/certificates as longer hashes, such that an RSA keypair can be used for both RS256 and RS512 algorithms, provided the key was generated with sufficient bits.

I hope that this write up has helped to explain how some of the less documented components of the JSON Web Token stack align. As organisations increasingly adopt a system of distributed infrastructure and services, the ability to centrally issue and remotely validate tokens will become more and more valuable. The use of JWT as a token format, and JWK as a format by which to both distribute and store the certificates required to validate these JWTs provides a mechanism by which to enable this. While some of the terms used in the specifications are a little obtuse, and couched security specific language, I would expect to see increased uptake and hope that this helped to at least provide a high-level understanding. Unfortunately, due to the lack of information available on the subject, if you do need more information, you might have to take a deep breath and dive into these RFCs.


Источник: redthunder.blog