Kaynağa Gözat

Merge branch 'files-hash'

Martijn Laan 3 ay önce
ebeveyn
işleme
6e32515c60

+ 5 - 6
Examples/CodeDownloadFiles.iss

@@ -7,10 +7,10 @@
 ; -For innosetup-latest.exe and MyProg-ExtraReadmes.7z: using Inno Setup
 ; -For innosetup-latest.exe and MyProg-ExtraReadmes.7z: using Inno Setup
 ;  Signature Tool, the [ISSigKeys] section, and the AddWithISSigVerify support
 ;  Signature Tool, the [ISSigKeys] section, and the AddWithISSigVerify support
 ;  function
 ;  function
-; -For iscrypt.dll: using a simple SHA256 check
+; -For iscrypt.dll: using a simple SHA-256 hash check
 ; Using the Inno Setup Signature Tool has the benefit that the script does not
 ; Using the Inno Setup Signature Tool has the benefit that the script does not
 ; need to be changed when the downloaded file changes, so any installers built
 ; need to be changed when the downloaded file changes, so any installers built
-; will also keep working
+; will also keep working (they are "evergreen")
 
 
 [Setup]
 [Setup]
 AppName=My Program
 AppName=My Program
@@ -38,10 +38,12 @@ Source: "Readme.txt"; DestDir: "{app}"; Flags: isreadme
 ; These files will be downloaded using [Files] only
 ; These files will be downloaded using [Files] only
 Source: "https://jrsoftware.org/download.php/is.exe?dontcount=1"; DestName: "innosetup-latest.exe"; DestDir: "{app}"; \
 Source: "https://jrsoftware.org/download.php/is.exe?dontcount=1"; DestName: "innosetup-latest.exe"; DestDir: "{app}"; \
   ExternalSize: 7_000_000; Flags: external download ignoreversion issigverify
   ExternalSize: 7_000_000; Flags: external download ignoreversion issigverify
+Source: "https://jrsoftware.org/download.php/iscrypt.dll?dontcount=1"; DestName: "ISCrypt.dll"; DestDir: "{app}"; \
+  Hash: "2f6294f9aa09f59a574b5dcd33be54e16b39377984f3d5658cda44950fa0f8fc"; \
+  ExternalSize: 2560; Flags: external download ignoreversion
 ; These files will be downloaded by [Code]. If you include flag issigverify here the file will be verified
 ; These files will be downloaded by [Code]. If you include flag issigverify here the file will be verified
 ; a second time while copying. Verification while copying is efficient, except for archives.
 ; a second time while copying. Verification while copying is efficient, except for archives.
 Source: "{tmp}\MyProg-ExtraReadmes.7z"; DestDir: "{app}"; Flags: external extractarchive recursesubdirs ignoreversion
 Source: "{tmp}\MyProg-ExtraReadmes.7z"; DestDir: "{app}"; Flags: external extractarchive recursesubdirs ignoreversion
-Source: "{tmp}\ISCrypt.dll"; DestDir: "{app}"; Flags: external ignoreversion
 
 
 [Icons]
 [Icons]
 Name: "{group}\My Program"; Filename: "{app}\MyProg.exe"
 Name: "{group}\My Program"; Filename: "{app}\MyProg.exe"
@@ -75,9 +77,6 @@ begin
     DownloadPage.AddWithISSigVerify(
     DownloadPage.AddWithISSigVerify(
       'https://jrsoftware.org/download.php/myprog-extrareadmes.7z', '',
       'https://jrsoftware.org/download.php/myprog-extrareadmes.7z', '',
       'MyProg-ExtraReadmes.7z', AllowedKeysRuntimeIDs);
       'MyProg-ExtraReadmes.7z', AllowedKeysRuntimeIDs);
-    DownloadPage.Add(
-      'https://jrsoftware.org/download.php/iscrypt.dll?dontcount=1',
-      'ISCrypt.dll', '2f6294f9aa09f59a574b5dcd33be54e16b39377984f3d5658cda44950fa0f8fc');
     DownloadPage.Show;
     DownloadPage.Show;
     try
     try
       try
       try

+ 0 - 2
Files/Default.isl

@@ -219,8 +219,6 @@ StopDownload=Are you sure you want to stop the download?
 ErrorDownloadAborted=Download aborted
 ErrorDownloadAborted=Download aborted
 ErrorDownloadFailed=Download failed: %1 %2
 ErrorDownloadFailed=Download failed: %1 %2
 ErrorDownloadSizeFailed=Getting size failed: %1 %2
 ErrorDownloadSizeFailed=Getting size failed: %1 %2
-ErrorFileHash1=File hash failed: %1
-ErrorFileHash2=Invalid file hash: expected %1, found %2
 ErrorProgress=Invalid progress: %1 of %2
 ErrorProgress=Invalid progress: %1 of %2
 ErrorFileSize=Invalid file size: expected %1, found %2
 ErrorFileSize=Invalid file size: expected %1, found %2
 
 

+ 8 - 1
ISHelp/isetup.xml

@@ -1665,6 +1665,13 @@ ExternalSize: 1_048_576; Flags: external
 </example>
 </example>
 </param>
 </param>
 
 
+<param name="Hash">
+<p>Instructs the compiler or Setup to do a simple SHA-256 hash check instead of a full signature verification, as an alternative to using the <tt>issigverify</tt> flag. The precise effect of this flag depends on whether it is combined with the <tt>external</tt> flag. See the <tt>issigverify</tt> flag description for more information.</p>
+<example>
+<pre>Hash: "2f6294f9aa09f59a574b5dcd33be54e16b39377984f3d5658cda44950fa0f8fc"</pre>
+</example>
+</param>
+
 <param name="ExtractArchivePassword">
 <param name="ExtractArchivePassword">
 <p>Specifies the password of the archive, which can include constants. Please be aware that this password is stored in an unencrypted form in the resulting Setup file(s), even if you have enabled encryption (using the [Setup] section directive <tt>Encryption</tt>).</p>
 <p>Specifies the password of the archive, which can include constants. Please be aware that this password is stored in an unencrypted form in the resulting Setup file(s), even if you have enabled encryption (using the [Setup] section directive <tt>Encryption</tt>).</p>
 <p>This parameter is ignored if the <tt>extractarchive</tt> flag isn't also specified.</p>
 <p>This parameter is ignored if the <tt>extractarchive</tt> flag isn't also specified.</p>
@@ -3709,7 +3716,7 @@ Source: "MyFile.txt"; DestDir: "{app}"; Flags: issigverify
 <p>Compile the script. In the compiler output, you should see a line indicating the file was successfully verified:</p>
 <p>Compile the script. In the compiler output, you should see a line indicating the file was successfully verified:</p>
 <precode>
 <precode>
    Compressing: MyFile.txt
    Compressing: MyFile.txt
-      ISSig verification successful.
+      Verification successful.
 </precode>
 </precode>
 </li>
 </li>
 
 

+ 6 - 5
Projects/Src/Compiler.Messages.pas

@@ -53,7 +53,7 @@ const
   SCompilerStatusFilesCompressingVersion = '   Compressing: %s   (%u.%u.%u.%u)';
   SCompilerStatusFilesCompressingVersion = '   Compressing: %s   (%u.%u.%u.%u)';
   SCompilerStatusFilesStoring = '   Storing: %s';
   SCompilerStatusFilesStoring = '   Storing: %s';
   SCompilerStatusFilesStoringVersion = '   Storing: %s   (%u.%u.%u.%u)';
   SCompilerStatusFilesStoringVersion = '   Storing: %s   (%u.%u.%u.%u)';
-  SCompilerStatusFilesISSigVerified = '      ISSig verification successful.';
+  SCompilerStatusFilesVerified = '      Verification successful.';
   SCompilerStatusCompressingSetupExe = '   Compressing Setup program executable';
   SCompilerStatusCompressingSetupExe = '   Compressing Setup program executable';
   SCompilerStatusUpdatingVersionInfo = '   Updating version info (%s)';
   SCompilerStatusUpdatingVersionInfo = '   Updating version info (%s)';
   SCompilerStatusUpdatingManifest = '   Updating manifest (%s)';
   SCompilerStatusUpdatingManifest = '   Updating manifest (%s)';
@@ -218,7 +218,7 @@ const
   { Flags }
   { Flags }
   SCompilerParamUnknownFlag2 = 'Parameter "%s" includes an unknown flag';
   SCompilerParamUnknownFlag2 = 'Parameter "%s" includes an unknown flag';
   SCompilerParamErrorBadCombo2 = 'Parameter "%s" cannot have both the "%s" and "%s" flags';
   SCompilerParamErrorBadCombo2 = 'Parameter "%s" cannot have both the "%s" and "%s" flags';
-  SCompilerParamErrorBadCombo3 = 'Parameter "%s" cannot have both the "%s" and "%s" flags on the same source file';
+  SCompilerParamErrorBadCombo2SameSource = 'Parameter "%s" cannot have both the "%s" and "%s" flags on a single source file';
   SCompilerParamUnsupportedFlag = 'Parameter "%s" includes a flag that is not supported in this section';
   SCompilerParamUnsupportedFlag = 'Parameter "%s" includes a flag that is not supported in this section';
   SCompilerParamFlagMissing = 'Flag "%s" must be used if flag "%s" is used';
   SCompilerParamFlagMissing = 'Flag "%s" must be used if flag "%s" is used';
   SCompilerParamFlagMissing2 = 'Flag "%s" must be used if parameter "%s" is used';
   SCompilerParamFlagMissing2 = 'Flag "%s" must be used if parameter "%s" is used';
@@ -291,8 +291,9 @@ const
   SCompilerFilesWildcardNotMatched = 'No files found matching "%s"';
   SCompilerFilesWildcardNotMatched = 'No files found matching "%s"';
   SCompilerFilesDestNameCantBeSpecified = 'Parameter "DestName" cannot be specified if ' +
   SCompilerFilesDestNameCantBeSpecified = 'Parameter "DestName" cannot be specified if ' +
     'the "Source" parameter contains wildcards or flag "extractarchive" is used';
     'the "Source" parameter contains wildcards or flag "extractarchive" is used';
