Browse Source

fcl-hash: started RSASSA_PSS_SIGN

mattias 3 years ago
parent
commit
de5c056ef3
3 changed files with 241 additions and 78 deletions
  1. 213 56
      packages/fcl-hash/src/fprsa.pas
  2. 20 0
      packages/fcl-hash/src/fptlsbigint.pas
  3. 8 22
      packages/fcl-web/tests/tcjwt.pp

+ 213 - 56
packages/fcl-hash/src/fprsa.pas

@@ -35,7 +35,8 @@ type
     DP: PBigInt;            // d mod (p-1)
     DQ: PBigInt;            // d mod (q-1)
     QInv: PBigInt;          // q^-1 mod p
-    ModulusLen: Integer;
+    ModulusLen: Integer; // in bytes
+    ModulusBits: Integer; // in bits
     Context: TBigIntContext;
   end;
 
@@ -83,22 +84,22 @@ procedure RSAInitFromPrivateKeyDER(var RSA: TRSA; const PrivateKeyDER: TBytes);
 procedure X509RsaPrivateKeyInitFromDER(out RSA: TX509RSAPrivateKey; const PrivateKeyDER: TBytes);
 
 { Perform PKCS1.5 Encryption or Signing
-  Context: The RSA context containing Private and/or Public keys
+  RSA: The RSA context containing Private and/or Public keys
   Input: The data to be encrypted
   Len: The size of the input data in bytes (Must be <= Modulus length - 11 to
        make the padding at least 8 bytes as recommended by RFC2313)
   Output: The buffer for the encrypted result (Must always be Modulus length)
   Sign: If true then sign instead of encrypting
-  Return: The number of bytes encrypted or -1 on error }
+  Result: The number of bytes encrypted or -1 on error }
 function RSAEncryptSign(var RSA: TRSA; const Input: PByte; Len: Integer; Output: PByte; Sign: Boolean): Integer;
 
 { Perform PKCS1.5 Decryption or Verification
-  Context: The RSA context containing Private and/or Public keys
+  RSA: The RSA context containing Private and/or Public keys
   Input: The data to be decrypted (Must always be Modulus length)
   Output: The buffer for the decrypted result
   Len: The size of the output buffer in bytes
   Verify: If true then verify instead of decrypting
-  Return: The number of bytes decrypted or -1 on error }
+  Result: The number of bytes decrypted or -1 on error }
 function RSADecryptVerify(var RSA: TRSA; const Input: PByte; Output: PByte; Len: Integer; Verify: Boolean): Integer;
 
 function RS256VerifyFromPublicKeyHexa(const PublicKeyHexa, SignatureBaseHash, Signature: String): Boolean;
@@ -106,14 +107,41 @@ function TestRS256Verify: Boolean;
 
 function EncodeDigestInfoSHA(SHAType, len: byte): TBytes;
 
