Browse Source

fcl-web: test writing rsa keys as DER, test with RFC 7515 values

mattias 3 years ago
parent
commit
a81e527e1d
2 changed files with 125 additions and 16 deletions
  1. 10 2
      packages/fcl-hash/src/fphashutils.pp
  2. 115 14
      packages/fcl-web/tests/tcjwt.pp

+ 10 - 2
packages/fcl-hash/src/fphashutils.pp

@@ -43,10 +43,11 @@ Procedure BytesToHexStr(out aHexStr : AnsiString; aBytes : TBytes); overload;
 Function BytesToHexStr(aBytes : TBytes) : AnsiString; overload;
 Function BytesToHexStr(aBytes : TBytes) : AnsiString; overload;
 Procedure BytesToHexStrAppend(aBytes : TBytes;var aHexStr : AnsiString);
 Procedure BytesToHexStrAppend(aBytes : TBytes;var aHexStr : AnsiString);
 
 
-procedure BytesEncodeBase64(Source: Tbytes; out Dest: AnsiString; const IsURL, MultiLines, Padding: Boolean);
+Procedure BytesEncodeBase64(Source: Tbytes; out Dest: AnsiString; const IsURL, MultiLines, Padding: Boolean);
 Function BytesEncodeBase64(Source: Tbytes; const IsURL, MultiLines, Padding: Boolean) : AnsiString;
 Function BytesEncodeBase64(Source: Tbytes; const IsURL, MultiLines, Padding: Boolean) : AnsiString;
+Function BytesToStr(const aBytes: TBytes): string; overload;
 
 
-function CryptoGetRandomBytes(Buffer: PByte; const Count: Integer; ZeroBytesAllowed: boolean = true): Boolean;
+Function CryptoGetRandomBytes(Buffer: PByte; const Count: Integer; ZeroBytesAllowed: boolean = true): Boolean;
 Function ExtractBetween(const ASource,aStart,aEnd : String) : String;
 Function ExtractBetween(const ASource,aStart,aEnd : String) : String;
 
 
 Type
 Type
@@ -340,6 +341,13 @@ begin
   BytesEncodeBase64(Source,Result,IsURL, MultiLines, Padding);
   BytesEncodeBase64(Source,Result,IsURL, MultiLines, Padding);
 end;
 end;
 
 
+function BytesToStr(const aBytes: TBytes): string;
+begin
+  SetLength(Result,length(aBytes));
+  if aBytes=nil then exit;
+  Move(aBytes[0],Result[1],length(aBytes));
+end;
+
 type
 type
   TUInt32 = Cardinal;
   TUInt32 = Cardinal;
   PUInt32Array = ^TUInt32;
   PUInt32Array = ^TUInt32;

+ 115 - 14
packages/fcl-web/tests/tcjwt.pp

@@ -5,7 +5,8 @@ unit tcjwt;
 interface
 interface
 
 
 uses
 uses
-  Classes, SysUtils, fpcunit, testregistry, DateUtils, fpjwt, fpjwarsa;
+  Classes, SysUtils, fpcunit, testregistry, DateUtils, fprsa, fphashutils,
+  fpjwt, fpjwarsa;
 
 
 type
 type
 
 
@@ -35,7 +36,6 @@ type
   protected
   protected
     procedure SetUp; override;
     procedure SetUp; override;
     procedure TearDown; override;
     procedure TearDown; override;
-    function CreateUnsignedInput(JOSEAlg, ClaimsIssuer: string): string;
     Property JWT : TJWT Read FJWT;
     Property JWT : TJWT Read FJWT;
     Property Key : TJWTKey Read FKey;
     Property Key : TJWTKey Read FKey;
     procedure TestVerifyRSAPem(SignerClass: TJWTSignerRSAClass); virtual;
     procedure TestVerifyRSAPem(SignerClass: TJWTSignerRSAClass); virtual;
@@ -53,6 +53,7 @@ type
     procedure TestVerifyRS256Pem;
     procedure TestVerifyRS256Pem;
     procedure TestVerifyRS384Pem;
     procedure TestVerifyRS384Pem;
     procedure TestVerifyRS512Pem;
     procedure TestVerifyRS512Pem;
