Parcourir la source

ES256 signing algorithm

Michaël Van Canneyt il y a 3 ans
Parent
commit
5afaeaa3ac

+ 2 - 0
packages/fcl-web/fpmake.pp

@@ -334,6 +334,8 @@ begin
     T.Dependencies.AddUnit('fpjwt');
     T.Dependencies.AddUnit('fpjwt');
     T:=P.Targets.AddUnit('fpjwasha384.pp');
     T:=P.Targets.AddUnit('fpjwasha384.pp');
     T.Dependencies.AddUnit('fpjwt');
     T.Dependencies.AddUnit('fpjwt');
+    T:=P.Targets.AddUnit('fpjwaes256.pp');
+    T.Dependencies.AddUnit('fpjwt');
     T:=P.Targets.AddUnit('fphttpwebclient.pp');
     T:=P.Targets.AddUnit('fphttpwebclient.pp');
     T.Dependencies.AddUnit('fpwebclient');
     T.Dependencies.AddUnit('fpwebclient');
     T:=P.Targets.AddUnit('restbase.pp');
     T:=P.Targets.AddUnit('restbase.pp');

+ 95 - 0
packages/fcl-web/src/jwt/fpjwaes256.pp

@@ -0,0 +1,95 @@
+unit fpjwaes256;
+
+{$mode ObjFPC}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, fpjwt, Ecc;
+
+Type
+  { TJWTSignerES256 }
+
+  TJWTSignerES256 = Class(TJWTSigner)
+  Public
+    Class function AlgorithmName : String; override;
+    Function CreateSignature(aJWT : TJWT; aKey : TJWTKey) : String; override;
+    Function Verify(const aJWT : String; aKey : TJWTKey) : Boolean; override; overload;
+    Class Function Verify(const aJWT : String; aPrivateKey : TECCPrivateKey) : Boolean; overload;
+    Class Function Verify(const aJWT : String; aPublicKey : TECCPublicKey) : Boolean; overload;
+  end;
+
+
+implementation
+
+uses hashutils, basenenc, ecdsa;
+
+{ TJWTSignerES256 }
+
+class function TJWTSignerES256.AlgorithmName: String;
+begin
+  Result:='ES256';
+end;
+
+function TJWTSignerES256.CreateSignature(aJWT: TJWT; aKey: TJWTKey): String;
+Var
+  B : TBytes;
+  aPrivateKey : TEccPrivateKey;
+  aSignature : TEccSignature;
+
+begin
+  Result:='';
+  aPrivateKey:=Default(TECCPrivateKey);
+  Move(aKey.AsPointer,aPrivateKey,Sizeof(aPrivateKey));
+  B:=GetSignInput(aJWT);
+  if TECDSA.SignSHA256(B,aPrivateKey,aSignature) then
+    Result:=Base64URL.Encode(@aSignature[0],Length(aSignature),False);
+end;
+
+function TJWTSignerES256.Verify(const aJWT: String; aKey: TJWTKey): Boolean;
+
+var
+  aPrivateKey : TECCPrivateKey;
+
+begin
+  aPrivateKey:=Default(TECCPrivateKey);
+  Move(aKey.AsPointer^,aPrivateKey,Sizeof(aPrivateKey));
+  Result:=Verify(aJWT,aPrivateKey);
+end;
+
+Class function TJWTSignerES256.Verify(const aJWT: String; aPrivateKey: TECCPrivateKey): Boolean;
+
+Var
+  J,C,S : AnsiString;
+  aSignature : TEccSignature;
+  B : TBytes;
+
+begin
+  Result:=GetParts(aJWT,J,C,S);
+  if Not Result then
+    exit;
+  B:=TEncoding.UTF8.GetAnsiBytes(J+'.'+C);
+  BytesToVar(Base64url.Decode(S),aSignature,Sizeof(aSignature));
+  Result:=TECDSA.verifySHA256(B,aPrivateKey,aSignature);
+end;
+
+class function TJWTSignerES256.Verify(const aJWT: String; aPublicKey: TECCPublicKey): Boolean;
+
+Var
+  J,C,S : AnsiString;
+  aSignature : TEccSignature;
+  B : TBytes;
+
+begin
+  Result:=GetParts(aJWT,J,C,S);
+  if Not Result then
+    exit;
+  B:=TEncoding.UTF8.GetAnsiBytes(J+'.'+C);
+  Base64url.Decode(S,@aSignature);
+  Result:=TECDSA.verifySHA256(B,aPublicKey,aSignature);
+end;
+
+initialization
+  TJWTSignerES256.Register;
+end.
+

+ 13 - 0
packages/fcl-web/src/jwt/fpjwt.pp

@@ -35,6 +35,7 @@ Type
     procedure SetLength(AValue: Integer);
     procedure SetLength(AValue: Integer);
   public
   public
     Bytes : TBytes;
     Bytes : TBytes;
