#1 2024-11-01 07:29:25

cadnan
Member
From: Sweden
Registered: 2020-09-22
Posts: 16

Validating JWT from JWKS certs in OAuth2

The task is rather simple, but somehow I cannot make it happen.
I would like to validate a JWT that is received when performing OAuth2 authentication. In order to obtain public key of the server I need to get JWKS from its URL. JWKS provides values like: "kid", "kty", "alg", "use", "n", "e", "x5c", "x5t", "x5t#S256". "x5c" is of interest here as it is a base64 encoded certificate chain containing public key. And here comes my problem. The following (minimal) code is not working for me:

procedure ChainInfo;
var
  cert_chain: TX509;
  cert_rawbytes: rawbytestring;
begin
  cert_chain := TX509.Create;
  try
    cert_rawbytes := Base64ToBin('MIICo......'); // value from "x5c" field
    if cert_chain.LoadFromDer(cert_rawbytes) then
      ShowMessage(cert_chain.PeerInfo);
  finally
    cert_chain.Free;
  end;
end;

By not working I mean I always get false result from LoadFromDer function.
I tried saving to a file (cert.crt) the cert_rawbytes content and viewing it with openssl which reported a lot of problems with the file.
Interestingly, if I decode x5c field using TNetEncoding.Base64.DecodeStringToBytes('MIICo...') and save the resulting TBytes to a file, I can view the content of the file with openssl. But the moment I convert those same TBytes to RawByteString I am again failing to view the content of the file.

So, something rather obvious I am missing but just cannot figure it out..

Offline

#2 2024-11-01 12:13:18

ab
Administrator
From: France
Registered: 2010-06-21
Posts: 14,707
Website

Re: Validating JWT from JWKS certs in OAuth2

TX509 holds a single certificate, not a certificates chain, which contains several certificates.

And in fact, the "x5c" field is not a chain of X.509 certificates, but a way to identify the certificate.
https://stackoverflow.com/a/64098091/458259

From the security point of view - do not use the x5c certificate to validate the signature directly. In that case, anybody could just provide their own certificate and spoof any identity.

So you would need the public key anyway, or a true X.509 signer, from another source - not from the JWT.

We need more info about the "x5c" field content.
"PKIX certificate value" is pretty vague in practice.
Could you put the "x5c" field in a gist (not in the forum itself)?
- note that this is a public key, so it is safe to publish.

Offline

#3 2024-11-01 14:35:51

danielkuettner
Member
From: Germany
Registered: 2014-08-06
Posts: 357

Re: Validating JWT from JWKS certs in OAuth2

I've implemented this task some time ago to check if I could handle it. It's not so easy, but perhaps the situation is more easy if I would have a real AD.

To verify the JWT you need the public key.
The public key in pem format you get from the x5c value. In my test case (at microsoft) the key changes every time (I can't remember the interval e.g. every 30 days).
To get it you have to call -> https://login.microsoftonline.com/'+ response.tenantId +'/discovery/v2.0/keys?appid=...
and compare the kid included in the JWT which you want to verify.

If you have the public key you can verify the JWT with this func:

function VerifyJWT(const aJWT: RawByteString; const aPublicKey: RawByteString; out JWT: Variant): Boolean;
  function VerifyWithmORMot(const aJWT: RawByteString; out JWT: Variant): Boolean;
  var
    Header, Payload, Signature: RawByteString;
    sigBinary, msgBinary: TBytes;
    idx, step: Integer;
    aBuf: PAnsiChar;
  begin
    Header:= '';
    Payload:= '';
    Signature:= '';
    idx:= 0;
    step:= 0;
    aBuf:= PAnsiChar(aJWT);
    while aBuf[idx] <> '' do begin
       INC(idx);
       if aBuf[idx] = '.' then begin
         INC(step);
         if step=1 then
           SetString(Header, aBuf, idx)
         else
         if step=2 then
           SetString(Payload, aBuf, idx)
         else
           SetString(Signature, aBuf, idx);
         INC(aBuf, idx+1);
         idx:= 0;

       end;
    end;

    if (idx > 0) and (step < 3) then
      SetString(Signature, aBuf, idx);

    JWT:= _Obj([]);
    sigBinary:= Base64UrlDecode(Signature);
    RawByteStringToBytes(Header+'.'+Payload, msgBinary);

    Result:= OpenSslVerify('', '', Pointer(msgBinary), Pointer(aPublicKey), Pointer(sigBinary), Length(msgBinary), Length(aPublicKey), Length(sigBinary));
    if Result then
      JWT:= Js2Var(Base64uriToBin(PAnsiChar(Payload), Length(Payload)));
  end;

begin
  Result:= VerifyWithmORMot(aJWT, JWT);
end;

Offline

#4 2024-11-01 18:47:01

cadnan
Member
From: Sweden
Registered: 2020-09-22
Posts: 16

Re: Validating JWT from JWKS certs in OAuth2

Thanks a lot Arnaud and Daniel for helping out. I have created a gist to explain a bit better with examples on what I want to achieve:
https://gist.github.com/cadnan/532751ef … 7543a2947c

We are at the moment using Keycloak as an OpenID Connect identity provider. Keycloak has a unique feature where you can access its public key in an easy json response from a specific url of your realm. However, this is not a standardized way.. I am scared to see that Microsoft has it's own way of doing it. AFAIK, if you have a .wellknown URL for your identity provider you should have a jwks_uri property which should give you a list of certificates with the famous x5c field.

In my gist I have explained how I extracted public key from x5c but using command line tools (certutil and openssl). It would be great if such could be achieved by mORMot2 and I have a feeling we have everything in place for that.

Offline

#5 2024-11-01 18:51:59

cadnan
Member
From: Sweden
Registered: 2020-09-22
Posts: 16

Re: Validating JWT from JWKS certs in OAuth2

Btw Daniel, verifying JWT in mORMot2 is a rather easy process (once you have a public key):

function VerifyToken(const APublicKey: RawByteString; const AToken: RawUTF8): boolean;
var
  FToken: TJWTAbstract;
  jwt: TJWTContent;
begin
  FToken := TJwtCrypt.Create(caaRS256, APublicKey, [jrcIssuer], []);
  FToken.Options := [joHeaderParse, joAllowUnexpectedClaims];
  FToken.Verify(access_token, jwt);
  Result := jwt.result = jwtValid;
end;

Offline

#6 2024-11-01 19:32:13

danielkuettner
Member
From: Germany
Registered: 2014-08-06
Posts: 357

Re: Validating JWT from JWKS certs in OAuth2

@cadnan works and is much easier than my version, thanks.

but you shouldn't forget

try
...
finally
  FToken.Free;
end;

Last edited by danielkuettner (2024-11-01 20:15:34)

Offline

Board footer

Powered by FluxBB