Browse Source

fcl-web: added TJWTSignerRS384, TJWTSignerRS512

mattias 3 years ago
parent
commit
f0c3d69068
2 changed files with 129 additions and 46 deletions
  1. 76 11
      packages/fcl-web/src/jwt/fpjwarsa.pp
  2. 53 35
      packages/fcl-web/tests/tcjwt.pp

+ 76 - 11
packages/fcl-web/src/jwt/fpjwarsa.pp

@@ -5,29 +5,95 @@ unit fpjwarsa;
 interface
 interface
 
 
 uses
 uses
-  Classes, SysUtils, basenenc, fpjwt, fprsa, fpsha256;
+  Classes, SysUtils, basenenc, fpjwt, fprsa, fpsha256, fpsha512;
 
 
 Type
 Type
 
 
-  { TJWTSignerRS256 }
+  { TJWTSignerRSA }
 
 
-  TJWTSignerRS256 = Class(TJWTSigner)
+  TJWTSignerRSA = Class(TJWTSigner)
   Public
   Public
     Class function AlgorithmName : String; override;
     Class function AlgorithmName : String; override;
+    function ComputeHash(const Value: TBytes): TBytes; virtual; abstract;
     Function CreateSignature(aJWT : TJWT; aKey : TJWTKey) : String; override;
     Function CreateSignature(aJWT : TJWT; aKey : TJWTKey) : String; override;
     Function Verify(const aJWT : String; aKey : TJWTKey) : Boolean; override; overload;
     Function Verify(const aJWT : String; aKey : TJWTKey) : Boolean; override; overload;
   end;
   end;
+  TJWTSignerRSAClass = class of TJWTSignerRSA;
+
+  { TJWTSignerRS256 }
+
+  TJWTSignerRS256 = class(TJWTSignerRSA)
+  public
+    class function AlgorithmName : String; override;
+    function ComputeHash(const Value: TBytes): TBytes; override;
+  end;
+
+  { TJWTSignerRS384 }
+
+  TJWTSignerRS384 = class(TJWTSignerRSA)
+  public
+    class function AlgorithmName : String; override;
+    function ComputeHash(const Value: TBytes): TBytes; override;
+  end;
+
+  { TJWTSignerRS512 }
+
+  TJWTSignerRS512 = class(TJWTSignerRSA)
+  public
+    class function AlgorithmName : String; override;
+    function ComputeHash(const Value: TBytes): TBytes; override;
+  end;
 
 
 implementation
 implementation
 
 
+{ TJWTSignerRS512 }
+
+class function TJWTSignerRS512.AlgorithmName: String;
+begin
+  Result:='RS512';
+end;
+
+function TJWTSignerRS512.ComputeHash(const Value: TBytes): TBytes;
+begin
+  Result:=nil;
+  TSHA512.DigestBytes(Value,Result);
+end;
+
+{ TJWTSignerRS384 }
+
+class function TJWTSignerRS384.AlgorithmName: String;
+begin
+  Result:='RS384';
+end;
+
+function TJWTSignerRS384.ComputeHash(const Value: TBytes): TBytes;
+begin
+  Result:=nil;
+  TSHA384.DigestBytes(Value,Result);
+end;
+
 { TJWTSignerRS256 }
 { TJWTSignerRS256 }
 
 
 class function TJWTSignerRS256.AlgorithmName: String;
 class function TJWTSignerRS256.AlgorithmName: String;
 begin
 begin
-  Result:='rs256';
+  Result:='RS256';
 end;
 end;
 
 
-function TJWTSignerRS256.CreateSignature(aJWT: TJWT; aKey: TJWTKey): String;
+function TJWTSignerRS256.ComputeHash(const Value: TBytes): TBytes;
+begin
+  Result:=nil;
+  TSHA256.DigestBytes(Value,Result);
+end;
+
+{ TJWTSignerRSA }
+
+class function TJWTSignerRSA.AlgorithmName: String;
+begin
+  raise Exception.Create('20220430014637 abstract class');
+  Result:='RSA';
+end;
+
+function TJWTSignerRSA.CreateSignature(aJWT: TJWT; aKey: TJWTKey): String;
 var
 var
   aSignInput, Hash, aSignature: TBytes;
   aSignInput, Hash, aSignature: TBytes;
   RSA: TRSA;
   RSA: TRSA;