+    procedure TestVerifyRS256_rfc7515;
   end;
   end;
 
 
 implementation
 implementation
@@ -296,6 +297,105 @@ begin
   TestVerifyRSAPem(TJWTSignerRS512);
   TestVerifyRSAPem(TJWTSignerRS512);
 end;
 end;
 
 
+procedure TTestJWT.TestVerifyRS256_rfc7515;
+const
+  // values from RFC 7515
+  HeaderJSON = '{"alg":"RS256"}';
+  HeaderExpected = 'eyJhbGciOiJSUzI1NiJ9';
+
+  PayloadJSON = '{"iss":"joe",'#13#10+
+                ' "exp":1300819380,'#13#10+
+                ' "http://example.com/is_root":true}';
+  PayloadExpected = 'eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ';
+
+  SignInputExpected: TBytes =
+    (101, 121, 74, 104, 98, 71, 99, 105, 79, 105, 74, 83, 85, 122, 73,
+     49, 78, 105, 74, 57, 46, 101, 121, 74, 112, 99, 51, 77, 105, 79, 105,
+     74, 113, 98, 50, 85, 105, 76, 65, 48, 75, 73, 67, 74, 108, 101, 72,
+     65, 105, 79, 106, 69, 122, 77, 68, 65, 52, 77, 84, 107, 122, 79, 68,
+     65, 115, 68, 81, 111, 103, 73, 109, 104, 48, 100, 72, 65, 54, 76,
+     121, 57, 108, 101, 71, 70, 116, 99, 71, 120, 108, 76, 109, 78, 118,
+     98, 83, 57, 112, 99, 49, 57, 121, 98, 50, 57, 48, 73, 106, 112, 48,
+     99, 110, 86, 108, 102, 81);
+
+  RSA_n = 'ofgWCuLjybRlzo0tZWJjNiuSfb4p4fAkd_wWJcyQoTbji9k0l8W26mPddx'+
+          'HmfHQp-Vaw-4qPCJrcS2mJPMEzP1Pt0Bm4d4QlL-yRT-SFd2lZS-pCgNMs'+
+          'D1W_YpRPEwOWvG6b32690r2jZ47soMZo9wGzjb_7OMg0LOL-bSf63kpaSH'+
+          'SXndS5z5rexMdbBYUsLA9e-KXBdQOS-UTo7WTBEMa2R2CapHg665xsmtdV'+
+          'MTBQY4uDZlxvb3qCo5ZwKh9kG4LT6_I5IhlJH7aGhyxXFvUK-DWNmoudF8'+
+          'NAco9_h9iaGNj8q2ethFkMLs91kzk2PAcDTW9gb54h4FRWyuXpoQ';
+  RSA_e = 'AQAB';
+  RSA_d = 'Eq5xpGnNCivDflJsRQBXHx1hdR1k6Ulwe2JZD50LpXyWPEAeP88vLNO97I'+
+          'jlA7_GQ5sLKMgvfTeXZx9SE-7YwVol2NXOoAJe46sui395IW_GO-pWJ1O0'+
+          'BkTGoVEn2bKVRUCgu-GjBVaYLU6f3l9kJfFNS3E0QbVdxzubSu3Mkqzjkn'+
+          '439X0M_V51gfpRLI9JYanrC4D4qAdGcopV_0ZHHzQlBjudU2QvXt4ehNYT'+
+          'CBr6XCLQUShb1juUO1ZdiYoFaFQT5Tw8bGUl_x_jTj3ccPDVZFD9pIuhLh'+
+          'BOneufuBiB4cS98l2SR_RQyGWSeWjnczT0QU91p1DhOVRuOopznQ';
+  RSA_p = '4BzEEOtIpmVdVEZNCqS7baC4crd0pqnRH_5IB3jw3bcxGn6QLvnEtfdUdi'+
+          'YrqBdss1l58BQ3KhooKeQTa9AB0Hw_Py5PJdTJNPY8cQn7ouZ2KKDcmnPG'+
+          'BY5t7yLc1QlQ5xHdwW1VhvKn-nXqhJTBgIPgtldC-KDV5z-y2XDwGUc';
+  RSA_q = 'uQPEfgmVtjL0Uyyx88GZFF1fOunH3-7cepKmtH4pxhtCoHqpWmT8YAmZxa'+
+          'ewHgHAjLYsp1ZSe7zFYHj7C6ul7TjeLQeZD_YwD66t62wDmpe_HlB-TnBA'+
+          '-njbglfIsRLtXlnDzQkv5dTltRJ11BKBBypeeF6689rjcJIDEz9RWdc';
+  RSA_dp = 'BwKfV3Akq5_MFZDFZCnW-wzl-CCo83WoZvnLQwCTeDv8uzluRSnm71I3Q'+
+          'CLdhrqE2e9YkxvuxdBfpT_PI7Yz-FOKnu1R6HsJeDCjn12Sk3vmAktV2zb'+
+          '34MCdy7cpdTh_YVr7tss2u6vneTwrA86rZtu5Mbr1C1XsmvkxHQAdYo0';
+  RSA_dq = 'h_96-mK1R_7glhsum81dZxjTnYynPbZpHziZjeeHcXYsXaaMwkOlODsWa'+
+          '7I9xXDoRwbKgB719rrmI2oKr6N3Do9U0ajaHF-NKJnwgjMd2w9cjz3_-ky'+
+          'NlxAr2v4IKhGNpmM5iIgOS1VZnOZ68m6_pbLBSp3nssTdlqvd0tIiTHU';
+  RSA_qi = 'IYd7DHOhrWvxkwPQsRM2tOgrjbcrfvtQJipd-DlcxyVuuM9sQLdgjVk2o'+
+          'y26F0EmpScGLq2MowX7fhd_QJQ3ydy5cY7YIBi87w93IKLEdfnbJtoOPLU'+
+          'W0ITrJReOgo1cq9SbsxYawBgfp_gh6A5603k2-ZQwVK0JKSHuLFkuQ3U';
+
+  Signature ='cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7'+
+             'AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjbKBYNX4'+
+             'BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K'+
+             '0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqv'+
+             'hJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrB'+
+             'p0igcN_IoypGlUPQGe77Rw';
+
+var
+  HeaderEncoded, PayloadEncoded, SignInput, aInput: String;
+  X509RSAPrivateKey: TX509RSAPrivateKey;
+  X509RSAPublicKey: TX509RSAPublicKey;
+  RSA: TRSA;
+begin
+  HeaderEncoded:=Base64URL.Encode(HeaderJSON,false);
+  AssertEquals('Header',HeaderExpected,HeaderEncoded);
+
+  PayloadEncoded:=Base64URL.Encode(PayloadJSON,false);
+  AssertEquals('Payload',PayloadExpected,PayloadEncoded);
+
+  SignInput:=HeaderEncoded+'.'+PayloadEncoded;
+  if (length(SignInput)<>length(SignInputExpected))
+      or not CompareMem(@SignInput[1],@SignInputExpected[0],length(SignInput)) then
+    Fail('SignInput');
+
+  X509RSAPrivateKey.InitWithBase64UrlEncoded(RSA_n,RSA_e,RSA_d,RSA_p,RSA_q,RSA_dp,RSA_dq,RSA_qi);
+  X509RSAPublicKey.InitWithBase64UrlEncoded(RSA_n,RSA_e);
+
+  RSACreate(RSA);
+  try
+    RSAInitFromPublicKey(RSA,X509RSAPublicKey);
+    FKey.AsBytes:=X509RSAPublicKey.AsDER;
+
+    aInput:=SignInput+'.'+Signature;
+    // verify
+    FVerifyResult:=TMyJWT.ValidateJWT(aInput,FKey);
+    AssertNotNull('Have result',FVerifyResult);
+    AssertEquals('Correct class',TMyJWT,FVerifyResult.ClassType);
+    AssertNotNull('Have result.claims',FVerifyResult.Claims);
+    AssertEquals('Correct claims class',TMyClaims,FVerifyResult.Claims.ClassType);
+    AssertEquals('Have correct algorithm','RS256',FVerifyResult.JOSE.Alg);
+    AssertEquals('Have correct typ','',FVerifyResult.JOSE.typ);
+    AssertEquals('Have correct iss','joe',FVerifyResult.Claims.iss);
+    AssertEquals('Have correct exp',1300819380,FVerifyResult.Claims.exp);
+
+  finally
+    RSAFree(RSA);
+  end;
+end;
+
 procedure TTestJWT.SetUp;
 procedure TTestJWT.SetUp;
 begin
 begin
   Inherited;
   Inherited;