-  SCompilerFilesCantHaveNonExternalExternalSize = 'Parameter "ExternalSize" may only be used when ' +
-    'the "external" flag is used';
+  SCompilerFilesParamRequiresFlag = 'Parameter "%s" may only be used when the "%s" flag is used';
+  SCompilerFilesParamFlagConflict = 'Parameter "%s" may not be used when the "%s" flag is used';
+  SCompilerFilesParamFlagConflictSameSource = 'Parameter "%s" and the "%s" flag cannot both be used on a single source file';
   SCompilerFilesExcludeTooLong = 'Parameter "Excludes" contains a pattern that is too long';
   SCompilerFilesExcludeTooLong = 'Parameter "Excludes" contains a pattern that is too long';
   SCompilerFilesUnsafeFile = 'Unsafe file detected: %s.' + SNewLine2 +
   SCompilerFilesUnsafeFile = 'Unsafe file detected: %s.' + SNewLine2 +
     'See the "Unsafe Files" topic in the help file for more information';
     'See the "Unsafe Files" topic in the help file for more information';
@@ -312,7 +313,7 @@ const
     'documentation in the help file for details.';
     'documentation in the help file for details.';
   SCompilerFilesISSigVerifyMissingISSigKeys = 'Flag "issigverify" may not be used when the "ISSigKeys" section doesn''t exist or is empty.';
   SCompilerFilesISSigVerifyMissingISSigKeys = 'Flag "issigverify" may not be used when the "ISSigKeys" section doesn''t exist or is empty.';
   SCompilerFilesISSigAllowedKeysMissingISSigVerify = 'Flag "issigverify" must be used when the "ISSigAllowedKeys" parameter is used.';
   SCompilerFilesISSigAllowedKeysMissingISSigVerify = 'Flag "issigverify" must be used when the "ISSigAllowedKeys" parameter is used.';
-  SCompilerFilesISSigAllowedKeysConflict = 'Parameter "ISSigAllowedKeys" cannot allow different keys on the same source file';
+  SCompilerFilesValueConflict = 'Parameter "%s" cannot allow different values on the same source file';
   SCompilerFilesUnkownISSigKeyNameOrGroupName = 'Parameter "%s" includes an unknown name or group name.';
   SCompilerFilesUnkownISSigKeyNameOrGroupName = 'Parameter "%s" includes an unknown name or group name.';
 
 
   { [Icons] }
   { [Icons] }

+ 63 - 28
Projects/Src/Compiler.SetupCompiler.pas

@@ -256,7 +256,7 @@ type
     procedure WriteCompiledCodeDebugInfo(const CompiledCodeDebugInfo: AnsiString);
     procedure WriteCompiledCodeDebugInfo(const CompiledCodeDebugInfo: AnsiString);
     function CreateMemoryStreamsFromFiles(const ADirectiveName, AFiles: String): TObjectList<TCustomMemoryStream>;
     function CreateMemoryStreamsFromFiles(const ADirectiveName, AFiles: String): TObjectList<TCustomMemoryStream>;
     function CreateMemoryStreamsFromResources(const AResourceNamesPrefixes, AResourceNamesPostfixes: array of String): TObjectList<TCustomMemoryStream>;
     function CreateMemoryStreamsFromResources(const AResourceNamesPrefixes, AResourceNamesPostfixes: array of String): TObjectList<TCustomMemoryStream>;
-    procedure ISSigVerifyError(const AError: TISSigVerifySignatureError;
+    procedure VerificationError(const AError: TVerificationError;
       const AFilename: String; const ASigFilename: String = '');
       const AFilename: String; const ASigFilename: String = '');
   public
   public
     AppData: Longint;
     AppData: Longint;
@@ -324,9 +324,9 @@ type
   PFileLocationEntryExtraInfo = ^TFileLocationEntryExtraInfo;
   PFileLocationEntryExtraInfo = ^TFileLocationEntryExtraInfo;
   TFileLocationEntryExtraInfo = record
   TFileLocationEntryExtraInfo = record
     Flags: set of (floVersionInfoNotValid, floIsUninstExe, floApplyTouchDateTime,
     Flags: set of (floVersionInfoNotValid, floIsUninstExe, floApplyTouchDateTime,
-      floSolidBreak, floISSigVerify);
+      floSolidBreak);
     Sign: TFileLocationSign;
     Sign: TFileLocationSign;
-    ISSigAllowedKeys: AnsiString;
+    Verification: TSetupFileVerification;
     ISSigKeyUsedID: String;
     ISSigKeyUsedID: String;
   end;
   end;
 
 
@@ -4668,7 +4668,7 @@ procedure TSetupCompiler.EnumFilesProc(const Line: PChar; const Ext: Integer);
 type
 type
   TParam = (paFlags, paSource, paDestDir, paDestName, paCopyMode, paAttribs,
   TParam = (paFlags, paSource, paDestDir, paDestName, paCopyMode, paAttribs,
     paPermissions, paFontInstall, paExcludes, paExternalSize, paExtractArchivePassword,
     paPermissions, paFontInstall, paExcludes, paExternalSize, paExtractArchivePassword,
-    paStrongAssemblyName, paISSigAllowedKeys, paDownloadISSigSource, paDownloadUserName,
+    paStrongAssemblyName, paHash, paISSigAllowedKeys, paDownloadISSigSource, paDownloadUserName,
     paDownloadPassword, paComponents, paTasks, paLanguages, paCheck, paBeforeInstall,
     paDownloadPassword, paComponents, paTasks, paLanguages, paCheck, paBeforeInstall,
     paAfterInstall, paMinVersion, paOnlyBelowVersion);
     paAfterInstall, paMinVersion, paOnlyBelowVersion);
 const
 const
@@ -4683,6 +4683,7 @@ const
   ParamFilesExternalSize = 'ExternalSize';
   ParamFilesExternalSize = 'ExternalSize';
   ParamFilesExtractArchivePassword = 'ExtractArchivePassword';
   ParamFilesExtractArchivePassword = 'ExtractArchivePassword';
   ParamFilesStrongAssemblyName = 'StrongAssemblyName';
   ParamFilesStrongAssemblyName = 'StrongAssemblyName';
+  ParamFilesHash = 'Hash';
   ParamFilesISSigAllowedKeys = 'ISSigAllowedKeys';
   ParamFilesISSigAllowedKeys = 'ISSigAllowedKeys';
   ParamFilesDownloadISSigSource = 'DownloadISSigSource';
   ParamFilesDownloadISSigSource = 'DownloadISSigSource';
   ParamFilesDownloadUserName = 'DownloadUserName';
   ParamFilesDownloadUserName = 'DownloadUserName';
@@ -4700,6 +4701,7 @@ const
     (Name: ParamFilesExternalSize; Flags: []),
     (Name: ParamFilesExternalSize; Flags: []),
     (Name: ParamFilesExtractArchivePassword; Flags: []),
     (Name: ParamFilesExtractArchivePassword; Flags: []),
     (Name: ParamFilesStrongAssemblyName; Flags: [piNoEmpty]),
     (Name: ParamFilesStrongAssemblyName; Flags: [piNoEmpty]),
+    (Name: ParamFilesHash; Flags: [piNoEmpty]),
     (Name: ParamFilesISSigAllowedKeys; Flags: [piNoEmpty]),
     (Name: ParamFilesISSigAllowedKeys; Flags: [piNoEmpty]),
     (Name: ParamFilesDownloadISSigSource; Flags: []),
     (Name: ParamFilesDownloadISSigSource; Flags: []),
     (Name: ParamFilesDownloadUserName; Flags: [piNoEmpty]),
     (Name: ParamFilesDownloadUserName; Flags: [piNoEmpty]),
@@ -4908,6 +4910,15 @@ type
       Sign := NewSign;
       Sign := NewSign;
   end;
   end;
 
 
+  procedure ApplyNewVerificationType(var VerificationType: TSetupFileVerificationType;
+    const NewVerificationType: TSetupFileVerificationType; const ErrorMessage: String);
+  begin
+    if not (VerificationType in [fvNone, NewVerificationType]) then
+       AbortCompileFmt(ErrorMessage, ['Hash', 'issigverify'])
+    else
+      VerificationType := NewVerificationType;
+  end;
+
   procedure ProcessFileList(const FileListBaseDir: String; FileList: TList);
   procedure ProcessFileList(const FileListBaseDir: String; FileList: TList);
   var
   var
     FileListRec: PFileListRec;
     FileListRec: PFileListRec;
@@ -4982,13 +4993,23 @@ type
               to compressing the first one }
               to compressing the first one }
             SolidBreak := False;
             SolidBreak := False;
           end;
           end;
-          NewFileLocationEntryExtraInfo^.ISSigAllowedKeys := NewFileEntry^.ISSigAllowedKeys;
-        end else if NewFileLocationEntryExtraInfo^.ISSigAllowedKeys <> NewFileEntry^.ISSigAllowedKeys then
-          AbortCompile(SCompilerFilesISSigAllowedKeysConflict);
+          NewFileLocationEntryExtraInfo^.Verification.Typ := fvNone; { Correct value set below }
+          NewFileLocationEntryExtraInfo^.Verification.Hash := NewFileEntry^.Verification.Hash;
+          NewFileLocationEntryExtraInfo^.Verification.ISSigAllowedKeys := NewFileEntry^.Verification.ISSigAllowedKeys;
+        end else begin
+          { Verification.Typ changes checked below }
+          if (NewFileLocationEntryExtraInfo^.Verification.Typ = fvHash) and
+             (NewFileEntry^.Verification.Typ = fvHash) and
+             not CompareMem(@NewFileLocationEntryExtraInfo^.Verification.Hash[0],
+               @NewFileEntry^.Verification.Hash[0], SizeOf(TSHA256Digest)) then
+            AbortCompileFmt(SCompilerFilesValueConflict, ['Hash']);
+          if (NewFileLocationEntryExtraInfo^.Verification.Typ = fvISSig) and
+             (NewFileEntry^.Verification.Typ = fvISSig) and
+             (NewFileLocationEntryExtraInfo^.Verification.ISSigAllowedKeys <> NewFileEntry^.Verification.ISSigAllowedKeys) then
+            AbortCompileFmt(SCompilerFilesValueConflict, ['ISSigAllowedKeys']);
+        end;
         if Touch then
         if Touch then
           Include(NewFileLocationEntryExtraInfo^.Flags, floApplyTouchDateTime);
           Include(NewFileLocationEntryExtraInfo^.Flags, floApplyTouchDateTime);
