Koos Gadellaa
Koos Gadellaa

Reputation: 1254

How to verify a JWKS's integrity and authenticity

I've run into the following. I have a JWKS which exposes my public keyset. However, I want to ensure that the keys are not modified in-transit through some man-in-the-middle. I don't see any support for this.

Of course, I'm free to add a signature to the JSON element next to the keys element. By having this done with a private key of a certificate, an outside party could use PKI to validate the certificate, thus validate the signature, thus validate the public keys in my JWKS. I could use JWS (if I don't mind the keys not being directly human-readable) or something like JSF (but that doesn't look too well supported/used). But I'm wondering if there is something I missed.

Preferably I'd use something in which existing solutions /can/ still use the message and decide themselves if they want to verify the signature.

Are there solutions by spec?

Upvotes: 1

Views: 1245

Answers (3)

Koos Gadellaa
Koos Gadellaa

Reputation: 1254

Another solution is to sign the JWKS using JWS (https://datatracker.ietf.org/doc/html/rfc7515)

This allows for the json of the jwks response to be signed. The signature can be verified by the retrieving party.

Similar to most Json web standards, it supports x5c as well, allowing a certificate chain to be attached in the header.

The possible disadvantage is it uses a header, which most webservers have a maximum size of 8kb or so; and a certificate is about 1.2kb in DER format. Assuming a 'regular' chain of 3, that's 4kb of headers. Tack on to that cookies, messagesize, content-type, etc and you may hit HTTP431 HEADER TOO LARGE...

Upvotes: 0

Koos Gadellaa
Koos Gadellaa

Reputation: 1254

Another solution is that the keys provided can be signed as well. If a jwks response contains keys, the keys are allowed to be formatted as the following:

"keys": [
  {
    "alg": "RS256",
    "kty": "RSA",
    "use": "sig",
    "x5c": [
      "MIIC+DCCAeCgAwIBAgIJBIGjYW6hFpn2MA0GCSqGSIb3DQEBBQUAMCMxITAfBgNVBAMTGGN1c3RvbWVyLWRlbW9zLmF1dGgwLmNvbTAeFw0xNjExMjIyMjIyMDVaFw0zMDA4MDEyMjIyMDVaMCMxITAfBgNVBAMTGGN1c3RvbWVyLWRlbW9zLmF1dGgwLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMnjZc5bm/eGIHq09N9HKHahM7Y31P0ul+A2wwP4lSpIwFrWHzxw88/7Dwk9QMc+orGXX95R6av4GF+Es/nG3uK45ooMVMa/hYCh0Mtx3gnSuoTavQEkLzCvSwTqVwzZ+5noukWVqJuMKNwjL77GNcPLY7Xy2/skMCT5bR8UoWaufooQvYq6SyPcRAU4BtdquZRiBT4U5f+4pwNTxSvey7ki50yc1tG49Per/0zA4O6Tlpv8x7Red6m1bCNHt7+Z5nSl3RX/QYyAEUX1a28VcYmR41Osy+o2OUCXYdUAphDaHo4/8rbKTJhlu8jEcc1KoMXAKjgaVZtG/v5ltx6AXY0CAwEAAaMvMC0wDAYDVR0TBAUwAwEB/zAdBgNVHQ4EFgQUQxFG602h1cG+pnyvJoy9pGJJoCswDQYJKoZIhvcNAQEFBQADggEBAGvtCbzGNBUJPLICth3mLsX0Z4z8T8iu4tyoiuAshP/Ry/ZBnFnXmhD8vwgMZ2lTgUWwlrvlgN+fAtYKnwFO2G3BOCFw96Nm8So9sjTda9CCZ3dhoH57F/hVMBB0K6xhklAc0b5ZxUpCIN92v/w+xZoz1XQBHe8ZbRHaP1HpRM4M7DJk2G5cgUCyu3UBvYS41sHvzrxQ3z7vIePRA4WF4bEkfX12gvny0RsPkrbVMXX1Rj9t6V7QXrbPYBAO+43JvDGYawxYVvLhz+BJ45x50GFQmHszfY3BR9TPK8xmMmQwtIvLu1PMttNCs7niCYkSiUv2sc2mlq1i3IashGkkgmo="
    ],
    "n": "yeNlzlub94YgerT030codqEztjfU_S6X4DbDA_iVKkjAWtYfPHDzz_sPCT1Axz6isZdf3lHpq_gYX4Sz-cbe4rjmigxUxr-FgKHQy3HeCdK6hNq9ASQvMK9LBOpXDNn7mei6RZWom4wo3CMvvsY1w8tjtfLb-yQwJPltHxShZq5-ihC9irpLI9xEBTgG12q5lGIFPhTl_7inA1PFK97LuSLnTJzW0bj096v_TMDg7pOWm_zHtF53qbVsI0e3v5nmdKXdFf9BjIARRfVrbxVxiZHjU6zL6jY5QJdh1QCmENoejj_ytspMmGW7yMRxzUqgxcAqOBpVm0b-_mW3HoBdjQ",
    "e": "AQAB",
    "kid": "NjVBRjY5MDlCMUIwNzU4RTA2QzZFMDQ4QzQ2MDAyQjVDNjk1RTM2Qg",
    "x5t": "NjVBRjY5MDlCMUIwNzU4RTA2QzZFMDQ4QzQ2MDAyQjVDNjk1RTM2Qg"
  }
]

As can be seen, the keys themselves can be signed using a certificate. With this it is possible to verify the signature of the key, and if the key is signed using a trusted certificate chain (e.g. the root certificate is in our truststore), all that is necessary is verifying the signing entity is trusted. This can be done using a simple CN check.

In essense, this is saying: we only trust keys, signed by "Kickass Company JWKS", using the certificate verifying that name, who's legitimacy we can verify through the chain of trust

It does imply that fetching keys from a JWKS endpoint becomes expensive (since each key needs to be vetted), but those are commonly cached anyway.

Upvotes: 0

Koos Gadellaa
Koos Gadellaa

Reputation: 1254

A way to do this (which is used in the wild) is described at https://www.nimbusds.com/products/server/docs/api/jwk-set#signed-keys which uses a jwt. So, instead of exposing something like /jwks, I could expose a /jwks.jwk endpoint.

In it, it could look something like this:

{
  "iss"  : "https://c2id.com",
  "sub"  : "https://c2id.com",
  "iat"  : 1594030600,
  "keys" : [
       {
         "kty" : "RSA",
         "use" : "sig",
         "kid" : "P9Zd",
         "e"   : "AQAB",
         "n"   : "kWp2zRA23Z3vTL4uoe8kTFptxBVFunIoP4t_8TDYJrOb7D1iZNDXVeEsYKp6ppmrTZDAgd-cNOTKLd4M39WJc5FN0maTAVKJc7NxklDeKc4dMe1BGvTZNG4MpWBo-taKULlYUu0ltYJuLzOjIrTHfarucrGoRWqM0sl3z2-fv9k"
       }
    ]
}

which would allow clients to verify the jwt's authenticity, and if it's authentic, they could then accept the supplied keys.

The /jwks.jwk could then require verification by using a PKI, by supplying the certificate chain as is described in the JWKS spec: https://www.rfc-editor.org/rfc/rfc7515#section-4.1.6

This would then have the following sample header:

  kid: '-1614245140',
  x5t: 'lDdNIsb3FxulMcYdAXxYJ_Z5950',   <-- the thumbprint of the certificate used for this signing (should be the last in the x5c)
  x5c: [                                <-- the certificate chain, starting with the root
    'MIIDqjCCApKgAwIBAgIESLNEvDA ...',  <-- the root certificate
    'MIICwzCCAasCCQCKVy9eKjvi+jA ...',  <-- the intermediary certificate
    'MIIDTDCCAjSgAwIBAgIJAPlnQYH...'    <-- the subject certificate (i.e. the one in the application, e.g. 'oam-jwks'
  ],
  alg: 'RS256'  <-- the algorithm used
}

Clients can then use the certificate chain to validate the authenticity of the certificate used for signing, use that certificate to verify the jwk's authenticity, and then happily use the supplied keyset.

Upvotes: 1

Related Questions