@@ -315,18 +415,6 @@ begin
   Inherited;
   Inherited;
 end;
 end;
 
 
-function TTestJWT.CreateUnsignedInput(JOSEAlg, ClaimsIssuer: string): string;
-var
-  IssuedAt, Expire: Int64;
-  Header, Claims: String;
-begin
-  IssuedAt:=DateTimeToUnix(Now-1);
-  Expire:=IssuedAt+1000000;
-  Header:='{"typ":"JWT","alg":"'+JOSEAlg+'"}';
-  Claims:='{"iat":'+IntToStr(IssuedAt)+',"exp":'+IntToStr(Expire)+',"iss":"'+ClaimsIssuer+'"}';
-  Result:=Base64URL.Encode(Header,false)+'.'+Base64URL.Encode(Claims,false);
-end;
-
 procedure TTestJWT.TestVerifyRSAPem(SignerClass: TJWTSignerRSAClass);
 procedure TTestJWT.TestVerifyRSAPem(SignerClass: TJWTSignerRSAClass);
 const
 const
   // generated with
   // generated with
@@ -372,6 +460,9 @@ const
 var
 var
   aInput: String;
   aInput: String;
   Signer: TJWTSignerRSA;
   Signer: TJWTSignerRSA;
+  NewDER: TBytes;
+  RSAPublic: TX509RSAPublicKey;
+  RSAPrivate: TX509RSAPrivateKey;
 begin
 begin
   // header
   // header
   jwt.JOSE.alg:=SignerClass.AlgorithmName;
   jwt.JOSE.alg:=SignerClass.AlgorithmName;