-        if foISSigVerify in NewFileEntry^.Options then
-          Include(NewFileLocationEntryExtraInfo^.Flags, floISSigVerify);
         { Note: "nocompression"/"noencryption" on one file makes all merged
         { Note: "nocompression"/"noencryption" on one file makes all merged
           copies uncompressed/unencrypted too }
           copies uncompressed/unencrypted too }
         if NoCompression then
         if NoCompression then
@@ -4996,7 +5017,10 @@ type
         if NoEncryption then
         if NoEncryption then
           Exclude(NewFileLocationEntry^.Flags, floChunkEncrypted);
           Exclude(NewFileLocationEntry^.Flags, floChunkEncrypted);
         if Sign <> fsNoSetting then
         if Sign <> fsNoSetting then
-          ApplyNewSign(NewFileLocationEntryExtraInfo.Sign, Sign, SCompilerParamErrorBadCombo3);
+          ApplyNewSign(NewFileLocationEntryExtraInfo.Sign, Sign, SCompilerParamErrorBadCombo2SameSource);
+        if NewFileEntry^.Verification.Typ <> fvNone  then
+          ApplyNewVerificationType(NewFileLocationEntryExtraInfo.Verification.Typ, NewFileEntry^.Verification.Typ,
+            SCompilerFilesParamFlagConflictSameSource);
       end
       end
       else begin
       else begin
         NewFileEntry^.SourceFilename := SourceFile;
         NewFileEntry^.SourceFilename := SourceFile;
@@ -5263,7 +5287,7 @@ begin
                    38: ApplyNewSign(Sign, fsYes, SCompilerParamErrorBadCombo2);
                    38: ApplyNewSign(Sign, fsYes, SCompilerParamErrorBadCombo2);
                    39: ApplyNewSign(Sign, fsOnce, SCompilerParamErrorBadCombo2);
                    39: ApplyNewSign(Sign, fsOnce, SCompilerParamErrorBadCombo2);
                    40: ApplyNewSign(Sign, fsCheck, SCompilerParamErrorBadCombo2);
                    40: ApplyNewSign(Sign, fsCheck, SCompilerParamErrorBadCombo2);
-                   41: Include(Options, foISSigVerify);
+                   41: ApplyNewVerificationType(Verification.Typ, fvISSig, SCompilerFilesParamFlagConflict);
                    42: Include(Options, foDownload);
                    42: Include(Options, foDownload);
                    43: Include(Options, foExtractArchive);
                    43: Include(Options, foExtractArchive);
                  end;
                  end;
@@ -5345,7 +5369,7 @@ begin
                { ExternalSize }
                { ExternalSize }
                if Values[paExternalSize].Found then begin
                if Values[paExternalSize].Found then begin
                  if not ExternalFile then
                  if not ExternalFile then
-                   AbortCompile(SCompilerFilesCantHaveNonExternalExternalSize);
+                   AbortCompileFmt(SCompilerFilesParamRequiresFlag, ['ExternalSize', 'external']);
                  if not StrToInteger64(Values[paExternalSize].Data, ExternalSize) then
                  if not StrToInteger64(Values[paExternalSize].Data, ExternalSize) then
                    AbortCompileParamError(SCompilerParamInvalid2, ParamFilesExternalSize);
                    AbortCompileParamError(SCompilerParamInvalid2, ParamFilesExternalSize);
                  Include(Options, foExternalSizePreset);
                  Include(Options, foExternalSizePreset);
@@ -5363,6 +5387,12 @@ begin
                { ExtractArchivePassword }
                { ExtractArchivePassword }
                ExtractArchivePassword := Values[paExtractArchivePassword].Data;
                ExtractArchivePassword := Values[paExtractArchivePassword].Data;
 
 
+               { Hash }
+               if Values[paHash].Found then begin
+                 ApplyNewVerificationType(Verification.Typ, fvHash, SCompilerFilesParamFlagConflict);
+                 Verification.Hash := SHA256DigestFromString(Values[paHash].Data);
+               end;
+
                { ISSigAllowedKeys }
                { ISSigAllowedKeys }
                var S := Values[paISSigAllowedKeys].Data;
                var S := Values[paISSigAllowedKeys].Data;
                while True do begin
                while True do begin
@@ -5374,7 +5404,7 @@ begin
                    var ISSigKeyEntryExtraInfo := PISSigKeyEntryExtraInfo(ISSigKeyEntryExtraInfos[KeyIndex]);
                    var ISSigKeyEntryExtraInfo := PISSigKeyEntryExtraInfo(ISSigKeyEntryExtraInfos[KeyIndex]);
                    if SameText(ISSigKeyEntryExtraInfo.Name, KeyNameOrGroupName) or
                    if SameText(ISSigKeyEntryExtraInfo.Name, KeyNameOrGroupName) or
                       ISSigKeyEntryExtraInfo.HasGroupName(KeyNameOrGroupName) then begin
                       ISSigKeyEntryExtraInfo.HasGroupName(KeyNameOrGroupName) then begin
-                     SetISSigAllowedKey(ISSigAllowedKeys, KeyIndex);
+                     SetISSigAllowedKey(Verification.ISSigAllowedKeys, KeyIndex);
                      FoundKey := True;
                      FoundKey := True;
                    end;
                    end;
                  end;
                  end;
@@ -5478,13 +5508,16 @@ begin
         if (foIgnoreVersion in Options) and (foReplaceSameVersionIfContentsDiffer in Options) then
         if (foIgnoreVersion in Options) and (foReplaceSameVersionIfContentsDiffer in Options) then
           AbortCompileFmt(SCompilerParamErrorBadCombo2, ['Flags', 'ignoreversion', 'replacesameversion']);
           AbortCompileFmt(SCompilerParamErrorBadCombo2, ['Flags', 'ignoreversion', 'replacesameversion']);
 
 
-        if (ISSigKeyEntries.Count = 0) and (foISSigVerify in Options) then
+        if (ISSigKeyEntries.Count = 0) and (Verification.Typ = fvISSig) then
           AbortCompile(SCompilerFilesISSigVerifyMissingISSigKeys);
           AbortCompile(SCompilerFilesISSigVerifyMissingISSigKeys);
-        if (ISSigAllowedKeys <> '') and not (foISSigVerify in Options) then
+        if (Verification.ISSigAllowedKeys <> '') and (Verification.Typ <> fvISSig) then
           AbortCompile(SCompilerFilesISSigAllowedKeysMissingISSigVerify);
           AbortCompile(SCompilerFilesISSigAllowedKeysMissingISSigVerify);
 
 
         if Sign in [fsYes, fsOnce] then begin
         if Sign in [fsYes, fsOnce] then begin
-          if foISSigVerify in Options then
+          if Verification.Typ = fvHash then
+            AbortCompileFmt(SCompilerFilesParamFlagConflict,
+              [ParamCommonFlags, 'Hash', SignFlags[Sign]]);
+          if Verification.Typ = fvISSig then
             AbortCompileFmt(SCompilerParamErrorBadCombo2,
             AbortCompileFmt(SCompilerParamErrorBadCombo2,
               [ParamCommonFlags, SignFlags[Sign], 'issigverify']);
               [ParamCommonFlags, SignFlags[Sign], 'issigverify']);
           if SignTools.Count = 0 then
           if SignTools.Count = 0 then
@@ -6643,10 +6676,10 @@ begin
   end;
   end;
 end;
 end;
 
 
-procedure TSetupCompiler.ISSigVerifyError(const AError: TISSigVerifySignatureError;
+procedure TSetupCompiler.VerificationError(const AError: TVerificationError;
   const AFilename, ASigFilename: String);
   const AFilename, ASigFilename: String);
 const
 const
-  Messages: array[TISSigVerifySignatureError] of String =
+  Messages: array[TVerificationError] of String =
     (SCompilerVerificationSignatureDoesntExist, SCompilerVerificationSignatureMalformed,
     (SCompilerVerificationSignatureDoesntExist, SCompilerVerificationSignatureMalformed,
      SCompilerVerificationKeyNotFound, SCompilerVerificationSignatureBad,
      SCompilerVerificationKeyNotFound, SCompilerVerificationSignatureBad,
      SCompilerVerificationFileSizeIncorrect, SCompilerVerificationFileHashIncorrect);
      SCompilerVerificationFileSizeIncorrect, SCompilerVerificationFileHashIncorrect);
@@ -7149,26 +7182,28 @@ var
           fdOpenExisting, faRead, fsRead);
           fdOpenExisting, faRead, fsRead);
         try
         try
           var ExpectedFileHash: TSHA256Digest;
           var ExpectedFileHash: TSHA256Digest;