-// integer <-> octetstring
-function I2OSP(c: DWord; Len: integer): string;
-function OSP2I(const Octet: string): DWord;
+const
+  RSA_PSS_SaltLen_HashLen = -1;
+  RSA_PSS_SaltLen_Max = -2;
+type
+  TRSAHashFunction = procedure(Input: PByte; InLen: Integer; Output: PByte);
+
+  { TRSAHashFuncInfo }
+
+  TRSAHashFuncInfo = record
+    Func: TRSAHashFunction;
+    DigestLen: Word;
+    procedure InitSHA1;
+    procedure InitSHA256;
+  end;
+  PRSAHashFuncInfo = ^TRSAHashFuncInfo;
+
+{ Perform PSASSA-PSS using MGF1 and a hash function
+  RSA: The RSA context containing the private key
+  Input: The data to be signed
+  Len: The size of the input data in bytes (Must be <= Modulus length - 11 to
+  Output: The buffer for the encrypted result (Must always be RSA.ModulusLen)
+  Result: The number of bytes encrypted or -1 on error }
+function RSASSA_PSS_SIGN(var RSA: TRSA; Input: PByte; Len: Integer;
+  HashFunc: PRSAHashFuncInfo; Output: PByte; SaltLen: integer = RSA_PSS_SaltLen_HashLen): Integer;
+
+procedure EMSA_PSS_ENCODE(Input: PByte; InLen: Integer; HashFunc: PRSAHashFuncInfo;
+  Output: PByte; OutLen: integer; ModBits: integer; SaltLen: integer = RSA_PSS_SaltLen_HashLen);
+
+// integer to octetstring
+function I2OSP(c: DWord; Len: integer): string; overload;
+procedure I2OSP(c: DWord; Dest: PByte; Len: integer); overload;
 
 // MGF1 (Mask Generating Function 1) of PKCS1 (Public Key Cryptography Standard #1)
-type
-  THashFunction = function(const s: string): string; // string to hash digest
-function MGF1(const InputStr: string; Len: integer; HashFunc: THashFunction): string;
+function MGF1(const InputStr: string; HashFunc: PRSAHashFuncInfo; Len: integer): string; overload;
+procedure MGF1(Input: PByte; InLen: Integer; HashFunc: PRSAHashFuncInfo; Output: PByte; OutLen: integer); overload;
 function MGF1SHA1(const InputStr: string; Len: integer): string;
 function MGF1SHA256(const InputStr: string; Len: integer): string;
 
@@ -159,6 +187,7 @@ procedure RsaInitFromPublicKey(var RSA: TRSA; const Modulus, Exponent: String);
 begin
   RSA.ModulusLen := length(Modulus);
   RSA.M := BIImport(RSA.Context, Modulus);
+  RSA.ModulusBits :=  BIBitCount(RSA.M);
   BISetMod(RSA.Context, RSA.M, BIGINT_M_OFFSET);
   RSA.E := BIImport(RSA.Context, Exponent);
   BIPermanent(RSA.E);
@@ -173,6 +202,7 @@ begin
     Exit;
   RSA.ModulusLen := length(RSAPublicKey.Modulus);
   RSA.M := BIImport(RSA.Context, RSAPublicKey.Modulus);
+  RSA.ModulusBits := BIBitCount(RSA.M);
   BISetMod(RSA.Context, RSA.M, BIGINT_M_OFFSET);
   RSA.E := BIImport(RSA.Context, RSAPublicKey.Exponent);
   BIPermanent(RSA.E);
@@ -189,7 +219,7 @@ end;
 procedure X509RsaPublicKeyInitFromDER(out RSA: TX509RSAPublicKey;
   const PublicKeyDER: TBytes);
 var
-  ASNType, ASNSize: integer;
+  ASNType, ASNSize, i: integer;
   List: TStringList;
 begin
   RSA:=Default(TX509RSAPublicKey);
@@ -201,7 +231,7 @@ begin
     ASNDebugList('X509RsaPublicKeyInitFromDER',List);
     {$ENDIF}
 
-    if List.Count<7 then
+    if List.Count<6 then
       raise Exception.Create('20220428180055');
 
     // check sequence
@@ -221,24 +251,29 @@ begin
     if List[2]<>RSAPublicKeyOID then
       raise Exception.Create('20220428181542');
 
-    // check null
-    ASNParse_GetItem(List,3,ASNType,ASNSize);
-    if ASNType<>ASN1_NULL then
-      raise Exception.Create('20220428181659');
+    // check optional null
+    i:=3;
+    ASNParse_GetItem(List,i,ASNType,ASNSize);
+    if ASNType=ASN1_NULL then
+      inc(i);
 
-    // check optional algorithm params
-    ASNParse_GetItem(List,4,ASNType,ASNSize);
+    // check algorithm params
+    ASNParse_GetItem(List,i,ASNType,ASNSize);
     if ASNType<>ASN1_BITSTR then
       raise Exception.Create('20220428181913');
+    inc(i);
+
+    if i+2>List.Count then
+      raise Exception.Create('20220428180055');
 
     // check sequence
-    ASNParse_GetItem(List,5,ASNType,ASNSize);
+    ASNParse_GetItem(List,i,ASNType,ASNSize);
     if ASNType<>ASN1_SEQ then
       raise Exception.Create('20220428181933');
 
     // public key
-    RSA.Modulus:=ASNParse_GetIntBytes(List,6,20220428182235);
-    RSA.Exponent:=ASNParse_GetIntBytes(List,7,20220428182241);
+    RSA.Modulus:=ASNParse_GetIntBytes(List,i+1,20220428182235);
+    RSA.Exponent:=ASNParse_GetIntBytes(List,i+2,20220428182241);
 
     {$IFDEF TLS_DEBUG}
     writeln('X509RsaPublicKeyInitFromDER: ');
@@ -271,6 +306,7 @@ begin
     Exit;
   RSA.ModulusLen := length(RSAPrivateKey.Modulus);
   RSA.M := BIImport(RSA.Context, RSAPrivateKey.Modulus);
+  RSA.ModulusBits := BIBitCount(RSA.M);
   BISetMod(RSA.Context, RSA.M, BIGINT_M_OFFSET);
   RSA.E := BIImport(RSA.Context, RSAPrivateKey.PublicExponent);
   BIPermanent(RSA.E);
@@ -689,78 +725,199 @@ begin
     ];
 end;
 
