Browse Source

ISSigTool: Add support for an issig-v2 format which also checks the base filename, case insensitively.

Verifying doesn't work for Unicode filenames because of the two checks against NonControlASCIICharsSet in ISSigLoadTextFromFile and ISSigVerifySignatureText. Not yet sure what to replace it with. Signing them does work though.

Still supports verification of the issig-v1 format.
Martijn Laan 3 months ago
parent
commit
22c8f729e2
2 changed files with 42 additions and 24 deletions
  1. 30 20
      Components/ISSigFunc.pas
  2. 12 4
      Projects/ISSigTool.dpr

+ 30 - 20
Components/ISSigFunc.pas

@@ -28,21 +28,22 @@ function ISSigLoadTextFromFile(const AFilename: String): String;
 procedure ISSigSaveTextToFile(const AFilename, AText: String);
 
 function ISSigCreateSignatureText(const AKey: TECDSAKey;
-  const AFileSize: Int64; const AFileHash: TSHA256Digest): String;
+  const AFileName: String; const AFileSize: Int64; const AFileHash: TSHA256Digest): String;
 function ISSigVerifySignatureText(const AAllowedKeys: array of TECDSAKey;
-  const AText: String; out AFileSize: Int64;
+  const AText: String; out AFileName: String; out AFileSize: Int64;
   out AFileHash: TSHA256Digest): TISSigVerifySignatureResult; overload;
 function ISSigVerifySignatureText(const AAllowedKeys: array of TECDSAKey;
-  const AText: String; out AFileSize: Int64;
+  const AText: String; out AFileName: String; out AFileSize: Int64;
   out AFileHash: TSHA256Digest; out AKeyUsedID: String): TISSigVerifySignatureResult; overload;
 
 function ISSigVerifySignature(const AFilename: String; const AAllowedKeys: array of TECDSAKey;
-  out AExpectedFileSize: Int64; out AExpectedFileHash: TSHA256Digest; out AKeyUsedID: String;
+  out AExpectedFileName: String; out AExpectedFileSize: Int64; out AExpectedFileHash: TSHA256Digest;
+  out AKeyUsedID: String;
   const AFileMissingErrorProc: TISSigVerifySignatureFileMissingErrorProc;
   const ASigFileMissingErrorProc: TISSigVerifySignatureSigFileMissingErrorProc;
   const AVerificationFailedErrorProc: TISSigVerifySignatureVerificationFailedErrorProc): Boolean; overload;
 function ISSigVerifySignature(const AFilename: String; const AAllowedKeys: array of TECDSAKey;
-  out AExpectedFileSize: Int64; out AExpectedFileHash: TSHA256Digest;
+  out AExpectedFileName: String; out AExpectedFileSize: Int64; out AExpectedFileHash: TSHA256Digest;
   const AFileMissingErrorProc: TISSigVerifySignatureFileMissingErrorProc;
   const ASigFileMissingErrorProc: TISSigVerifySignatureSigFileMissingErrorProc;
   const AVerificationFailedErrorProc: TISSigVerifySignatureVerificationFailedErrorProc): Boolean; overload;
@@ -93,11 +94,13 @@ begin
   TSHA256Digest(Result) := SHA256DigestFromString(S);
 end;
 
-function CalcHashToSign(const AFileSize: Int64;
+function CalcHashToSign(const AFileName: String; const AFileSize: Int64;
   const AFileHash: TSHA256Digest): TSHA256Digest;
 begin
   var Context: TSHA256Context;
   SHA256Init(Context);
+  if AFileName <> '' then
+    SHA256Update(Context, Pointer(AFileName)^, Length(AFileName)*SizeOf(AFileName[1]));
   SHA256Update(Context, AFileSize, SizeOf(AFileSize));
   SHA256Update(Context, AFileHash, SizeOf(AFileHash));
   Result := SHA256Final(Context);
@@ -165,7 +168,7 @@ begin
 end;
 
 function ISSigCreateSignatureText(const AKey: TECDSAKey;
-  const AFileSize: Int64; const AFileHash: TSHA256Digest): String;
+  const AFileName: String; const AFileSize: Int64; const AFileHash: TSHA256Digest): String;
 begin
   { File size is limited to 16 digits (enough for >9 EB) }
   if (AFileSize < 0) or (AFileSize > {$IF CompilerVersion >= 36.0} 9_999_999_999_999_999 {$ELSE} 9999999999999999 {$ENDIF} ) then
@@ -174,18 +177,20 @@ begin
   var PublicKey: TECDSAPublicKey;
   AKey.ExportPublicKey(PublicKey);
 