@@ -37,9 +103,7 @@ begin
   aSignInput:=GetSignInput(aJWT);
   aSignInput:=GetSignInput(aJWT);
   if length(aSignInput)=0 then
   if length(aSignInput)=0 then
     raise Exception.Create('20220430010854: missing SignInput');
     raise Exception.Create('20220430010854: missing SignInput');
-
-  Hash:=nil;
-  TSHA256.DigestBytes(aSignInput,Hash);
+  Hash:=ComputeHash(aSignInput);
 
 
   RSACreate(RSA);
   RSACreate(RSA);
   try
   try
@@ -53,7 +117,7 @@ begin
   end;
   end;
 end;
 end;
 
 
-function TJWTSignerRS256.Verify(const aJWT: String; aKey: TJWTKey): Boolean;
+function TJWTSignerRSA.Verify(const aJWT: String; aKey: TJWTKey): Boolean;
 var
 var
   aHeader, theClaims, aSignature, aInput: String;
   aHeader, theClaims, aSignature, aInput: String;
   InputBytes, EncryptedHash, DecryptedHash, ActualHash: TBytes;
   InputBytes, EncryptedHash, DecryptedHash, ActualHash: TBytes;
@@ -84,8 +148,7 @@ begin
   aInput:=aHeader+'.'+theClaims;
   aInput:=aHeader+'.'+theClaims;
   SetLength(InputBytes{%H-},length(aInput));
   SetLength(InputBytes{%H-},length(aInput));
   Move(aInput[1],InputBytes[0],length(aInput));
   Move(aInput[1],InputBytes[0],length(aInput));
-  ActualHash:=nil;
-  TSHA256.DigestBytes(InputBytes,ActualHash);
+  ActualHash:=ComputeHash(InputBytes);
 
 
   // check decrypted hash and actual hash fit
   // check decrypted hash and actual hash fit
   Result:=(length(DecryptedHash)=length(ActualHash))
   Result:=(length(DecryptedHash)=length(ActualHash))
@@ -94,5 +157,7 @@ end;
 
 
 initialization
 initialization
   TJWTSignerRS256.Register;
   TJWTSignerRS256.Register;
+  TJWTSignerRS384.Register;
+  TJWTSignerRS512.Register;
 end.
 end.
 
 

+ 53 - 35
packages/fcl-web/tests/tcjwt.pp

@@ -38,6 +38,7 @@ type
     function CreateUnsignedInput(JOSEAlg, ClaimsIssuer: string): string;
     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;
   published
   published
     procedure TestSignNone;
     procedure TestSignNone;
     procedure TestVerifyNone;
     procedure TestVerifyNone;
@@ -50,6 +51,8 @@ type
     procedure TestVerifyES256;
     procedure TestVerifyES256;
     procedure TestVerifyES256Pem;
     procedure TestVerifyES256Pem;
     procedure TestVerifyRS256Pem;
     procedure TestVerifyRS256Pem;
+    procedure TestVerifyRS384Pem;
+    procedure TestVerifyRS512Pem;
   end;
   end;
 
 
 implementation
 implementation
@@ -280,6 +283,52 @@ begin
 end;
 end;
 
 
 procedure TTestJWT.TestVerifyRS256Pem;
 procedure TTestJWT.TestVerifyRS256Pem;