-function I2OSP(c: DWord; Len: integer): string;
+function RSASSA_PSS_SIGN(var RSA: TRSA; Input: PByte; Len: Integer;
+  HashFunc: PRSAHashFuncInfo; Output: PByte; SaltLen: integer): Integer;
 var
-  i: DWord;
+  EncodedMsg: TBytes;
+  ModBits: Integer;
+begin
+  Result:=-1;
+
+  ModBits:=(RSA.ModulusBits-1) and 7;
+  if ModBits=0 then
+    raise Exception.Create('20220502000942 RSA n too small');
+
+  SetLength(EncodedMsg{%H-},RSA.ModulusLen);
+  EMSA_PSS_ENCODE(Input,Len, HashFunc, @EncodedMsg[0], length(EncodedMsg), ModBits, SaltLen);
+
+  raise Exception.Create('20220502000942 implement me');
+
+  //Result:=RSASP1(RSA,EncodedMsg,Output);
+end;
+
+procedure EMSA_PSS_ENCODE(Input: PByte; InLen: Integer;
+  HashFunc: PRSAHashFuncInfo; Output: PByte; OutLen: integer; ModBits: integer;
+  SaltLen: integer);
+// RFC 3447 9.1.1  Encoding operation
+var
+  ZeroesHashSalt, H, DB, DBMask, MaskedDB: TBytes;
+  MsgHashP, SaltP: PByte;
+  Padding, HashLen, i: Integer;
+begin
+  HashLen:=HashFunc^.DigestLen;
+
+  if SaltLen = RSA_PSS_SaltLen_HashLen then
+    SaltLen:=HashLen
+  else if SaltLen = RSA_PSS_SaltLen_Max then
+    SaltLen:=OutLen-HashLen-2
+  else if SaltLen < RSA_PSS_SaltLen_Max then
+    raise Exception.Create('20220501233610');
+
+  // check OutLen
+  if HashLen + SaltLen + 2 > OutLen then
+    raise Exception.Create('20220501221837');
+
+  // ZeroesHashSalt := 8 zeroes + InputHash + Salt
+  SetLength(ZeroesHashSalt{%H-},8+HashLen+SaltLen);
+  FillByte(ZeroesHashSalt[0],8,0);
+  MsgHashP:=@ZeroesHashSalt[8];
+  HashFunc^.Func(Input,InLen,MsgHashP);
+  SaltP:=MsgHashP+HashLen;
+  if SaltLen>0 then
+    if not CryptoGetRandomBytes(SaltP,SaltLen) then
+      raise Exception.Create('20220501222748');
+
+  // hash ZeroesHashSalt
+  SetLength(H{%H-},HashLen);
+  HashFunc^.Func(@ZeroesHashSalt[0],length(ZeroesHashSalt),@H[0]);
+
+  // DB := padding zeroes + #1 + Salt
+  SetLength(DB{%H-},OutLen-HashLen-1);
+  Padding:=length(DB)-SaltLen-1;
+  if Padding>0 then
+    FillByte(DB[0],Padding,0);
+  DB[Padding]:=1;
+  System.Move(SaltP^,DB[Padding+1],SaltLen);
+
+  // dbMask := MGF(H, OutLen - HashLen - 1)
+  SetLength(DBMask{%H-},length(DB));
+  MGF1(@H[0],HashLen,HashFunc,@DBMask[0],length(DB));
+
+  // MaskedDB := DB xor DBMask
+  SetLength(MaskedDB{%H-},length(DB));
+  for i:=0 to length(DB) do
+    MaskedDB[i]:=DB[i] xor DBMask[i];
+
+  // set the leftmost bits of leftmost byte to zero
+  if ModBits>0 then
+    MaskedDB[0] := MaskedDB[0] and ($ff shr (8-ModBits));
+
+  System.Move(MaskedDB[0],Output^,length(MaskedDB));
+  inc(Output,length(MaskedDB));
+  System.Move(H[0],Output^,length(H));
+  inc(Output,length(H));
+  Output^:=$bc;
+end;
+
+function I2OSP(c: DWord; Len: integer): string;
 begin
