ES256 signing algorithm

This commit is contained in:
Michaël Van Canneyt 2021-11-08 20:52:14 +01:00
parent 6ae8840ece
commit 5afaeaa3ac
6 changed files with 198 additions and 6 deletions

View File

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

View File

@ -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.

View File

@ -35,6 +35,7 @@ Type
procedure SetLength(AValue: Integer);
public
Bytes : TBytes;
Class Function Create(aData : PByte; aSize : Word) : TJWTKey; static;
Class Function Create(aBytes : TBytes) : TJWTKey; static;
Class Function Create(aString : UTF8String) : TJWTKey; static;
Class Function Empty : TJWTKey; static;
@ -244,6 +245,18 @@ begin
System.SetLength(Bytes,aValue)
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;
begin
Result.AsBytes:=aBytes;

View File

@ -47,11 +47,14 @@ type
procedure TestVerifySHA512;
procedure TestSignSHA384;
procedure TestVerifySHA384;
procedure TestVerifyES256;
procedure TestVerifyES256Pem;
end;
implementation
uses basenenc, sha256, fpjwasha256, sha512, fpjwasha512, fpjwasha384;
uses
basenenc, sha256, fpjwasha256, sha512, fpjwasha512, fpjwasha384, fpjwaes256, ecc, pem;
{ TMyJWT }
@ -178,7 +181,7 @@ begin
if not TSHA384.HMAC(FKey.AsPointer,FKey.Length,PByte(B),Length(B),aDigest) then
Fail('Could not HMAC');
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));
end;
@ -204,7 +207,81 @@ begin
AssertEquals('Have correct admin',true,(TMyJWT(FVerifyResult).Claims as TMyClaims).Admin);
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;
begin
Inherited;
FKey:=TJWTKey.Create('mysecretkey');

View File

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

View File

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