+begin
+  TestVerifyRSAPem(TJWTSignerRS256);
+end;
+
+procedure TTestJWT.TestVerifyRS384Pem;
+begin
+  TestVerifyRSAPem(TJWTSignerRS384);
+end;
+
+procedure TTestJWT.TestVerifyRS512Pem;
+begin
+  TestVerifyRSAPem(TJWTSignerRS512);
+end;
+
+procedure TTestJWT.SetUp;
+begin
+  Inherited;
+  FKey:=TJWTKey.Create('mysecretkey');
+  FJWT:=TMyJWT.Create;
+  FJWT.JOSE.alg:='none';
+  FJWT.JOSE.typ:='JWT';
+  FJWT.Claims.sub:='1234567890';
+  FJWT.Claims.iat:=1516239022;
+  (FJWT.Claims as TMyClaims).Name:='John Doe';
+end;
+
+procedure TTestJWT.TearDown;
+begin
+  FreeAndNil(FJWT);
+  FreeAndNil(FVerifyResult);
+  Inherited;
+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);
 const
 const
   // generated with
   // generated with
   //   openssl genrsa -out private.pem 2048
   //   openssl genrsa -out private.pem 2048
@@ -313,10 +362,10 @@ const
     '-----END RSA PRIVATE KEY-----'#10;
     '-----END RSA PRIVATE KEY-----'#10;
 var
 var
   aInput: String;
   aInput: String;
-  Signer: TJWTSignerRS256;
+  Signer: TJWTSignerRSA;
 begin
 begin
   // header
   // header
-  jwt.JOSE.alg:='RS256';
+  jwt.JOSE.alg:=SignerClass.AlgorithmName;
 
 
   // claims
   // claims
   jwt.Claims.exp:=DateTimeToUnix(Now+10);
   jwt.Claims.exp:=DateTimeToUnix(Now+10);
@@ -325,7 +374,7 @@ 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);
 
 
-  Signer:=TJWTSignerRS256.Create;
+  Signer:=TJWTSignerRSA(SignerClass.Create);
   try
   try
     aInput:=Signer.AppendSignature(JWT,Key);
     aInput:=Signer.AppendSignature(JWT,Key);
   finally
   finally
@@ -337,44 +386,13 @@ begin
   AssertEquals('Correct class',TMyJWT,FVerifyResult.ClassType);
   AssertEquals('Correct class',TMyJWT,FVerifyResult.ClassType);
   AssertNotNull('Have result.claims',FVerifyResult.Claims);
   AssertNotNull('Have result.claims',FVerifyResult.Claims);
   AssertEquals('Correct claims class',TMyClaims,FVerifyResult.Claims.ClassType);
   AssertEquals('Correct claims class',TMyClaims,FVerifyResult.Claims.ClassType);
-  AssertEquals('Have correct algorithm','RS256',FVerifyResult.JOSE.Alg);
+  AssertEquals('Have correct algorithm',SignerClass.AlgorithmName,FVerifyResult.JOSE.Alg);
   AssertEquals('Have correct typ','JWT',FVerifyResult.JOSE.typ);
   AssertEquals('Have correct typ','JWT',FVerifyResult.JOSE.typ);
   AssertEquals('Have correct sub','1234567890',FVerifyResult.Claims.sub);
   AssertEquals('Have correct sub','1234567890',FVerifyResult.Claims.sub);
   AssertEquals('Have correct name','John Doe',(TMyJWT(FVerifyResult).Claims as TMyClaims).Name);
   AssertEquals('Have correct name','John Doe',(TMyJWT(FVerifyResult).Claims as TMyClaims).Name);
   AssertEquals('Have correct admin',False,(TMyJWT(FVerifyResult).Claims as TMyClaims).Admin);
   AssertEquals('Have correct admin',False,(TMyJWT(FVerifyResult).Claims as TMyClaims).Admin);
 end;
 end;
 
 
-procedure TTestJWT.SetUp;
-begin
-  Inherited;
-  FKey:=TJWTKey.Create('mysecretkey');
-  FJWT:=TMyJWT.Create;
-  FJWT.JOSE.alg:='none';
-  FJWT.JOSE.typ:='JWT';
-  FJWT.Claims.sub:='1234567890';
-  FJWT.Claims.iat:=1516239022;
-  (FJWT.Claims as TMyClaims).Name:='John Doe';
-end;
-
-procedure TTestJWT.TearDown;
-begin
-  FreeAndNil(FJWT);
-  FreeAndNil(FVerifyResult);
-  Inherited;
-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;
-
 initialization
 initialization
   RegisterTest(TTestJWT);
   RegisterTest(TTestJWT);
 end.
 end.