+    Class Function Create(aData : PByte; aSize : Word) : TJWTKey; static;
     Class Function Create(aBytes : TBytes) : TJWTKey; static;
     Class Function Create(aBytes : TBytes) : TJWTKey; static;
     Class Function Create(aString : UTF8String) : TJWTKey; static;
     Class Function Create(aString : UTF8String) : TJWTKey; static;
     Class Function Empty : TJWTKey; static;
     Class Function Empty : TJWTKey; static;
@@ -244,6 +245,18 @@ begin
   System.SetLength(Bytes,aValue)
   System.SetLength(Bytes,aValue)
 end;
 end;
 
 
+class function TJWTKey.Create(aData: PByte; aSize : Word): TJWTKey;
+
+Var
+  B : TBytes;
+
+begin
+  B:=[];
+  System.SetLength(B,aSize);
+  Move(aData^,B[0],aSize);
+  Result:=Create(B);
+end;
+
 class function TJWTKey.Create(aBytes: TBytes): TJWTKey;
 class function TJWTKey.Create(aBytes: TBytes): TJWTKey;
 begin
 begin
   Result.AsBytes:=aBytes;
   Result.AsBytes:=aBytes;

+ 79 - 2
packages/fcl-web/tests/tcjwt.pp

@@ -47,11 +47,14 @@ type
     procedure TestVerifySHA512;
     procedure TestVerifySHA512;
     procedure TestSignSHA384;
     procedure TestSignSHA384;
     procedure TestVerifySHA384;
     procedure TestVerifySHA384;
+    procedure TestVerifyES256;
+    procedure TestVerifyES256Pem;
   end;
   end;
 
 
 implementation
 implementation
 
 
-uses basenenc, sha256, fpjwasha256, sha512, fpjwasha512, fpjwasha384;
+uses
+  basenenc, sha256, fpjwasha256, sha512, fpjwasha512, fpjwasha384, fpjwaes256, ecc, pem;
 
 
 { TMyJWT }
 { TMyJWT }
 
 
@@ -178,7 +181,7 @@ begin
   if not TSHA384.HMAC(FKey.AsPointer,FKey.Length,PByte(B),Length(B),aDigest) then
   if not TSHA384.HMAC(FKey.AsPointer,FKey.Length,PByte(B),Length(B),aDigest) then
     Fail('Could not HMAC');
     Fail('Could not HMAC');
   Sign:=Base64URL.Encode(@aDigest[0],Length(aDigest),False);
   Sign:=Base64URL.Encode(@aDigest[0],Length(aDigest),False);
-  Writeln('Signed: ',P1+'.'+P2+'.'+Sign);
+  // Writeln('Signed: ',P1+'.'+P2+'.'+Sign);
   AssertEquals('Signed with SHA384',P1+'.'+P2+'.'+Sign,FJWT.Sign(FKey));
   AssertEquals('Signed with SHA384',P1+'.'+P2+'.'+Sign,FJWT.Sign(FKey));
 end;
 end;
 
 
@@ -204,7 +207,81 @@ begin
   AssertEquals('Have correct admin',true,(TMyJWT(FVerifyResult).Claims as TMyClaims).Admin);
   AssertEquals('Have correct admin',true,(TMyJWT(FVerifyResult).Claims as TMyClaims).Admin);
 end;
 end;
 
 
