Search code examples
inno-setupdata-integrity

Integrity check of whole Inno Setup installer


We use Inno Setup for our installer. Recently a user reported the following error during installation:

enter image description here

An error occurred while trying to copy a file: the source file is corrupted

This was due to a setup file that was indeed corrupted somehow.

Ideally the setup EXE would have performed some kind of check upon initialization to see if the entire EXE was valid or not. But apparently it only did this on a file-by-file basis. Is it possible to get InnoSetup to do that?


I looked in Inno Setup documentation for keywords like 'check', 'hash', etc. but didn't see anything - perhaps I missed it.

Quite similar question (from about 10 years ago - though specifically asking about MD5): How to implement MD5 check into Inno Setup installer to get 'like NSIS integrity check'? . That question seemed to state that such a check should already be happening. So perhaps the issue is not whether or not the setup EXE is validated but when this information is used / shown to the user. Also the accepted answer seemed quite manual, ideally I'd like Inno to do this itself.

Similar question with a different error message: "Source file corrupted: SHA-1 hash mismatch" error from Inno Setup


Solution

  • Add a checksum to the installer and verify it when the installer is starting. A standard (and recommended) way to do that is to sign the installer using code signing certificate. It's a must anyway these days.

    Easy way to verify the signature is using PowerShell Get-AuthenticodeSignature. You need PowerShell 5.1 for that. It is bundled with Windows 10 Build 14393 (August 2016) and newer. The following code uses that (and skips the check on older versions of Windows).

    function InitializeSetup(): Boolean;
    var
      WindowsVersion: TWindowsVersion;
      S: string;
      ResultCode: Integer;
    begin
      Result := True;
    
      GetWindowsVersionEx(WindowsVersion);
      Log(Format('Windows build %d', [WindowsVersion.Build]));
      // TODO: Better would be to check PowerShell version
      if WindowsVersion.Build < 14393 then
      begin
        Log('Old version of Windows, skipping certificate check');
      end
        else
      begin
        S := ExpandConstant('{srcexe}');
        if (Pos('''', S) > 0) or (Pos('"', S) > 0) then
          RaiseException('Possible code injection');
    
        S := 'if ((Get-AuthenticodeSignature ''' + S + ''').Status -ne ''Valid'') ' +
             '{ exit 1 }';
        if ExecAsOriginalUser(
             'powershell', '-ExecutionPolicy Bypass -command "' + S + '"',
             '', SW_HIDE, ewWaitUntilTerminated, ResultCode) and
           (ResultCode = 0) then
        begin
          Log('Installer signature is valid');
        end
          else
        begin
          S := 'Installer signature is not valid. Are you sure you want to continue?';
          Result := (MsgBox(S, mbError, MB_YESNO) = IDYES);
        end;
      end;
    end;
    

    If you need to support older versions of Windows, you will have to use more complicated methods, like:

    See How to check if a file has a digital signature.