-  if Len>4 then
-    raise Exception.Create('20220501190110');
   SetLength(Result{%H-},Len);
-  for i:=Len downto 1 do
+  I2OSP(c,@Result[1],Len);
+end;
+
+procedure I2OSP(c: DWord; Dest: PByte; Len: integer);
+var
+  i: Integer;
+begin
+  for i:=Len-1 downto 0 do
   begin
-    Result[i]:=chr(c and $ff);
+    Dest[i]:=c and $ff;
     c:=c shr 8;
   end;
   if c>0 then
     raise Exception.Create('20220501190124');
 end;
 
-function OSP2I(const Octet: string): DWord;
-var
-  i: Integer;
+function MGF1(const InputStr: string; HashFunc: PRSAHashFuncInfo; Len: integer): string;
 begin
-  Result:=0;
-  if length(Octet)>4 then
-    raise Exception.Create('20220501190308');
-  for i:=1 to length(Octet) do
-    Result:=Result shl 8 + ord(Octet[i]);
+  SetLength(Result{%H-},Len);
+  if Len=0 then exit;
+  MGF1(PByte(PChar(InputStr)){InputStr might be empty!},length(InputStr), HashFunc, @Result[1], Len);
 end;
 
-function MGF1(const InputStr: string; Len: integer; HashFunc: THashFunction
-  ): string;
+procedure MGF1(Input: PByte; InLen: Integer; HashFunc: PRSAHashFuncInfo;
+  Output: PByte; OutLen: integer);
 var
+  p, CounterP, InpP: PByte;
+  i, r: Integer;
+  HashLen: Word;
+  InputCounted, Tmp: TBytes;
   Counter: DWord;
 begin
   Counter:=0;
-  Result:='';
-  while length(Result)<Len do
+  HashLen:=HashFunc^.DigestLen;
+  SetLength(InputCounted{%H-},InLen+4);
+  InpP:=@InputCounted[0];
+  if InLen>0 then
+    System.Move(Input^,InpP^,InLen);
+  CounterP:=InpP+InLen;
+  p:=Output;
+  for i:=1 to (OutLen div HashLen) do
   begin
-    Result:=Result+HashFunc(InputStr+I2OSP(Counter,4));
+    I2OSP(Counter,CounterP,4);
+    HashFunc^.Func(InpP,InLen+4,p);
+    inc(p,HashLen);
     inc(Counter);
   end;
-  SetLength(Result,Len);
+  r:=OutLen mod HashLen;
+  if r>0 then
+  begin
+    I2OSP(Counter,CounterP,4);
+    SetLength(Tmp{%H-},HashLen);
+    HashFunc^.Func(InpP,InLen+4,@Tmp[0]);
+    System.Move(Tmp[0],p^,r);
+  end;
 end;
 
-function SHA1StrToDigest(const InputStr: string): string;
+function MGF1SHA1(const InputStr: string; Len: integer): string;
 var
-  Digest: TSHA1Digest;
+  HashFunc: TRSAHashFuncInfo;
 begin
-  Digest:=SHA1String(InputStr);
-  SetLength(Result{%H-},length(Digest));
-  System.Move(Digest[0],Result[1],length(Digest));
-  if Digest[0]=0 then ;
+  HashFunc.InitSHA1;
+  Result:=MGF1(InputStr,@HashFunc,Len);
 end;
 
-function MGF1SHA1(const InputStr: string; Len: integer): string;
+function MGF1SHA256(const InputStr: string; Len: integer): string;
+var
+  HashFunc: TRSAHashFuncInfo;
+begin
+  HashFunc.InitSHA256;
+  Result:=MGF1(InputStr,@HashFunc,Len);
+end;
+
+procedure HashFuncSHA1(Input: PByte; InLen: Integer; Output: PByte);
+var
+  Context: TSHA1Context;
+  Digest: TSHA1Digest;
 begin
-  Result:=MGF1(InputStr,Len,@SHA1StrToDigest);
+  SHA1Init(Context);
+  SHA1Update(Context,Input^,InLen);
+  SHA1Final(Context,Digest);
+  System.Move(Digest[0],Output^,SizeOf(Digest));
 end;
 
-function SHA256StrToDigest(const InputStr: string): string;
+procedure HashFuncSHA256(Input: PByte; InLen: Integer; Output: PByte);
 var
   SHA256: TSHA256;
 begin
   SHA256.Init;
-  SHA256.Update(@InputStr[1],length(InputStr));
+  SHA256.Update(Input,InLen);
   SHA256.Final;