-  const HashToSign = CalcHashToSign(AFileSize, AFileHash);
+  const HashToSign = CalcHashToSign(AFileName, AFileSize, AFileHash);
   var Sig: TECDSASignature;
   AKey.SignHash(HashToSign, Sig);
 
   Result := Format(
-    'format issig-v1'#13#10 +
+    'format issig-v2'#13#10 +
+    'file-name %s'#13#10 +
     'file-size %d'#13#10 +
     'file-hash %s'#13#10 +
     'key-id %s'#13#10 +
     'sig-r %s'#13#10 +
     'sig-s %s'#13#10,
-    [AFileSize,
+    [AFileName,
+     AFileSize,
      SHA256DigestToString(AFileHash),
      SHA256DigestToString(CalcKeyID(PublicKey)),
      ECDSAInt256ToString(Sig.Sig_r),
@@ -193,15 +198,16 @@ begin
 end;
 
 function ISSigVerifySignatureText(const AAllowedKeys: array of TECDSAKey;
-  const AText: String; out AFileSize: Int64;
+  const AText: String; out AFileName: String; out AFileSize: Int64;
   out AFileHash: TSHA256Digest; out AKeyUsedID: String): TISSigVerifySignatureResult;
 var
   TextValues: record
-    Format, FileSize, FileHash, KeyID, Sig_r, Sig_s: String;
+    Format, FileName, FileSize, FileHash, KeyID, Sig_r, Sig_s: String;
   end;
 begin
   { To be extra safe, clear the "out" parameters just in case the caller isn't
     properly checking the function result }
+  AFileName := '';
   AFileSize := -1;
   FillChar(AFileHash, SizeOf(AFileHash), 0);
   AKeyUsedID := '';
@@ -213,7 +219,8 @@ begin
 
   var SS := TStringScanner.Create(AText);
   if not ConsumeLineValue(SS, 'format', TextValues.Format, 8, 8, NonControlASCIICharsSet) or
-     (TextValues.Format <> 'issig-v1') or
+     ((TextValues.Format <> 'issig-v1') and ((TextValues.Format <> 'issig-v2'))) or
+     ((TextValues.Format = 'issig-v2') and not ConsumeLineValue(SS, 'file-name', TextValues.FileName, 1, MaxInt, NonControlASCIICharsSet)) or
      not ConsumeLineValue(SS, 'file-size', TextValues.FileSize, 1, 16, DigitsSet) or
      not ConsumeLineValue(SS, 'file-hash', TextValues.FileHash, 64, 64, HexDigitsSet) or
      not ConsumeLineValue(SS, 'key-id', TextValues.KeyID, 64, 64, HexDigitsSet) or
@@ -242,13 +249,15 @@ begin
     Exit(vsrKeyNotFound);
   AKeyUsedID := TextValues.KeyID;
 
+  const UnverifiedFileName = TextValues.FileName;
   const UnverifiedFileSize = StrToInt64(TextValues.FileSize);
   const UnverifiedFileHash = SHA256DigestFromString(TextValues.FileHash);
-  const HashToSign = CalcHashToSign(UnverifiedFileSize, UnverifiedFileHash);
+  const HashToSign = CalcHashToSign(UnverifiedFileName, UnverifiedFileSize, UnverifiedFileHash);
   var Sig: TECDSASignature;
   Sig.Sig_r := ECDSAInt256FromString(TextValues.Sig_r);
   Sig.Sig_s := ECDSAInt256FromString(TextValues.Sig_s);
   if KeyUsed.VerifySignature(HashToSign, Sig) then begin
+    AFileName := UnverifiedFileName;
     AFileSize := UnverifiedFileSize;
     AFileHash := UnverifiedFileHash;
     Result := vsrSuccess;
@@ -257,15 +266,16 @@ begin
 end;
 
 function ISSigVerifySignatureText(const AAllowedKeys: array of TECDSAKey;
-  const AText: String; out AFileSize: Int64;
+  const AText: String; out AFileName: String; out AFileSize: Int64;
   out AFileHash: TSHA256Digest): TISSigVerifySignatureResult;
 begin
   var KeyUsedID: String;
-  Result := ISSigVerifySignatureText(AAllowedKeys, AText, AFileSize, AFileHash, KeyUsedID);
+  Result := ISSigVerifySignatureText(AAllowedKeys, AText, AFileName, AFileSize, AFileHash, KeyUsedID);
 end;
 
 function ISSigVerifySignature(const AFilename: String; const AAllowedKeys: array of TECDSAKey;
-  out AExpectedFileSize: Int64; out AExpectedFileHash: TSHA256Digest; out AKeyUsedID: String;
+  out AExpectedFileName: String; out AExpectedFileSize: Int64; out AExpectedFileHash: TSHA256Digest;
+  out AKeyUsedID: String;
   const AFileMissingErrorProc: TISSigVerifySignatureFileMissingErrorProc;
   const ASigFileMissingErrorProc: TISSigVerifySignatureSigFileMissingErrorProc;
   const AVerificationFailedErrorProc: TISSigVerifySignatureVerificationFailedErrorProc): Boolean;
@@ -294,20 +304,20 @@ begin
   end;
   const SigText = ISSigLoadTextFromFile(SigFilename);
   const VerifyResult = ISSigVerifySignatureText(AAllowedKeys, SigText,
-    AExpectedFileSize, AExpectedFileHash, AKeyUsedID);
+    AExpectedFileName, AExpectedFileSize, AExpectedFileHash, AKeyUsedID);
   Result := VerifyResult = vsrSuccess;
   if not Result and Assigned(AVerificationFailedErrorProc) then
     AVerificationFailedErrorProc(AFilename, SigFilename, VerifyResult);
 end;
 
 function ISSigVerifySignature(const AFilename: String; const AAllowedKeys: array of TECDSAKey;
-  out AExpectedFileSize: Int64; out AExpectedFileHash: TSHA256Digest;
+  out AExpectedFileName: String; out AExpectedFileSize: Int64; out AExpectedFileHash: TSHA256Digest;
   const AFileMissingErrorProc: TISSigVerifySignatureFileMissingErrorProc;
   const ASigFileMissingErrorProc: TISSigVerifySignatureSigFileMissingErrorProc;
   const AVerificationFailedErrorProc: TISSigVerifySignatureVerificationFailedErrorProc): Boolean;
 begin
   var KeyUsedID: String;
-  Result := ISSigVerifySignature(AFilename, AAllowedKeys, AExpectedFileSize,
+  Result := ISSigVerifySignature(AFilename, AAllowedKeys, AExpectedFileName, AExpectedFileSize,
     AExpectedFileHash, KeyUsedID, AFileMissingErrorProc, ASigFileMissingErrorProc,
     AVerificationFailedErrorProc);
 end;

+ 12 - 4
Projects/ISSigTool.dpr

@@ -159,6 +159,7 @@ procedure SignSingleFile(const AKey: TECDSAKey; const AFilename: String);
 begin
   PrintFmtUnlessQuiet('%s: ', [AFilename], False);
 
+  const FileName = PathExtractName(AFilename);
   var FileSize: Int64;
   var FileHash: TSHA256Digest;
   const F = TFile.Create(AFilename, fdOpenExisting, faRead, fsRead);
@@ -175,18 +176,19 @@ begin
     being re-signed but its contents haven't changed, we attempt to load and
     verify the existing .issig file. If the key, file size, and file hash are
     all up to date, then we skip creation of a new .issig file. }
+  var ExistingFileName: String;
   var ExistingFileSize: Int64;
   var ExistingFileHash: TSHA256Digest;
   const Verified = ISSigVerifySignature(AFilename, [AKey],
-    ExistingFileSize, ExistingFileHash, nil, nil, nil);
+    ExistingFileName, ExistingFileSize, ExistingFileHash, nil, nil, nil);
 
-  if Verified and (FileSize = ExistingFileSize) and
+  if Verified and SameText(FileName, ExistingFileName) and (FileSize = ExistingFileSize) and
      SHA256DigestsEqual(FileHash, ExistingFileHash) then begin
     PrintUnlessQuiet('signature unchanged');
     Exit;
   end;
 
-  const SigText = ISSigCreateSignatureText(AKey, FileSize, FileHash);
+  const SigText = ISSigCreateSignatureText(AKey, FileName, FileSize, FileHash);
   ISSigSaveTextToFile(AFilename + ISSigExt, SigText);
   PrintUnlessQuiet('signature written');
 end;
@@ -209,9 +211,10 @@ begin
   Result := False;
   PrintFmtUnlessQuiet('%s: ', [AFilename], False);
 
+  var ExpectedFileName: String;
   var ExpectedFileSize: Int64;
   var ExpectedFileHash: TSHA256Digest;
-  if not ISSigVerifySignature(AFilename, [AKey], ExpectedFileSize, ExpectedFileHash,
+  if not ISSigVerifySignature(AFilename, [AKey], ExpectedFileName, ExpectedFileSize, ExpectedFileHash,
     procedure(const Filename: String)
     begin
       PrintUnlessQuiet('MISSINGFILE (File does not exist)');
@@ -234,6 +237,11 @@ begin
   ) then
     Exit;
 
+  if (ExpectedFileName <> '') and not SameText(PathExtractName(AFilename), ExpectedFileName) then begin
+    PrintUnlessQuiet('WRONGNAME (File name is incorrect)');
+    Exit;
+  end;
+
   const F = TFile.Create(AFilename, fdOpenExisting, faRead, fsRead);
   try
     if Int64(F.Size) <> ExpectedFileSize then begin