Home 
username password  
Welcome, Guest.
Your IP: 3.235.172.123
2024-04-13 10:13:19 
 Public Support
 Validate openid token using public keys in JWK
Bottom
 
Total posts: 5
 Author 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
Top

:: Written with and Powered by the RealThinClient SDK and StreamSec Tools 4.0::
Copyright (c) Danijel Tkalcec, StreamSec HB