+  System.Move(SHA256.Digest[0],Output^,SHA256_DIGEST_SIZE);
+end;
 
-  SetLength(Result{%H-},length(SHA256.Digest));
-  System.Move(SHA256.Digest[0],Result[1],length(SHA256.Digest));
+{ TRSAHashFuncInfo }
+
+procedure TRSAHashFuncInfo.InitSHA1;
+begin
+  Func:=@HashFuncSHA1;
+  DigestLen:=SizeOf(TSHA1Digest);
 end;
 
-function MGF1SHA256(const InputStr: string; Len: integer): string;
+procedure TRSAHashFuncInfo.InitSHA256;
 begin
-  Result:=MGF1(InputStr,Len,@SHA256StrToDigest);
+  Func:=@HashFuncSHA256;
+  DigestLen:=SHA256_DIGEST_SIZE;
 end;
 
 { TX509RSAPrivateKey }

+ 20 - 0
packages/fcl-hash/src/fptlsbigint.pas

@@ -85,6 +85,7 @@ function BIImport(var Context: TBigIntContext; Data: PByte; const Size: Integer)
 function BIImport(var Context: TBigIntContext; const Data: TBytes): PBigInt; overload;
 function BIImport(var Context: TBigIntContext; const Data: AnsiString): PBigInt; overload;
 function IntToBI(var Context: TBigIntContext; I: TBIComponent): PBigInt;
+function BIBitCount(BI: PBigInt): integer;
 
 function BIAdd(var Context: TBigIntContext; BIA, BIB: PBigInt): PBigInt;
 function BISubtract(var Context: TBigIntContext; BIA, BIB: PBigInt;out IsNegative: Boolean): PBigInt;
@@ -667,6 +668,25 @@ begin
   Result^.Components[0] := I;
 end;
 
+function BIBitCount(BI: PBigInt): integer;
+var
+  i: Integer;
+  c: TBIComponent;
+begin
+  i:=BI^.Size-1;
+  while (i>=0) and (BI^.Components[i]=0) do
+    dec(i);
+  if i<0 then
+    exit(0);
+  Result:=i*BIGINT_COMP_BIT_SIZE;
+  c:=BI^.Components[i];
+  while (c>0) do
+  begin
+    inc(Result);
+    c:=c shr 1;
+  end;
+end;
+
 function BIAdd(var Context: TBigIntContext; BIA, BIB: PBigInt): PBigInt;
 var
   N: Integer;

+ 8 - 22
packages/fcl-web/tests/tcjwt.pp

@@ -62,9 +62,9 @@ type
     procedure TestVerifyRS512Pem;
     procedure TestVerifyRS256_rfc7515;
     procedure TestI2OSP;
-    procedure TestOSP2I;
     procedure TestMGF1SHA1;
     procedure TestMGF1SHA256;
+    procedure TestVerifyPS256; // ToDo
   end;
 
 implementation
@@ -428,27 +428,6 @@ begin
   t($ffffffff,4,#255#255#255#255);
 end;
 
-procedure TTestJWT.TestOSP2I;
-
-  procedure t(const Octet: string; const Expected: DWord);
-  var
-    Actual: DWord;
-  begin
-    Actual:=OSP2I(Octet);
-    if Actual<>Expected then
-      Fail('OSP2I('+StringToHex(Octet)+') expected "'+HexStr(Expected,8)+'", but got "'+HexStr(Actual,8)+'"');
-  end;
-
-begin
-  t('',0);
-  t(#0,0);
-  t(#0#0,0);
-  t(#0#0#0,0);
-  t(#0#0#0#0,0);
-  t(#1#0#0#0,$1000000);
-  t(#255#255#255#255,$ffffffff);
-end;
-
 procedure TTestJWT.TestMGF1SHA1;
 
   procedure t(const InputStr: string; Len: integer; const ExpectedHex: String);
@@ -479,9 +458,16 @@ procedure TTestJWT.TestMGF1SHA256;
   end;
 
 begin
+  t('bar',1,'38');
   t('bar',50,'382576A7841021CC28FC4C0948753FB8312090CEA942EA4C4E735D10DC724B155F9F6069F289D61DACA0CB814502EF04EAE1');
 end;
 
+procedure TTestJWT.TestVerifyPS256;
+begin
+  // RSASSA-PSS using SHA-256 and MGF1 with SHA-256
+
+end;
+
 procedure TTestJWT.SetUp;
 begin
   Inherited;