|
![]() |
Validate openid token using public keys in JWK |
Peter Royston 2022-12-19 17:39:57 Registered user |
I have some code working which converts JWK public keys into a "RSA PUBLIC KEY" PEM formatted string, but it means introducing another lib to our codebase, and also relies on OPENSSL. I'd rather use StreamSec, which we already use for DKIM, for speed and to keep the codebase lean, etc.
Using our existing PKC Tools 4.0 capabilities, I would like to validate an openid token, or alternately, to convert the JWK into a "RSA PUBLIC KEY" or "PUBLIC KEY" PEM file/string for which I already have code for validation. Ideally, I can avoid the conversion to PEM so as keep things as simple as possible, but either way would be great. At this point, I'm just trying to get the conversion to PEM working to verify that I have prepared the public key object correctly. This is what I have so far, which doesn't raise an error, but also doesn't produce the expected result. function NewCreatePublicPEMFromJWK(json: ISuperObject): string; var Key: tIFPublicKey; RSAKey: TstRSAPublicKey; PEM: TStringStream; nnBin : TArray<Byte>; eeBin : TArray<Byte>; base64URL : TBase64URLURLEncoding; begin Key := tIFPublicKey.Create; RSAKey := TstRSAPublicKey.Create(nil); base64URL := TBase64URLURLEncoding.Create; try nnBin := base64URL.DecodeStringToBytes(json.s['n']+'=='); eeBin := base64URL.DecodeStringToBytes(json.s['e']); Key.N := IntToDSInt(GetDSIPool,BEToN(PUint32(@nnBin)^)); Key.E := IntToDSInt(GetDSIPool,BEToN(PUint32(@eeBin)^)); PEM := TStringStream.Create; try // Save the RSA key in PEM format to the stream RSAKey.EnabledDesignTime := False; RSAKey.EnabledRunTime := False; RSAKey.SignEncoding := seEMSA3; RSAKey.EncryptEncoding := eeEME_PKCS1_v1_5; RSAKey.SetPublicKey(Key); RSAKey.PublicKeyFormat.SelectedOptionNames := 'PKCS1-RSAPublic-a'; RSAKey.SavePublicKeyToStream(PEM,iUnknown); Result := '-----BEGIN PUBLIC KEY-----'#10+BytesToASCII(StrToMime64(PEM.DataString))+#10'-----END PUBLIC KEY-----'; finally PEM.Free; end; finally base64URL.Free; RSAKey.Free; end; end; Am I on the right track? Or is this not something easily accomplished using PKC Tools 4.0? Any guidance would be greatly appreciated. Todd |
Henrick Wibell Hellström 2022-12-20 14:04:15 Registered user |
You seem to be on the right track, but I have have not yet tested your code.
Are you using a Unicode enabled compiler (i.e. Delphi 2009 or later)? In such case you should make sure the the TStringStream uses an 8-bit character encoding, or else your function will output garbage. Also, it might be better to use the StrToMIME64Str() function directly, instead of BytesToASCII(StrToMime64()), but it is optional. |
Henrick Wibell Hellström 2022-12-20 14:11:10 Registered user |
Another thing: The e value of the public key is usually no more than 32 bits, but the n value is typically a lot larger, and in such case your conversion routine won't work. You should better use the iDSIPool.FromBase256() method for converting nnBin to Key.N. That will work as long as the nnBin contain the byte representation of the public key modulus with the most significant octet first. I don't know if that is the case, but I would presume it is.
|
Peter Royston 2022-12-20 19:07:45 Registered user |
Thanks for that information! I now have something that works, below:
function NewCreatePublicPEMFromJWK(json: ISuperObject): string; function Base64BytesToUMPInt(const aValueBytes: TArray<Byte>; aLocal: Boolean = False) : iDSInteger; var lValue: tBytes; aValue: AnsiString; begin lValue := nil; aValue := TEncoding.ANSI.GetString(aValueBytes); if aValue <> '' then begin lValue := OSToBytes(aValue); MustGetDSIArith.UFromBase256(lValue,Result,aLocal); end else Result := nil; end; var Key: iIFPublicKey; RSAKey: TstRSAPublicKey; PEM: TStringStream; base64URL : TBase64URLURLEncoding; begin Key := MustGetDSIIF.NewPublicKey; RSAKey := TstRSAPublicKey.Create(nil); base64URL := TBase64URLURLEncoding.Create; try Key.N := Base64BytesToUMPInt(base64URL.DecodeStringToBytes(json.s['n']+'==')); Key.E := Base64BytesToUMPInt(base64URL.DecodeStringToBytes(json.s['e'])); PEM := TStringStream.Create; try // Save the RSA key in PEM format to the stream RSAKey.EnabledDesignTime := False; RSAKey.EnabledRunTime := False; RSAKey.SignEncoding := seEMSA3; RSAKey.EncryptEncoding := eeEME_PKCS1_v1_5; RSAKey.SetPublicKey(Key); RSAKey.PublicKeyFormat.SelectedOptionNames := 'PKCS1-RSAPublic-a'; RSAKey.SavePublicKeyToStream(PEM,iUnknown); Result := '-----BEGIN RSA PUBLIC KEY-----'#10+BytesToASCII(StrToMime64(PEM.DataString))+#10'-----END RSA PUBLIC KEY-----'; finally PEM.Free; end; finally base64URL.Free; RSAKey.Free; end; end; I'm not in love with my use of the TBase64URLURLEncoding component for conversion of JWK Base64urlUInt-encoded, and the related conversion from TArray of bytes to TBytes, so I'll look at cleaning that up. (Is there's something equivalent in PKC Tools 4.0?) In either case, I'm well on my way to getting this working. Next I'll tackle validation of the signature. |
Peter Royston 2022-12-20 19:12:02 Registered user |
Also, I will update to use StrToMIME64Str(), and yes, this is XE7 so I will insure 8-bit encoding for TStringStream.
Thanks again! Todd |