@@ -382,6 +473,11 @@ begin
 
 
   // load private key from pem
   // load private key from pem
   FKey.AsBytes:=PemToDER(APrivateKeyPem,_BEGIN_RSA_PRIVATE_KEY,_END_RSA_PRIVATE_KEY);
   FKey.AsBytes:=PemToDER(APrivateKeyPem,_BEGIN_RSA_PRIVATE_KEY,_END_RSA_PRIVATE_KEY);
+  X509RsaPrivateKeyInitFromDER(RSAPrivate,FKey.AsBytes);
+  NewDER:=RSAPrivate.AsDER;
+  if (length(FKey.AsBytes)<>length(NewDER)) or
+      not CompareMem(@FKey.AsBytes[0],@NewDER[0],length(NewDER)) then
+    Fail('TX509RSAPrivateKey.AsDER');
 
 
   // sign
   // sign
   Signer:=TJWTSignerRSA(SignerClass.Create);
   Signer:=TJWTSignerRSA(SignerClass.Create);
@@ -393,6 +489,11 @@ begin
 
 
   // load public key from pem
   // load public key from pem
   FKey.AsBytes:=PemToDER(APublicKeyPem,_BEGIN_PUBLIC_KEY,_END_PUBLIC_KEY);
   FKey.AsBytes:=PemToDER(APublicKeyPem,_BEGIN_PUBLIC_KEY,_END_PUBLIC_KEY);
+  X509RsaPublicKeyInitFromDER(RSAPublic,FKey.AsBytes);
+  NewDER:=RSAPublic.AsDER;
+  if (length(FKey.AsBytes)<>length(NewDER)) or
+      not CompareMem(@FKey.AsBytes[0],@NewDER[0],length(NewDER)) then
+    Fail('TX509RSAPublicKey.AsDER');
 
 
   // verify
   // verify
   FVerifyResult:=TMyJWT.ValidateJWT(aInput,FKey);
   FVerifyResult:=TMyJWT.ValidateJWT(aInput,FKey);