You are not logged in.
Pages: 1
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
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.
Online
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
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
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
@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
Pages: 1