Search code examples
securitydigital-signaturejwk

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?


Solution

  • 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.