+procedure TTestJWT.TestVerifyES256;
+Const
+  // from JWT.IO
+  aJWT =
+     'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.'+
+     'eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.'+
+     'tyh-VfuzIxCyGYDlkBA7DfyjrqmSHu6pQ2hoZuFqUSLPNY2N0mpHb3nk5K17HWP_3cYHBw7AhHale5wky6-sVA';
+
+Var
+  aPrivateKey2: TEccPrivateKey = ($7a,$f6,$73,$2f,$58,$1d,$00,$5a,$fc,$f2,$16,$f6,$38,$5f,$f6,
+                $37,$10,$29,$24,$2c,$c6,$08,$40,$dd,$7d,$2a,$7a,$55,$03,$b7,
+                $d2,$1c);
+
+
+begin
+  FKey:=TJWTKey.Create(@aPrivateKey2,SizeOf(TEccPrivateKey));
+  FVerifyResult:=TMyJWT.ValidateJWT(aJWT,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','ES256',FVerifyResult.JOSE.Alg);
+  AssertEquals('Have correct typ','JWT',FVerifyResult.JOSE.typ);
+  AssertEquals('Have correct sub','1234567890',FVerifyResult.Claims.sub);
+  AssertEquals('Have correct name','John Doe',(TMyJWT(FVerifyResult).Claims as TMyClaims).Name);
+  AssertEquals('Have correct admin',true,(TMyJWT(FVerifyResult).Claims as TMyClaims).Admin);
+
+end;
+
+procedure TTestJWT.TestVerifyES256Pem;
+
+Const
+  aInput =
+    'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.' +
+    'eyJpYXQiOjE1MTYyMzkwMjIsImV4cCI6MTUxNjI0OTAyMiwiaXNzIjoiRGVscGhpIEpPU0UgYW5kIEpXVCBMaWJyYXJ5In0.'+
+    '4QDMKAvHwb6pA5fN0oQjlzuKmPIlNpmIQ8vPH7zy4fjZdtcPVJMtfiVhztwQldQL9A5yzBKI8q2puVygm-2Adw';
+
+// Private key in PEM format
+Const APrivateKeyPem =
+    '-----BEGIN EC PRIVATE KEY-----'+ #10+
+    'MHcCAQEEIFzS3/5bCnrlpa4902/zkYzURF6E2D8pazgnJu4smhpQoAoGCCqGSM49'+ #10+
+    'AwEHoUQDQgAEqTjyg2z65i+zbyUZW8BQ+K87DNsICRaEH7Fy7Rm3MseXy9ItSCQU'+ #10+
+    'VeJbtO6kYUA00mx7bKoC1sx5sbtFExnYPQ=='+ #10+
+    '-----END EC PRIVATE KEY-----';
+
+
+Var
+  S : TStringStream;
+  aPrivateKey : TEccPrivateKey;
+  aPublicKey : TEccPublicKey;
+  X,Y : AnsiString;
+
+begin
+  S:=TStringStream.Create(aPrivateKeyPem);
+  try
+    PemLoadECDSA(S,aPrivateKey,aPublicKey,X,Y);
+  finally
+    S.Free;
+  end;
+  FKey:=TJWTKey.Create(@aPrivateKey,SizeOf(TEccPrivateKey));
+  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','ES256',FVerifyResult.JOSE.Alg);
+  AssertEquals('Have correct typ','JWT',FVerifyResult.JOSE.typ);
+  AssertEquals('Have correct sub','',FVerifyResult.Claims.sub);
+  AssertEquals('Have correct name','',(TMyJWT(FVerifyResult).Claims as TMyClaims).Name);
+  AssertEquals('Have correct admin',False,(TMyJWT(FVerifyResult).Claims as TMyClaims).Admin);
+end;
+
 procedure TTestJWT.SetUp;
 procedure TTestJWT.SetUp;
+
+
 begin
 begin
   Inherited;
   Inherited;
   FKey:=TJWTKey.Create('mysecretkey');
   FKey:=TJWTKey.Create('mysecretkey');

+ 7 - 3
packages/fcl-web/tests/testfpweb.lpi

@@ -19,13 +19,13 @@
     </PublishOptions>
     </PublishOptions>
     <RunParams>
     <RunParams>
       <local>
       <local>
-        <CommandLineParams Value="--suite=TTestJWT"/>
+        <CommandLineParams Value="--suite=TTestJWT.TestVerifyES256"/>
       </local>
       </local>
       <FormatVersion Value="2"/>
       <FormatVersion Value="2"/>
       <Modes Count="1">
       <Modes Count="1">
         <Mode0 Name="default">
         <Mode0 Name="default">
           <local>
           <local>
-            <CommandLineParams Value="--suite=TTestJWT"/>
+            <CommandLineParams Value="--suite=TTestJWT.TestVerifyES256"/>
           </local>
           </local>
         </Mode0>
         </Mode0>
       </Modes>
       </Modes>
@@ -35,7 +35,7 @@
         <PackageName Value="FCL"/>
         <PackageName Value="FCL"/>
       </Item1>
       </Item1>
     </RequiredPackages>
     </RequiredPackages>
-    <Units Count="7">
+    <Units Count="8">
       <Unit0>
       <Unit0>
         <Filename Value="testfpweb.lpr"/>
         <Filename Value="testfpweb.lpr"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
@@ -64,6 +64,10 @@
         <Filename Value="../src/jwt/fpjwasha384.pp"/>
         <Filename Value="../src/jwt/fpjwasha384.pp"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
       </Unit6>
       </Unit6>
+      <Unit7>
+        <Filename Value="../src/jwt/fpjwaes256.pp"/>
+        <IsPartOfProject Value="True"/>
+      </Unit7>
     </Units>
     </Units>
   </ProjectOptions>
   </ProjectOptions>
   <CompilerOptions>
   <CompilerOptions>

+ 2 - 1
packages/fcl-web/tests/testfpweb.lpr

@@ -3,7 +3,8 @@ program testfpweb;
 {$mode objfpc}{$H+}
 {$mode objfpc}{$H+}
 
 
 uses
 uses
-  Classes, consoletestrunner, tchttproute, tcjwt, jsonparser, fpjwasha256, fpjwasha512, fpjwasha384;
+  Classes, consoletestrunner, tchttproute, tcjwt, jsonparser,
+  fpjwasha256, fpjwasha512, fpjwasha384, fpjwaes256;
 
 
 type
 type