-          if floISSigVerify in FLExtraInfo.Flags then begin
+          if FLExtraInfo.Verification.Typ = fvHash then
+            ExpectedFileHash := FLExtraInfo.Verification.Hash
+          else if FLExtraInfo.Verification.Typ = fvISSig then begin
             { See Setup.Install's CopySourceFileToDestFile for similar code }
             { See Setup.Install's CopySourceFileToDestFile for similar code }
             if Length(ISSigAvailableKeys) = 0 then { shouldn't fail: flag stripped already }
             if Length(ISSigAvailableKeys) = 0 then { shouldn't fail: flag stripped already }
               AbortCompileFmt(SCompilerCompressInternalError, ['Length(ISSigAvailableKeys) = 0']);
               AbortCompileFmt(SCompilerCompressInternalError, ['Length(ISSigAvailableKeys) = 0']);
             var ExpectedFileSize: Int64;
             var ExpectedFileSize: Int64;
             if not ISSigVerifySignature(FileLocationEntryFilenames[I],
             if not ISSigVerifySignature(FileLocationEntryFilenames[I],
-              GetISSigAllowedKeys(ISSigAvailableKeys, FLExtraInfo.ISSigAllowedKeys),
+              GetISSigAllowedKeys(ISSigAvailableKeys, FLExtraInfo.Verification.ISSigAllowedKeys),
               ExpectedFileSize, ExpectedFileHash, FLExtraInfo.ISSigKeyUsedID,
               ExpectedFileSize, ExpectedFileHash, FLExtraInfo.ISSigKeyUsedID,
               nil,
               nil,
               procedure(const Filename, SigFilename: String)
               procedure(const Filename, SigFilename: String)
               begin
               begin
-                ISSigVerifyError(vseSignatureMissing, Filename, SigFilename);
+                VerificationError(veSignatureMissing, Filename, SigFilename);
               end,
               end,
               procedure(const Filename, SigFilename: String; const VerifyResult: TISSigVerifySignatureResult)
               procedure(const Filename, SigFilename: String; const VerifyResult: TISSigVerifySignatureResult)
               begin
               begin
                 var VerifyResultAsString: String;
                 var VerifyResultAsString: String;
                 case VerifyResult of
                 case VerifyResult of
-                  vsrMalformed: ISSigVerifyError(vseSignatureMalformed, Filename, SigFilename);
-                  vsrBad: ISSigVerifyError(vseSignatureBad, Filename, SigFilename);
-                  vsrKeyNotFound: ISSigVerifyError(vseKeyNotFound, Filename, SigFilename);
+                  vsrMalformed: VerificationError(veSignatureMalformed, Filename, SigFilename);
+                  vsrBad: VerificationError(veSignatureBad, Filename, SigFilename);
+                  vsrKeyNotFound: VerificationError(veKeyNotFound, Filename, SigFilename);
                 else
                 else
                   AbortCompileFmt(SCompilerCompressInternalError, ['Unknown ISSigVerifySignature result'])
                   AbortCompileFmt(SCompilerCompressInternalError, ['Unknown ISSigVerifySignature result'])
                 end;
                 end;
@@ -7176,7 +7211,7 @@ var
             ) then
             ) then
               AbortCompileFmt(SCompilerCompressInternalError, ['Unexpected ISSigVerifySignature result']);
               AbortCompileFmt(SCompilerCompressInternalError, ['Unexpected ISSigVerifySignature result']);
             if Int64(SourceFile.Size) <> ExpectedFileSize then
             if Int64(SourceFile.Size) <> ExpectedFileSize then
-              ISSigVerifyError(vseFileSizeIncorrect, FileLocationEntryFilenames[I]);
+              VerificationError(veFileSizeIncorrect, FileLocationEntryFilenames[I]);
             { ExpectedFileHash checked below after compression }
             { ExpectedFileHash checked below after compression }
           end;
           end;
 
 
@@ -7226,10 +7261,10 @@ var
           CH.CompressFile(SourceFile, FL.OriginalSize,
           CH.CompressFile(SourceFile, FL.OriginalSize,
             floCallInstructionOptimized in FL.Flags, FL.SHA256Sum);
             floCallInstructionOptimized in FL.Flags, FL.SHA256Sum);
 
 
-          if floISSigVerify in FLExtraInfo.Flags then begin
+          if FLExtraInfo.Verification.Typ <> fvNone then begin
             if not SHA256DigestsEqual(FL.SHA256Sum, ExpectedFileHash) then
             if not SHA256DigestsEqual(FL.SHA256Sum, ExpectedFileHash) then
-              ISSigVerifyError(vseFileHashIncorrect, FileLocationEntryFilenames[I]);
-            AddStatus(SCompilerStatusFilesISSigVerified);
+              VerificationError(veFileHashIncorrect, FileLocationEntryFilenames[I]);
+            AddStatus(SCompilerStatusFilesVerified);
           end;
           end;
         finally
         finally
           SourceFile.Free;
           SourceFile.Free;

+ 1 - 1
Projects/Src/IDE.ScintStylerInnoSetup.pas

@@ -245,7 +245,7 @@ const
     'AfterInstall', 'Attribs', 'BeforeInstall', 'Check', 'Components', 'CopyMode',
     'AfterInstall', 'Attribs', 'BeforeInstall', 'Check', 'Components', 'CopyMode',
     'DestDir', 'DestName', 'DownloadISSigSource', 'DownloadPassword',
     'DestDir', 'DestName', 'DownloadISSigSource', 'DownloadPassword',
     'DownloadUserName', 'Excludes', 'ExternalSize', 'ExtractArchivePassword',
     'DownloadUserName', 'Excludes', 'ExternalSize', 'ExtractArchivePassword',
-    'Flags', 'FontInstall', 'ISSigAllowedKeys', 'Languages', 'MinVersion',
+    'Flags', 'FontInstall', 'Hash', 'ISSigAllowedKeys', 'Languages', 'MinVersion',
     'OnlyBelowVersion', 'Permissions', 'Source', 'StrongAssemblyName', 'Tasks'
     'OnlyBelowVersion', 'Permissions', 'Source', 'StrongAssemblyName', 'Tasks'
   ];
   ];
 
 

+ 86 - 76
Projects/Src/Setup.Install.pas

@@ -12,9 +12,11 @@ unit Setup.Install;
 interface
 interface
 
 
 uses
 uses
-  Classes, SHA256, Shared.FileClass, Shared.SetupTypes, Shared.Int64Em;
+  Classes, SHA256, Shared.FileClass, Shared.SetupTypes, Shared.Int64Em, Shared.Struct;
 
 
-procedure ISSigVerifyError(const AError: TISSigVerifySignatureError;
+function NoVerification: TSetupFileVerification;
+
+procedure VerificationError(const AError: TVerificationError;
   const ASigFilename: String = '');
   const ASigFilename: String = '');
 
 
 procedure DoISSigVerify(const SourceF: TFile; const SourceFS: TFileStream;
 procedure DoISSigVerify(const SourceF: TFile; const SourceFS: TFileStream;
@@ -31,13 +33,11 @@ type
 procedure ExtractTemporaryFile(const BaseName: String);
 procedure ExtractTemporaryFile(const BaseName: String);
 function ExtractTemporaryFiles(const Pattern: String): Integer;
 function ExtractTemporaryFiles(const Pattern: String): Integer;
 function DownloadFile(const Url, CustomUserName, CustomPassword: String;
 function DownloadFile(const Url, CustomUserName, CustomPassword: String;
-  const DestF: TFile; const ISSigVerify: Boolean; const ISSigAllowedKeys: AnsiString;
-  const ISSigSourceFilename: String;
+  const DestF: TFile; [ref] const Verification: TSetupFileVerification; const ISSigSourceFilename: String;
   const OnSimpleDownloadProgress: TOnSimpleDownloadProgress;
   const OnSimpleDownloadProgress: TOnSimpleDownloadProgress;
   const OnSimpleDownloadProgressParam: Integer64): Int64;
   const OnSimpleDownloadProgressParam: Integer64): Int64;
-function DownloadTemporaryFile(const Url, BaseName, RequiredSHA256OfFile: String;
-  const ISSigVerify: Boolean; const ISSigAllowedKeys: AnsiString;
-  const OnDownloadProgress: TOnDownloadProgress): Int64;
+function DownloadTemporaryFile(const Url, BaseName: String;
+  [ref] const Verification: TSetupFileVerification; const OnDownloadProgress: TOnDownloadProgress): Int64;
 function DownloadTemporaryFileSize(const Url: String): Int64;
 function DownloadTemporaryFileSize(const Url: String): Int64;
 function DownloadTemporaryFileDate(const Url: String): String;
 function DownloadTemporaryFileDate(const Url: String): String;
 procedure SetDownloadTemporaryFileCredentials(const User, Pass: String);
 procedure SetDownloadTemporaryFileCredentials(const User, Pass: String);
@@ -46,7 +46,7 @@ function GetISSigUrl(const Url, ISSigUrl: String): String;
 implementation
 implementation
 
 
 uses
 uses
-  Windows, SysUtils, Messages, Forms, ShlObj, Shared.Struct, Setup.UninstallLog,
+  Windows, SysUtils, Messages, Forms, ShlObj, Setup.UninstallLog,
   SetupLdrAndSetup.InstFunc, Setup.InstFunc, Setup.InstFunc.Ole, Setup.SecurityFunc, SetupLdrAndSetup.Messages,
   SetupLdrAndSetup.InstFunc, Setup.InstFunc, Setup.InstFunc.Ole, Setup.SecurityFunc, SetupLdrAndSetup.Messages,
   Setup.MainFunc, Setup.LoggingFunc, Setup.FileExtractor,
   Setup.MainFunc, Setup.LoggingFunc, Setup.FileExtractor,
   Compression.Base, PathFunc, ISSigFunc, Shared.CommonFunc.Vcl, Compression.SevenZipDLLDecoder,
   Compression.Base, PathFunc, ISSigFunc, Shared.CommonFunc.Vcl, Compression.SevenZipDLLDecoder,
@@ -287,18 +287,24 @@ begin
   end;
   end;
 end;
 end;
 
 
-procedure ISSigVerifyError(const AError: TISSigVerifySignatureError;
+function NoVerification: TSetupFileVerification;
+begin
+  Result := Default(TSetupFileVerification);
+  Result.Typ := fvNone;
+end;
+
+procedure VerificationError(const AError: TVerificationError;
   const ASigFilename: String);
   const ASigFilename: String);
 const
 const
-  LogMessages: array[TISSigVerifySignatureError] of String =
+  LogMessages: array[TVerificationError] of String =
     ('Signature file does not exist', 'Signature is malformed', 'No matching key found',
     ('Signature file does not exist', 'Signature is malformed', 'No matching key found',
      'Signature is bad', 'File size is incorrect', 'File hash is incorrect');
      'Signature is bad', 'File size is incorrect', 'File hash is incorrect');
-  SetupMessageIDs: array[TISSigVerifySignatureError] of TSetupMessageID =
+  SetupMessageIDs: array[TVerificationError] of TSetupMessageID =
     (msgVerificationSignatureDoesntExist, msgVerificationSignatureInvalid, msgVerificationKeyNotFound,
     (msgVerificationSignatureDoesntExist, msgVerificationSignatureInvalid, msgVerificationKeyNotFound,
      msgVerificationSignatureInvalid, msgVerificationFileSizeIncorrect, msgVerificationFileHashIncorrect);
      msgVerificationSignatureInvalid, msgVerificationFileSizeIncorrect, msgVerificationFileHashIncorrect);
 begin
 begin
   { Also see Compiler.SetupCompiler for a similar function }
   { Also see Compiler.SetupCompiler for a similar function }
-  Log('ISSig verification error: ' + AddPeriod(LogMessages[AError]));
+  Log('Verification error: ' + AddPeriod(LogMessages[AError]));
   raise Exception.Create(FmtSetupMessage1(msgSourceVerificationFailed,
   raise Exception.Create(FmtSetupMessage1(msgSourceVerificationFailed,
     FmtSetupMessage1(SetupMessageIDs[AError], PathExtractName(ASigFilename)))); { Not all messages actually have a %1 parameter but that's OK }
     FmtSetupMessage1(SetupMessageIDs[AError], PathExtractName(ASigFilename)))); { Not all messages actually have a %1 parameter but that's OK }
 end;
 end;
@@ -319,14 +325,14 @@ begin
     nil,
     nil,
     procedure(const Filename, SigFilename: String)
     procedure(const Filename, SigFilename: String)
     begin
     begin
-      ISSigVerifyError(vseSignatureMissing, SigFilename);
+      VerificationError(veSignatureMissing, SigFilename);
     end,
     end,
     procedure(const Filename, SigFilename: String; const VerifyResult: TISSigVerifySignatureResult)
     procedure(const Filename, SigFilename: String; const VerifyResult: TISSigVerifySignatureResult)
     begin
     begin
       case VerifyResult of
       case VerifyResult of
-        vsrMalformed:  ISSigVerifyError(vseSignatureMalformed, SigFilename);
-        vsrBad: ISSigVerifyError(vseSignatureBad, SigFilename);
-        vsrKeyNotFound: ISSigVerifyError(vseKeyNotFound, SigFilename);
+        vsrMalformed:  VerificationError(veSignatureMalformed, SigFilename);
+        vsrBad: VerificationError(veSignatureBad, SigFilename);
+        vsrKeyNotFound: VerificationError(veKeyNotFound, SigFilename);
       else
       else
         InternalError('Unknown ISSigVerifySignature result');
         InternalError('Unknown ISSigVerifySignature result');
       end;
       end;
@@ -339,15 +345,15 @@ begin
   else
   else
     FileSize := SourceFS.Size;
     FileSize := SourceFS.Size;
   if FileSize <> ExpectedFileSize then
   if FileSize <> ExpectedFileSize then
-    ISSigVerifyError(vseFileSizeIncorrect);
+    VerificationError(veFileSizeIncorrect);
   { Caller must check ExpectedFileHash }
   { Caller must check ExpectedFileHash }
 end;
 end;
 
 
 const
 const
-  ISSigVerificationSuccessfulLogMessage = 'ISSig verification successful.';
+  VerificationSuccessfulLogMessage = 'Verification successful.';
 
 
 procedure CopySourceFileToDestFile(const SourceF, DestF: TFile;
 procedure CopySourceFileToDestFile(const SourceF, DestF: TFile;
-  const ISSigVerify: Boolean; const ISSigAllowedKeys: AnsiString; const ISSigSourceFilename: String;
+  [ref] const Verification: TSetupFileVerification; const ISSigSourceFilename: String;
   const AExpectedSize: Integer64);
   const AExpectedSize: Integer64);
 { Copies all bytes from SourceF to DestF, incrementing process meter as it
 { Copies all bytes from SourceF to DestF, incrementing process meter as it
   goes. Assumes file pointers of both are 0. }
   goes. Assumes file pointers of both are 0. }
@@ -358,8 +364,11 @@ var
   Context: TSHA256Context;
   Context: TSHA256Context;
 begin
 begin
   var ExpectedFileHash: TSHA256Digest;
   var ExpectedFileHash: TSHA256Digest;
-  if ISSigVerify then begin
-    DoISSigVerify(SourceF, nil, ISSigSourceFilename, ISSigAllowedKeys, ExpectedFileHash);
+  if Verification.Typ <> fvNone then begin
+    if Verification.Typ = fvHash then
+      ExpectedFileHash := Verification.Hash
+    else
+      DoISSigVerify(SourceF, nil, ISSigSourceFilename, Verification.ISSigAllowedKeys, ExpectedFileHash);
     { ExpectedFileHash checked below after copy }
     { ExpectedFileHash checked below after copy }
     SHA256Init(Context);
     SHA256Init(Context);
   end;
   end;
@@ -385,16 +394,16 @@ begin
     DestF.WriteBuffer(Buf, BufSize);
     DestF.WriteBuffer(Buf, BufSize);
     Dec64(BytesLeft, BufSize);
     Dec64(BytesLeft, BufSize);
 
 
-    if ISSigVerify then
+    if Verification.Typ <> fvNone then
       SHA256Update(Context, Buf, BufSize);
       SHA256Update(Context, Buf, BufSize);
 
 
     ExternalProgressProc64(To64(BufSize), MaxProgress);
     ExternalProgressProc64(To64(BufSize), MaxProgress);
   end;
   end;
 
 
-  if ISSigVerify then begin
+  if Verification.Typ <> fvNone then begin
     if not SHA256DigestsEqual(SHA256Final(Context), ExpectedFileHash) then
     if not SHA256DigestsEqual(SHA256Final(Context), ExpectedFileHash) then
-      ISSigVerifyError(vseFileHashIncorrect);
-    Log(ISSigVerificationSuccessfulLogMessage);
+      VerificationError(veFileHashIncorrect);
+    Log(VerificationSuccessfulLogMessage);
   end;
   end;
 
 
   { In case the source file was shorter than we thought it was, bump the
   { In case the source file was shorter than we thought it was, bump the
@@ -1196,7 +1205,6 @@ var
     CurFileVersionInfoValid: Boolean;
     CurFileVersionInfoValid: Boolean;
     CurFileVersionInfo, ExistingVersionInfo: TFileVersionNumbers;
     CurFileVersionInfo, ExistingVersionInfo: TFileVersionNumbers;
     CurFileDateValid, ExistingFileDateValid: Boolean;
     CurFileDateValid, ExistingFileDateValid: Boolean;
-    CurFileHash, ExistingFileHash: TSHA256Digest;
     IsProtectedFile, AllowTimeStampComparison: Boolean;
     IsProtectedFile, AllowTimeStampComparison: Boolean;
     DeleteFlags: Longint;
     DeleteFlags: Longint;
     CurFileDate, ExistingFileDate: TFileTime;
     CurFileDate, ExistingFileDate: TFileTime;
@@ -1386,7 +1394,9 @@ var
                    not(foOverwriteSameVersion in CurFile^.Options) then begin
                    not(foOverwriteSameVersion in CurFile^.Options) then begin
                   if foReplaceSameVersionIfContentsDiffer in CurFile^.Options then begin
                   if foReplaceSameVersionIfContentsDiffer in CurFile^.Options then begin
                     { Get the two files' SHA-256 hashes and compare them }
                     { Get the two files' SHA-256 hashes and compare them }
+                    var ExistingFileHash: TSHA256Digest;
                     if TryToGetSHA256OfFile(DisableFsRedir, DestFile, ExistingFileHash) then begin
                     if TryToGetSHA256OfFile(DisableFsRedir, DestFile, ExistingFileHash) then begin
+                      var CurFileHash: TSHA256Digest;
                       if Assigned(CurFileLocation) then
                       if Assigned(CurFileLocation) then
                         CurFileHash := CurFileLocation^.SHA256Sum
                         CurFileHash := CurFileLocation^.SHA256Sum
                       else begin
                       else begin
@@ -1585,18 +1595,18 @@ var
               const DownloadPassword = ExpandConst(CurFile^.DownloadPassword);
               const DownloadPassword = ExpandConst(CurFile^.DownloadPassword);
               var MaxProgress := CurProgress;
               var MaxProgress := CurProgress;
               Inc6464(MaxProgress, AExternalSize);
               Inc6464(MaxProgress, AExternalSize);
-              if foISSigVerify in CurFile^.Options then begin
+              if CurFile^.Verification.Typ = fvISSig then begin
                 const ISSigTempFile = TempFile + ISSigExt;
                 const ISSigTempFile = TempFile + ISSigExt;
                 const ISSigDestF = TFileRedir.Create(DisableFsRedir, ISSigTempFile, fdCreateAlways, faReadWrite, fsNone);
                 const ISSigDestF = TFileRedir.Create(DisableFsRedir, ISSigTempFile, fdCreateAlways, faReadWrite, fsNone);
                 try
                 try
                   { Download the .issig file }
                   { Download the .issig file }
                   const ISSigUrl = GetISSigUrl(SourceFile, ExpandConst(CurFile^.DownloadISSigSource));
                   const ISSigUrl = GetISSigUrl(SourceFile, ExpandConst(CurFile^.DownloadISSigSource));
                   DownloadFile(ISSigUrl, DownloadUserName, DownloadPassword,
                   DownloadFile(ISSigUrl, DownloadUserName, DownloadPassword,
-                    ISSigDestF, False, '', '', JustProcessEventsProc64, To64(0));
+                    ISSigDestF, NoVerification, '', JustProcessEventsProc64, To64(0));
                   FreeAndNil(ISSigDestF);
                   FreeAndNil(ISSigDestF);
                   { Download and verify the actual file }
                   { Download and verify the actual file }
                   DownloadFile(SourceFile, DownloadUserName, DownloadPassword,
                   DownloadFile(SourceFile, DownloadUserName, DownloadPassword,
-                    DestF, True, CurFile^.ISSigAllowedKeys, TempFile, ExternalProgressProc64, MaxProgress);
+                    DestF, CurFile^.Verification, TempFile, ExternalProgressProc64, MaxProgress);
                 finally
                 finally
                   ISSigDestF.Free;
                   ISSigDestF.Free;
                   { Delete the .issig file }
                   { Delete the .issig file }
@@ -1604,7 +1614,7 @@ var
                 end;
                 end;
               end else
               end else
                 DownloadFile(SourceFile, DownloadUserName, DownloadPassword,
                 DownloadFile(SourceFile, DownloadUserName, DownloadPassword,
-                  DestF, False, '', '', ExternalProgressProc64, MaxProgress);
+                  DestF, CurFile^.Verification, '', ExternalProgressProc64, MaxProgress);
             end
             end
             else begin
             else begin
               { Copy a duplicated non-external file, or an external file }
               { Copy a duplicated non-external file, or an external file }
@@ -1612,11 +1622,11 @@ var
               try
               try
                 LastOperation := SetupMessages[msgErrorCopying];
                 LastOperation := SetupMessages[msgErrorCopying];
                 if Assigned(CurFileLocation) then
                 if Assigned(CurFileLocation) then
-                  CopySourceFileToDestFile(SourceF, DestF, False,
-                    '', '', CurFileLocation^.OriginalSize)
+                  CopySourceFileToDestFile(SourceF, DestF, NoVerification,
+                    '', CurFileLocation^.OriginalSize)
                 else
                 else
-                  CopySourceFileToDestFile(SourceF, DestF, foISSigVerify in CurFile^.Options,
-                    CurFile^.ISSigAllowedKeys, SourceFile, AExternalSize);
+                  CopySourceFileToDestFile(SourceF, DestF, CurFile^.Verification,
+                    SourceFile, AExternalSize);
               finally
               finally
                 SourceF.Free;
                 SourceF.Free;
               end;
               end;
@@ -2023,25 +2033,29 @@ var
         InternalError('Unexpected custom DestName');
         InternalError('Unexpected custom DestName');
       const DestDir = ExpandConst(CurFile^.DestName);
       const DestDir = ExpandConst(CurFile^.DestName);
 
 
-      var ISSigVerifySourceF: TFile := nil;
+      var VerifySourceF: TFile := nil;
       try
       try
         var FindData: TWin32FindData;
         var FindData: TWin32FindData;
         var H: TArchiveFindHandle := INVALID_HANDLE_VALUE;
         var H: TArchiveFindHandle := INVALID_HANDLE_VALUE;
         var Failed: String;
         var Failed: String;
         repeat
         repeat
           try
           try
-            if foISSigVerify in CurFile^.Options then begin
-              if ISSigVerifySourceF = nil then
-                ISSigVerifySourceF := TFileRedir.Create(DisableFsRedir, ArchiveFilename, fdOpenExisting, faRead, fsRead);
+            if CurFile^.Verification.Typ <> fvNone then begin
+              if VerifySourceF = nil then
+                VerifySourceF := TFileRedir.Create(DisableFsRedir, ArchiveFilename, fdOpenExisting, faRead, fsRead);
               var ExpectedFileHash: TSHA256Digest;
               var ExpectedFileHash: TSHA256Digest;
-              DoISSigVerify(ISSigVerifySourceF, nil, ArchiveFilename, CurFile^.ISSigAllowedKeys,
+              if CurFile^.Verification.Typ = fvHash then
+                ExpectedFileHash := CurFile^.Verification.Hash
+              else begin
+                DoISSigVerify(VerifySourceF, nil, ArchiveFilename, CurFile^.Verification.ISSigAllowedKeys,
                 ExpectedFileHash);
                 ExpectedFileHash);
+              end;
               { Can't get the SHA-256 while extracting so need to get and check it now }
               { Can't get the SHA-256 while extracting so need to get and check it now }
-              const ActualFileHash = GetSHA256OfFile(ISSigVerifySourceF);
+              const ActualFileHash = GetSHA256OfFile(VerifySourceF);
               if not SHA256DigestsEqual(ActualFileHash, ExpectedFileHash) then
               if not SHA256DigestsEqual(ActualFileHash, ExpectedFileHash) then
-                ISSigVerifyError(vseFileHashIncorrect);
-              Log(ISSigVerificationSuccessfulLogMessage);
-              { Keeping ISSigVerifySourceF open until extraction has completed }
+                VerificationError(veFileHashIncorrect);
+              Log(VerificationSuccessfulLogMessage);
+              { Keep VerifySourceF open until extraction has completed to prevent TOCTOU problem }
             end;
             end;
 
 
             H := ArchiveFindFirstFileRedir(DisableFsRedir, ArchiveFilename, DestDir,
             H := ArchiveFindFirstFileRedir(DisableFsRedir, ArchiveFilename, DestDir,
@@ -2094,7 +2108,7 @@ var
           Log('Successfully extracted the archive.');
           Log('Successfully extracted the archive.');
         end;
         end;
       finally
       finally
-        ISSigVerifySourceF.Free;
+        VerifySourceF.Free;
       end;
       end;
     end;
     end;
 
 
@@ -3799,8 +3813,7 @@ begin
 end;
 end;
 
 
 function DownloadFile(const Url, CustomUserName, CustomPassword: String;
 function DownloadFile(const Url, CustomUserName, CustomPassword: String;
-  const DestF: TFile; const ISSigVerify: Boolean; const ISSigAllowedKeys: AnsiString;
-  const ISSigSourceFilename: String;
+  const DestF: TFile; [ref] const Verification: TSetupFileVerification; const ISSigSourceFilename: String;
   const OnSimpleDownloadProgress: TOnSimpleDownloadProgress;
   const OnSimpleDownloadProgress: TOnSimpleDownloadProgress;
   const OnSimpleDownloadProgressParam: Integer64): Int64;
   const OnSimpleDownloadProgressParam: Integer64): Int64;
 var
 var
@@ -3855,14 +3868,17 @@ begin
       Result := HandleStream.Size;
       Result := HandleStream.Size;
       FreeAndNil(HandleStream);
       FreeAndNil(HandleStream);
 
 
-      { Check .issig if specified, otherwise check everything else we can check }
-      if ISSigVerify then begin
+      { Check verification if specified, otherwise check everything else we can check }
+      if Verification.Typ <> fvNone then begin
         var ExpectedFileHash: TSHA256Digest;
         var ExpectedFileHash: TSHA256Digest;
-        DoISSigVerify(DestF, nil, ISSigSourceFilename, ISSigAllowedKeys, ExpectedFileHash);
+        if Verification.Typ = fvHash then
+          ExpectedFileHash := Verification.Hash
+        else
+          DoISSigVerify(DestF, nil, ISSigSourceFilename, Verification.ISSigAllowedKeys, ExpectedFileHash);
         const FileHash = GetSHA256OfFile(DestF);
         const FileHash = GetSHA256OfFile(DestF);
         if not SHA256DigestsEqual(FileHash, ExpectedFileHash) then
         if not SHA256DigestsEqual(FileHash, ExpectedFileHash) then
-          ISSigVerifyError(vseFileHashIncorrect);
-        Log(ISSigVerificationSuccessfulLogMessage);
+          VerificationError(veFileHashIncorrect);
+        Log(VerificationSuccessfulLogMessage);
       end else begin
       end else begin
         if HTTPDataReceiver.ProgressMax > 0 then begin
         if HTTPDataReceiver.ProgressMax > 0 then begin
           if HTTPDataReceiver.Progress <> HTTPDataReceiver.ProgressMax then
           if HTTPDataReceiver.Progress <> HTTPDataReceiver.ProgressMax then
@@ -3879,9 +3895,8 @@ begin
   end;
   end;
 end;
 end;
 
 
-function DownloadTemporaryFile(const Url, BaseName, RequiredSHA256OfFile: String;
-  const ISSigVerify: Boolean; const ISSigAllowedKeys: AnsiString;
-  const OnDownloadProgress: TOnDownloadProgress): Int64;
+function DownloadTemporaryFile(const Url, BaseName: String;
+  [ref] const Verification: TSetupFileVerification; const OnDownloadProgress: TOnDownloadProgress): Int64;
 var
 var
   DestFile, TempFile: String;
   DestFile, TempFile: String;
   TempF: TFile;
   TempF: TFile;
@@ -3890,7 +3905,6 @@ var
   HTTPDataReceiver: THTTPDataReceiver;
   HTTPDataReceiver: THTTPDataReceiver;
   HTTPClient: THTTPClient;
   HTTPClient: THTTPClient;
   HTTPResponse: IHTTPResponse;
   HTTPResponse: IHTTPResponse;
-  SHA256OfFile: String;
   RetriesLeft: Integer;
   RetriesLeft: Integer;
   LastError: DWORD;
   LastError: DWORD;
   User, Pass, CleanUrl: String;
   User, Pass, CleanUrl: String;
@@ -3909,10 +3923,16 @@ begin
 
 
   { Prepare directory }
   { Prepare directory }
   if NewFileExists(DestFile) then begin
   if NewFileExists(DestFile) then begin
-    if ISSigVerify then begin
+    if Verification.Typ = fvHash then begin
+      if SHA256DigestsEqual(GetSHA256OfFile(False, DestFile), Verification.Hash) then begin
+        Log('  File already downloaded.');
+        Result := 0;
+        Exit;
+      end;
+    end else if Verification.Typ = fvISSig then begin
       var ExistingFileSize: Int64;
       var ExistingFileSize: Int64;
       var ExistingFileHash: TSHA256Digest;
       var ExistingFileHash: TSHA256Digest;
-      if ISSigVerifySignature(DestFile, GetISSigAllowedKeys(ISSigAvailableKeys, ISSigAllowedKeys),
+      if ISSigVerifySignature(DestFile, GetISSigAllowedKeys(ISSigAvailableKeys, Verification.ISSigAllowedKeys),
            ExistingFileSize, ExistingFileHash, nil, nil, nil) then begin
            ExistingFileSize, ExistingFileHash, nil, nil, nil) then begin
         const DestF = TFile.Create(DestFile, fdOpenExisting, faRead, fsReadWrite);
         const DestF = TFile.Create(DestFile, fdOpenExisting, faRead, fsReadWrite);
         try
         try
@@ -3926,11 +3946,6 @@ begin
           DestF.Free;
           DestF.Free;
         end;
         end;
       end;
       end;
-    end else if (RequiredSHA256OfFile <> '') and
-                SameText(RequiredSHA256OfFile, SHA256DigestToString(GetSHA256OfFile(False, DestFile))) then begin
-      Log('  File already downloaded.');
-      Result := 0;
-      Exit;
     end;
     end;
 
 
     SetFileAttributes(PChar(DestFile), GetFileAttributes(PChar(DestFile)) and not FILE_ATTRIBUTE_READONLY);
     SetFileAttributes(PChar(DestFile), GetFileAttributes(PChar(DestFile)) and not FILE_ATTRIBUTE_READONLY);
@@ -3991,26 +4006,21 @@ begin
       Result := HandleStream.Size;
       Result := HandleStream.Size;
       FreeAndNil(HandleStream);
       FreeAndNil(HandleStream);
 
 
-      { Check .issig or hash if specified, otherwise check everything else we can check }
-      if ISSigVerify then begin
+      { Check verification if specified, otherwise check everything else we can check }
+      if Verification.Typ <> fvNone then begin
         var ExpectedFileHash: TSHA256Digest;
         var ExpectedFileHash: TSHA256Digest;
-        DoISSigVerify(TempF, nil, DestFile, ISSigAllowedKeys, ExpectedFileHash);
+        if Verification.Typ = fvHash then
+          ExpectedFileHash := Verification.Hash
+        else
+          DoISSigVerify(TempF, nil, DestFile, Verification.ISSigAllowedKeys, ExpectedFileHash);
         FreeAndNil(TempF);
         FreeAndNil(TempF);
         const FileHash = GetSHA256OfFile(False, TempFile);
         const FileHash = GetSHA256OfFile(False, TempFile);
         if not SHA256DigestsEqual(FileHash, ExpectedFileHash) then
         if not SHA256DigestsEqual(FileHash, ExpectedFileHash) then
-          ISSigVerifyError(vseFileHashIncorrect);
-        Log(ISSigVerificationSuccessfulLogMessage);
+          VerificationError(veFileHashIncorrect);
+        Log(VerificationSuccessfulLogMessage);
       end else begin
       end else begin
         FreeAndNil(TempF);
         FreeAndNil(TempF);
-        if RequiredSHA256OfFile <> '' then begin
-          try
-            SHA256OfFile := SHA256DigestToString(GetSHA256OfFile(False, TempFile));
-          except on E: Exception do
-            raise Exception.Create(FmtSetupMessage(msgErrorFileHash1, [E.Message]));
-          end;
-          if not SameText(RequiredSHA256OfFile, SHA256OfFile) then
-            raise Exception.Create(FmtSetupMessage(msgErrorFileHash2, [RequiredSHA256OfFile, SHA256OfFile]));
-        end else if HTTPDataReceiver.ProgressMax > 0 then begin
+        if HTTPDataReceiver.ProgressMax > 0 then begin
           if HTTPDataReceiver.Progress <> HTTPDataReceiver.ProgressMax then
           if HTTPDataReceiver.Progress <> HTTPDataReceiver.ProgressMax then
             raise Exception.Create(FmtSetupMessage(msgErrorProgress, [IntToStr(HTTPDataReceiver.Progress), IntToStr(HTTPDataReceiver.ProgressMax)]))
             raise Exception.Create(FmtSetupMessage(msgErrorProgress, [IntToStr(HTTPDataReceiver.Progress), IntToStr(HTTPDataReceiver.ProgressMax)]))
           else if HTTPDataReceiver.ProgressMax <> Result then
           else if HTTPDataReceiver.ProgressMax <> Result then

+ 14 - 11
Projects/Src/Setup.ScriptDlg.pas

@@ -13,7 +13,7 @@ interface
 
 
 uses
 uses
   Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, StdCtrls, Contnrs, Generics.Collections,
   Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, StdCtrls, Contnrs, Generics.Collections,
-  Setup.WizardForm, Setup.Install, Compression.SevenZipDecoder,
+  Shared.Struct, Setup.WizardForm, Setup.Install, Compression.SevenZipDecoder,
   NewCheckListBox, NewStaticText, NewProgressBar, PasswordEdit, RichEditViewer,
   NewCheckListBox, NewStaticText, NewProgressBar, PasswordEdit, RichEditViewer,
   BidiCtrls, TaskbarProgressFunc;
   BidiCtrls, TaskbarProgressFunc;
 
 
@@ -173,9 +173,8 @@ type
   end;
   end;
 
 
   TDownloadFile = class
   TDownloadFile = class
-    Url, BaseName, RequiredSHA256OfFile, UserName, Password: String;
-    ISSigVerify: Boolean;
-    ISSigAllowedKeys: AnsiString;
+    Url, BaseName, UserName, Password: String;
+    Verification: TSetupFileVerification;
   end;
   end;
   TDownloadFiles = TObjectList<TDownloadFile>;
   TDownloadFiles = TObjectList<TDownloadFile>;
 
 
@@ -249,8 +248,8 @@ function ConvertAllowedKeysRuntimeIDsToISSigAllowedKeys(const AllowedKeysRuntime
 implementation
 implementation
 
 
 uses
 uses
-  StrUtils, ISSigFunc,
-  Shared.Struct, Shared.SetupTypes, Setup.MainFunc, Setup.SelectFolderForm,
+  StrUtils, ISSigFunc, SHA256,
+  Shared.SetupTypes, Setup.MainFunc, Setup.SelectFolderForm,
   SetupLdrAndSetup.Messages, Shared.SetupMessageIDs, PathFunc, Shared.CommonFunc.Vcl,
   SetupLdrAndSetup.Messages, Shared.SetupMessageIDs, PathFunc, Shared.CommonFunc.Vcl,
   Shared.CommonFunc, BrowseFunc, Setup.LoggingFunc, Setup.InstFunc,
   Shared.CommonFunc, BrowseFunc, Setup.LoggingFunc, Setup.InstFunc,
   Compression.SevenZipDLLDecoder;
   Compression.SevenZipDLLDecoder;
@@ -1063,11 +1062,16 @@ begin
   var F := TDownloadFile.Create;
   var F := TDownloadFile.Create;
   F.Url := Url;
   F.Url := Url;
   F.BaseName := BaseName;
   F.BaseName := BaseName;
-  F.RequiredSHA256OfFile := RequiredSHA256OfFile;
   F.UserName := UserName;
   F.UserName := UserName;
   F.Password := Password;
   F.Password := Password;
-  F.ISSigVerify := ISSigVerify;
-  F.ISSigAllowedKeys := ISSigAllowedKeys;
+  F.Verification := NoVerification;
+  if RequiredSHA256OfFile <> '' then begin
+    F.Verification.Typ := fvHash;
+    F.Verification.Hash := SHA256DigestFromString(RequiredSHA256OfFile)
+  end else if ISSigVerify then begin
+    F.Verification.Typ := fvISSig;
+    F.Verification.ISSigAllowedKeys := ISSigAllowedKeys
+  end;
   Result := FFiles.Add(F);
   Result := FFiles.Add(F);
 end;
 end;
 
 
@@ -1132,8 +1136,7 @@ begin
   for var F in FFiles do begin
   for var F in FFiles do begin
     { Don't need to set DownloadTemporaryFileOrExtractArchiveProcessMessages before downloading since we already process messages ourselves }
     { Don't need to set DownloadTemporaryFileOrExtractArchiveProcessMessages before downloading since we already process messages ourselves }
     SetDownloadTemporaryFileCredentials(F.UserName, F.Password);
     SetDownloadTemporaryFileCredentials(F.UserName, F.Password);
-    Result := Result + DownloadTemporaryFile(F.Url, F.BaseName, F.RequiredSHA256OfFile,
-      F.ISSigVerify, F.ISSigAllowedKeys, InternalOnDownloadProgress);
+    Result := Result + DownloadTemporaryFile(F.Url, F.BaseName, F.Verification, InternalOnDownloadProgress);
   end;
   end;
   SetDownloadTemporaryFileCredentials('', '');
   SetDownloadTemporaryFileCredentials('', '');
 end;
 end;

+ 12 - 3
Projects/Src/Setup.ScriptFunc.pas

@@ -822,10 +822,19 @@ var
         OnDownloadProgress := TOnDownloadProgress(Stack.GetProc(PStart-4, Caller));
         OnDownloadProgress := TOnDownloadProgress(Stack.GetProc(PStart-4, Caller));
       end;
       end;
 
 
+      var Verification := NoVerification;
+      if RequiredSHA256OfFile <> '' then begin
+        Verification.Typ := fvHash;
+        Verification.Hash := SHA256DigestFromString(RequiredSHA256OfFile)
+      end else if ISSigVerify then begin
+        Verification.Typ := fvISSig;
+        Verification.ISSigAllowedKeys := ISSigAllowedKeys
+      end;
+
       { Also see Setup.ScriptDlg TDownloadWizardPage.AddExWithISSigVerify }
       { Also see Setup.ScriptDlg TDownloadWizardPage.AddExWithISSigVerify }
       if ISSigVerify then
       if ISSigVerify then
-        DownloadTemporaryFile(GetISSigUrl(Url, ISSigUrl), BaseName + ISSigExt, '', False, '', OnDownloadProgress);
-      Stack.SetInt64(PStart, DownloadTemporaryFile(Url, BaseName, RequiredSHA256OfFile, ISSigVerify, ISSigAllowedKeys, OnDownloadProgress));
+        DownloadTemporaryFile(GetISSigUrl(Url, ISSigUrl), BaseName + ISSigExt, NoVerification, OnDownloadProgress);
+      Stack.SetInt64(PStart, DownloadTemporaryFile(Url, BaseName, Verification, OnDownloadProgress));
     end);
     end);
     RegisterScriptFunc('DownloadTemporaryFileSize', sfNoUninstall, procedure(const Caller: TPSExec; const OrgName: AnsiString; const Stack: TPSStack; const PStart: Cardinal)
     RegisterScriptFunc('DownloadTemporaryFileSize', sfNoUninstall, procedure(const Caller: TPSExec; const OrgName: AnsiString; const Stack: TPSStack; const PStart: Cardinal)
     begin
     begin
@@ -1864,7 +1873,7 @@ var
          { Couldn't get the SHA-256 while downloading so need to get and check it now }
          { Couldn't get the SHA-256 while downloading so need to get and check it now }
         const ActualFileHash = ISSigCalcStreamHash(F);
         const ActualFileHash = ISSigCalcStreamHash(F);
         if not SHA256DigestsEqual(ActualFileHash, ExpectedFileHash) then
         if not SHA256DigestsEqual(ActualFileHash, ExpectedFileHash) then
-          ISSigVerifyError(vseFileHashIncorrect);
+          VerificationError(veFileHashIncorrect);
       except
       except
         FreeAndNil(F);
         FreeAndNil(F);
         raise;
         raise;

+ 0 - 2
Projects/Src/Shared.SetupMessageIDs.pas

@@ -93,8 +93,6 @@ type
     msgErrorExtracting,
     msgErrorExtracting,
     msgErrorExtractionAborted,
     msgErrorExtractionAborted,
     msgErrorExtractionFailed,
     msgErrorExtractionFailed,
-    msgErrorFileHash1,
-    msgErrorFileHash2,
     msgErrorFileSize,
     msgErrorFileSize,
     msgErrorFunctionFailed,
     msgErrorFunctionFailed,
     msgErrorFunctionFailedNoCode,
     msgErrorFunctionFailedNoCode,

+ 2 - 2
Projects/Src/Shared.SetupTypes.pas

@@ -39,8 +39,8 @@ type
 
 
   TArrayOfECDSAKey = array of TECDSAKey;
   TArrayOfECDSAKey = array of TECDSAKey;
 
 
-  TISSigVerifySignatureError = (vseSignatureMissing, vseSignatureMalformed, vseKeyNotFound,
-    vseSignatureBad, vseFileSizeIncorrect, vseFileHashIncorrect);
+  TVerificationError = (veSignatureMissing, veSignatureMalformed, veKeyNotFound,
+    veSignatureBad, veFileSizeIncorrect, veFileHashIncorrect);
 
 
 const
 const
   crHand = 1;
   crHand = 1;

+ 8 - 2
Projects/Src/Shared.Struct.pas

@@ -231,11 +231,17 @@ const
   SetupFileEntryAnsiStrings = 1;
   SetupFileEntryAnsiStrings = 1;
 type
 type
   PSetupFileEntry = ^TSetupFileEntry;
   PSetupFileEntry = ^TSetupFileEntry;
+  TSetupFileVerificationType = (fvNone, fvHash, fvISSig);
+  TSetupFileVerification = packed record
+    ISSigAllowedKeys: AnsiString; { Must be first }
+    Hash: TSHA256Digest;
+    Typ: TSetupFileVerificationType;
+  end;
   TSetupFileEntry = packed record
   TSetupFileEntry = packed record
     SourceFilename, DestName, InstallFontName, StrongAssemblyName, Components,
     SourceFilename, DestName, InstallFontName, StrongAssemblyName, Components,
     Tasks, Languages, Check, AfterInstall, BeforeInstall, Excludes,
     Tasks, Languages, Check, AfterInstall, BeforeInstall, Excludes,
     DownloadISSigSource, DownloadUserName, DownloadPassword, ExtractArchivePassword: String;
     DownloadISSigSource, DownloadUserName, DownloadPassword, ExtractArchivePassword: String;
-    ISSigAllowedKeys: AnsiString;
+    Verification: TSetupFileVerification; { Must be first after strings }
     MinVersion, OnlyBelowVersion: TSetupVersionData;
     MinVersion, OnlyBelowVersion: TSetupVersionData;
     LocationEntry: Integer;
     LocationEntry: Integer;
     Attribs: Integer;
     Attribs: Integer;
@@ -251,7 +257,7 @@ type
       foRecurseSubDirsExternal, foReplaceSameVersionIfContentsDiffer,
       foRecurseSubDirsExternal, foReplaceSameVersionIfContentsDiffer,
       foDontVerifyChecksum, foUninsNoSharedFilePrompt, foCreateAllSubDirs,
       foDontVerifyChecksum, foUninsNoSharedFilePrompt, foCreateAllSubDirs,
       fo32Bit, fo64Bit, foExternalSizePreset, foSetNTFSCompression,
       fo32Bit, fo64Bit, foExternalSizePreset, foSetNTFSCompression,
-      foUnsetNTFSCompression, foGacInstall, foISSigVerify, foDownload,
+      foUnsetNTFSCompression, foGacInstall, foDownload,
       foExtractArchive);
       foExtractArchive);
     FileType: (ftUserFile, ftUninstExe);
     FileType: (ftUserFile, ftUninstExe);
   end;
   end;

+ 23 - 7
whatsnew.htm

@@ -137,12 +137,16 @@ Name: bosskey; KeyFile: "boss.ispublickey"</pre>
     <li>Note: The <tt>issigverify</tt> flag cannot be combined with the <tt>sign</tt> or <tt>signonce</tt> flags. Use <tt>signcheck</tt> instead.</li>
     <li>Note: The <tt>issigverify</tt> flag cannot be combined with the <tt>sign</tt> or <tt>signonce</tt> flags. Use <tt>signcheck</tt> instead.</li>
     <li>Example section:
     <li>Example section:
       <pre>[Files]
       <pre>[Files]
-Source: "MyProg.exe"; DestDir: "{app}"; Flags: issigverify; \
-  ISSigAllowedKeys: exesigner bosskey
-Source: "MyProg.chm"; DestDir: "{app}"; Flags: issigverify; \
-  ISSigAllowedKeys: docsigner bosskey
-Source: "{src}\Extra\*.chm"; DestDir: "{app}"; Flags: issigverify external; \
-  ISSigAllowedKeys: docsigner bosskey; Excludes: "*.issig"</pre>
+Source: "MyProg.exe"; DestDir: "{app}"; \
+  ISSigAllowedKeys: "exesigner bosskey"; Flags: issigverify 
+Source: "MyProg.chm"; DestDir: "{app}"; \
+  ISSigAllowedKeys: "docsigner bosskey"; Flags: issigverify
+Source: "{src}\Extra\*.chm"; DestDir: "{app}"; \
+  ISSigAllowedKeys: "docsigner bosskey"; Flags: issigverify external; \
+  Excludes: "*.issig"
+Source: "https://jrsoftware.org/download.php/is.exe?dontcount=1"; DestDir: "{app}"; \
+  ISSigAllowedKeys: "exesigner bosskey"; Flags: issigverify external download ignoreversion; \
+  DestName: "innosetup-latest.exe"; ExternalSize: 7_000_000</pre>
     </li>
     </li>
   </ul>
   </ul>
   </li>
   </li>
@@ -169,6 +173,13 @@ issigtool --key-file="MyKey.ispublickey" verify "MyProg.dll"</pre>
   <li>Other related changes:
   <li>Other related changes:
   <ul>
   <ul>
     <li>The compiler now verifies that precompiled files like <i>SetupLdr.e32</i> and <i>Setup.e32</i> remain unchanged before using them. Can be disabled using new [Setup] section directive <tt>VerifyPrecompiledFiles</tt>. Doing so is <i>not</i> recommended.</li>
     <li>The compiler now verifies that precompiled files like <i>SetupLdr.e32</i> and <i>Setup.e32</i> remain unchanged before using them. Can be disabled using new [Setup] section directive <tt>VerifyPrecompiledFiles</tt>. Doing so is <i>not</i> recommended.</li>
+    <li>Added new [Files] section parameter <tt>Hash</tt>. Instructs the compiler or Setup to do a simple SHA-256 hash check instead of a full signature verification, as an alternative to using the <tt>issigverify</tt> flag.<br/>
+        Example script:
+      <pre>[Files]
+Source: "https://jrsoftware.org/download.php/iscrypt.dll?dontcount=1"; DestName: "ISCrypt.dll"; DestDir: "{app}"; \
+  Hash: "2f6294f9aa09f59a574b5dcd33be54e16b39377984f3d5658cda44950fa0f8fc"; \
+  ExternalSize: 2560; Flags: external download ignoreversion</pre>
+    </li>
     <li>Pascal Scripting:
     <li>Pascal Scripting:
       <ul>
       <ul>
         <li>Added new <tt>ISSigVerify</tt> and <tt>DownloadTemporaryFileWithISSigVerify</tt> support functions.</li>
         <li>Added new <tt>ISSigVerify</tt> and <tt>DownloadTemporaryFileWithISSigVerify</tt> support functions.</li>
@@ -205,8 +216,13 @@ issigtool --key-file="MyKey.ispublickey" verify "MyProg.dll"</pre>
   <li>Minor tweaks.</li>
   <li>Minor tweaks.</li>
 </ul>
 </ul>
 
 
-<p>Some messages have been added and changed in this version: (<a href="https://github.com/jrsoftware/issrc/commit/f832ee26">View differences in Default.isl</a>.)</p>
+<p>Some messages have been removed and added in this version: (<a href="https://github.com/jrsoftware/issrc/commit/9224d13e">View differences in Default.isl</a>.)</p>
 <ul>
 <ul>
+  <li><b>Removed messages:</b>
+  <ul>
+    <li>ErrorFileHash1, ErrorFileHash2.</li>
+  </ul>
+  </li>
   <li><b>New messages:</b>
   <li><b>New messages:</b>
   <ul>
   <ul>
     <li>ArchiveIncorrectPassword, ArchiveIsCorrupted, ArchiveUnsupportedFormat.</li>
     <li>ArchiveIncorrectPassword, ArchiveIsCorrupted, ArchiveUnsupportedFormat.</li>