Sign the bundled PostgreSQL binaries in the Windows build (#10355)
The PostgreSQL and Kerberos utilities and libraries we ship in the Windows package are not signed, whilst the installer, the uninstaller and pgAdmin4.exe are. Nothing verifies an Authenticode signature when these run, so this is not a security fix, but it does make life easier for organisations running application allow-listing under WDAC or AppLocker, where publisher rules are simpler and more durable than path or hash based ones, and it cuts down on antivirus false positives. The signtool invocation moves into a :SIGN_FILES subroutine, and a new :SIGN_COMPONENTS step signs everything we build ourselves once :CREATE_RUNTIME_ENV has staged it. Names are matched through DIR /B so the optional libraries are skipped when absent, and the whole thing is a no-op when PGADMIN_WINDOWS_CSC is unset, so unsigned developer builds are unaffected. The Electron, Python and VC++ runtime components are deliberately left alone, being third party binaries whose own signatures ours would replace. Everything goes through a single signtool call, since the hardware token prompts for a PIN on the first signature of a session. The file digest moves from SHA-1 to SHA-256 for both the runtime and the installer. SHA-1 was never a deliberate choice: it dates from the move to the Certum token, at a point where the certificate in the machine store had no key provider link, which limits signing to SHA-1. The buildfarm now repairs that link with certutil when it provisions the host. The four optional PostgreSQL libraries were copied without checking the result; they now abort the build like the others do. PGADMIN_POSTGRES_DIR and PGADMIN_KRB5_DIR defaulted to an EDB PostgreSQL installation and an MIT Kerberos for Windows installation respectively, whereas both sets of binaries have come from winpgbuild for some time and the buildfarm points at C:\build64\postgresql and C:\build64\krb5. The four "does not exist" messages that report a missing prerequisite used !VAR!, but the SETLOCAL at the top of the file does not enable delayed expansion, so they printed the variable name rather than the path that was looked for. Finally, the acceptance test now verifies the signatures on everything :SIGN_COMPONENTS signs, walking the same list of names where the files land: none of them exist outside the installer until the installer has run, so that is the only place a published build can be checked. The expected publisher is taken from the installer itself rather than hard coded, so a component signed by somebody else is caught without the workflow needing an edit when the certificate is renewed.
This commit is contained in:
67
.github/workflows/acceptance-test.yml
vendored
67
.github/workflows/acceptance-test.yml
vendored
@@ -231,23 +231,64 @@ jobs:
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$root = 'C:\pgadmin4-test'
|
||||
|
||||
foreach ($f in @(
|
||||
"$root\runtime\pgAdmin4.exe",
|
||||
"$root\runtime\libpq.dll",
|
||||
"$root\runtime\pg_dump.exe",
|
||||
"$root\runtime\gssapi64.dll",
|
||||
"$root\python\python.exe",
|
||||
"$root\web\pgAdmin4.py")) {
|
||||
# The components that carry no signature of ours, and so are only
|
||||
# checked for presence. Everything we do sign is covered by the step
|
||||
# below, which checks that it is there as a side effect of verifying
|
||||
# it.
|
||||
foreach ($f in @("$root\python\python.exe", "$root\web\pgAdmin4.py")) {
|
||||
if (-not (Test-Path $f)) { throw "Missing from the installation: $f" }
|
||||
Write-Output "present: $f"
|
||||
}
|
||||
|
||||
# The executable users actually run. Verified here because it is the
|
||||
# only place it can be: it does not exist outside the installer until
|
||||
# the installer has run.
|
||||
$sig = Get-AuthenticodeSignature "$root\runtime\pgAdmin4.exe"
|
||||
Write-Output "pgAdmin4.exe signature: $($sig.Status) / $($sig.SignerCertificate.Subject)"
|
||||
if ($sig.Status -ne 'Valid') { throw "pgAdmin4.exe signature is $($sig.Status)" }
|
||||
# Everything Make.bat's :SIGN_COMPONENTS signs, verified where it lands.
|
||||
# None of it exists outside the installer until the installer has run, so
|
||||
# this is the only place a published build can be checked: the runtime
|
||||
# executable, and the PostgreSQL and Kerberos binaries we ship alongside
|
||||
# it. The Electron, Python and VC++ runtime components are deliberately
|
||||
# not signed by us and so are not listed here.
|
||||
- name: Check the component signatures
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$root = 'C:\pgadmin4-test'
|
||||
|
||||
# The publisher is taken from the installer rather than hard coded,
|
||||
# so that a component signed by somebody else is caught without this
|
||||
# needing an edit every time the certificate is renewed.
|
||||
$expected = (Get-AuthenticodeSignature installer.exe).SignerCertificate.Subject
|
||||
Write-Output "Expected publisher: $expected"
|
||||
|
||||
# winpgbuild does not always ship these four, and Make.bat copies them
|
||||
# only when they are present, so an empty match is not a failure.
|
||||
$optional = @('libintl-*.dll', 'libiconv-*.dll', 'liblz4.dll', 'libzstd.dll')
|
||||
|
||||
foreach ($pattern in @(
|
||||
'pgAdmin4.exe',
|
||||
'libpq.dll', 'libcrypto-*-x64.dll', 'libssl-*-x64.dll', 'zlib1.dll',
|
||||
'pg_dump.exe', 'pg_dumpall.exe', 'pg_restore.exe', 'psql.exe',
|
||||
'kinit.exe', 'krb5_64.dll', 'comerr64.dll', 'k5sprt64.dll',
|
||||
'gssapi64.dll', 'krbcc64.dll', 'ccapiserver.exe') + $optional) {
|
||||
|
||||
$files = @(Get-ChildItem -Path "$root\runtime\$pattern" -ErrorAction SilentlyContinue)
|
||||
if ($files.Count -eq 0) {
|
||||
if ($optional -contains $pattern) {
|
||||
Write-Output "absent, and optional: $pattern"
|
||||
continue
|
||||
}
|
||||
throw "Missing from the installation: $root\runtime\$pattern"
|
||||
}
|
||||
|
||||
foreach ($f in $files) {
|
||||
$sig = Get-AuthenticodeSignature $f.FullName
|
||||
Write-Output "$($f.Name): $($sig.Status) / $($sig.SignerCertificate.Subject)"
|
||||
if ($sig.Status -ne 'Valid') {
|
||||
throw "$($f.Name) signature is $($sig.Status)"
|
||||
}
|
||||
if ($sig.SignerCertificate.Subject -ne $expected) {
|
||||
throw "$($f.Name) was signed by $($sig.SignerCertificate.Subject)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# The macOS disk image as published, rather than the bundle as built. The
|
||||
# snapshot workflow checks the bundle it has just made; this checks what a
|
||||
|
||||
111
Make.bat
111
Make.bat
@@ -31,6 +31,7 @@ CALL :VALIDATE_ENVIRONMENT || EXIT /B 1
|
||||
CALL :CREATE_VIRTUAL_ENV || EXIT /B 1
|
||||
CALL :CREATE_PYTHON_ENV || EXIT /B 1
|
||||
CALL :CREATE_RUNTIME_ENV || EXIT /B 1
|
||||
CALL :SIGN_COMPONENTS || EXIT /B 1
|
||||
CALL :GENERATE_SBOM || EXIT /B 1
|
||||
CALL :CREATE_INSTALLER || EXIT /B 1
|
||||
CALL :VERIFY_SIGNATURE || EXIT /B 1
|
||||
@@ -121,8 +122,8 @@ REM Main build sequence Ends
|
||||
|
||||
:SET_ENVIRONMENT
|
||||
ECHO Configuring the environment...
|
||||
IF "%PGADMIN_KRB5_DIR%" == "" SET "PGADMIN_KRB5_DIR=C:\Program Files\MIT\Kerberos"
|
||||
IF "%PGADMIN_POSTGRES_DIR%" == "" SET "PGADMIN_POSTGRES_DIR=C:\Program Files\PostgreSQL\17"
|
||||
IF "%PGADMIN_KRB5_DIR%" == "" SET "PGADMIN_KRB5_DIR=C:\build64\krb5"
|
||||
IF "%PGADMIN_POSTGRES_DIR%" == "" SET "PGADMIN_POSTGRES_DIR=C:\build64\postgresql"
|
||||
IF "%PGADMIN_INNOTOOL_DIR%" == "" SET "PGADMIN_INNOTOOL_DIR=C:\Program Files (x86)\Inno Setup 6"
|
||||
IF "%PGADMIN_VCREDIST_DIR%" == "" SET "PGADMIN_VCREDIST_DIR=C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Redist\MSVC\14.40.33807"
|
||||
IF "%PGADMIN_VCREDIST_FILE%" == "" SET "PGADMIN_VCREDIST_FILE=vc_redist.x64.exe"
|
||||
@@ -177,26 +178,28 @@ REM Main build sequence Ends
|
||||
|
||||
ECHO Checking the environment...
|
||||
IF NOT EXIST "%PGADMIN_INNOTOOL_DIR%" (
|
||||
ECHO !PGADMIN_INNOTOOL_DIR! does not exist
|
||||
ECHO %PGADMIN_INNOTOOL_DIR% does not exist
|
||||
ECHO Please install InnoTool and set the PGADMIN_INNOTOOL_DIR environment variable.
|
||||
EXIT /B 1
|
||||
)
|
||||
|
||||
IF NOT EXIST "%PGADMIN_VCREDIST_DIR%" (
|
||||
ECHO !PGADMIN_VCREDIST_DIR! does not exist
|
||||
ECHO %PGADMIN_VCREDIST_DIR% does not exist
|
||||
ECHO Please install Microsoft Visual studio and set the PGADMIN_VCREDIST_DIR environment variable.
|
||||
EXIT /B 1
|
||||
)
|
||||
|
||||
IF NOT EXIST "%PGADMIN_KRB5_DIR%" (
|
||||
ECHO !PGADMIN_KRB5_DIR! does not exist.
|
||||
ECHO Please install MIT Kerberos for Windows and set the PGADMIN_KRB5_DIR environment variable.
|
||||
ECHO %PGADMIN_KRB5_DIR% does not exist.
|
||||
ECHO Please install MIT Kerberos for Windows, from the winpgbuild project or
|
||||
ECHO elsewhere, and set the PGADMIN_KRB5_DIR environment variable.
|
||||
EXIT /B 1
|
||||
)
|
||||
|
||||
IF NOT EXIST "%PGADMIN_POSTGRES_DIR%" (
|
||||
ECHO !PGADMIN_POSTGRES_DIR! does not exist.
|
||||
ECHO Please install PostgreSQL and set the PGADMIN_POSTGRES_DIR environment variable.
|
||||
ECHO %PGADMIN_POSTGRES_DIR% does not exist.
|
||||
ECHO Please install PostgreSQL, from the winpgbuild project or elsewhere, and
|
||||
ECHO set the PGADMIN_POSTGRES_DIR environment variable.
|
||||
EXIT /B 1
|
||||
)
|
||||
|
||||
@@ -387,33 +390,22 @@ REM Main build sequence Ends
|
||||
%TMPDIR%\rcedit-x64.exe "%BUILDROOT%\runtime\pgAdmin4.exe" --set-version-string "ProductName" "%APP_NAME%"
|
||||
%TMPDIR%\rcedit-x64.exe "%BUILDROOT%\runtime\pgAdmin4.exe" --set-product-version "%APP_VERSION%"
|
||||
|
||||
IF NOT "%PGADMIN_WINDOWS_CSC%" == "" (
|
||||
ECHO Attempting to sign the pgAdmin4.exe...
|
||||
CALL "%PGADMIN_SIGNTOOL_DIR%\signtool.exe" sign /sm /n "%PGADMIN_WINDOWS_CSC%" /tr http://timestamp.digicert.com /td sha256 /fd sha1 /v "%BUILDROOT%\runtime\pgAdmin4.exe"
|
||||
REM IF ERRORLEVEL, not IF %ERRORLEVEL%: inside a parenthesised block the
|
||||
REM latter is expanded when the block is parsed, before signtool has run,
|
||||
REM so it tested a stale value and this branch never fired. A failed
|
||||
REM signature on the executable users actually run was undetectable, and
|
||||
REM the installer around it was signed and verified regardless.
|
||||
IF ERRORLEVEL 1 (
|
||||
ECHO.
|
||||
ECHO ************************************************************
|
||||
ECHO * Failed to sign the pgAdmin4.exe
|
||||
ECHO ************************************************************
|
||||
EXIT /B 1
|
||||
)
|
||||
) ELSE (
|
||||
ECHO Skipping code signing ^(PGADMIN_WINDOWS_CSC is not set^)...
|
||||
)
|
||||
|
||||
ECHO Staging PostgreSQL components...
|
||||
COPY "%PGADMIN_POSTGRES_DIR%\bin\libpq.dll" "%BUILDROOT%\runtime" > nul || EXIT /B 1
|
||||
COPY "%PGADMIN_POSTGRES_DIR%\bin\libcrypto-*-x64.dll" "%BUILDROOT%\runtime" > nul || EXIT /B 1
|
||||
COPY "%PGADMIN_POSTGRES_DIR%\bin\libssl-*-x64.dll" "%BUILDROOT%\runtime" > nul || EXIT /B 1
|
||||
IF EXIST "%PGADMIN_POSTGRES_DIR%\bin\libintl-*.dll" COPY "%PGADMIN_POSTGRES_DIR%\bin\libintl-*.dll" "%BUILDROOT%\runtime" > nul
|
||||
IF EXIST "%PGADMIN_POSTGRES_DIR%\bin\libiconv-*.dll" COPY "%PGADMIN_POSTGRES_DIR%\bin\libiconv-*.dll" "%BUILDROOT%\runtime" > nul
|
||||
IF EXIST "%PGADMIN_POSTGRES_DIR%\bin\liblz4.dll" COPY "%PGADMIN_POSTGRES_DIR%\bin\liblz4.dll" "%BUILDROOT%\runtime" > nul
|
||||
IF EXIST "%PGADMIN_POSTGRES_DIR%\bin\libzstd.dll" COPY "%PGADMIN_POSTGRES_DIR%\bin\libzstd.dll" "%BUILDROOT%\runtime" > nul
|
||||
IF EXIST "%PGADMIN_POSTGRES_DIR%\bin\libintl-*.dll" (
|
||||
COPY "%PGADMIN_POSTGRES_DIR%\bin\libintl-*.dll" "%BUILDROOT%\runtime" > nul || EXIT /B 1
|
||||
)
|
||||
IF EXIST "%PGADMIN_POSTGRES_DIR%\bin\libiconv-*.dll" (
|
||||
COPY "%PGADMIN_POSTGRES_DIR%\bin\libiconv-*.dll" "%BUILDROOT%\runtime" > nul || EXIT /B 1
|
||||
)
|
||||
IF EXIST "%PGADMIN_POSTGRES_DIR%\bin\liblz4.dll" (
|
||||
COPY "%PGADMIN_POSTGRES_DIR%\bin\liblz4.dll" "%BUILDROOT%\runtime" > nul || EXIT /B 1
|
||||
)
|
||||
IF EXIST "%PGADMIN_POSTGRES_DIR%\bin\libzstd.dll" (
|
||||
COPY "%PGADMIN_POSTGRES_DIR%\bin\libzstd.dll" "%BUILDROOT%\runtime" > nul || EXIT /B 1
|
||||
)
|
||||
COPY "%PGADMIN_POSTGRES_DIR%\bin\zlib1.dll" "%BUILDROOT%\runtime" > nul || EXIT /B 1
|
||||
COPY "%PGADMIN_POSTGRES_DIR%\bin\pg_dump.exe" "%BUILDROOT%\runtime" > nul || EXIT /B 1
|
||||
COPY "%PGADMIN_POSTGRES_DIR%\bin\pg_dumpall.exe" "%BUILDROOT%\runtime" > nul || EXIT /B 1
|
||||
@@ -468,7 +460,7 @@ REM Main build sequence Ends
|
||||
|
||||
ECHO Creating windows installer using INNO tool...
|
||||
IF NOT "%PGADMIN_WINDOWS_CSC%" == "" (
|
||||
CALL "%PGADMIN_INNOTOOL_DIR%\ISCC.exe" "%WD%\pkg\win32\installer.iss" "/DSIGNED" "/SpgAdminSigntool=%PGADMIN_SIGNTOOL_DIR%\signtool.exe sign /sm /n $q%PGADMIN_WINDOWS_CSC%$q /tr http://timestamp.digicert.com /td sha256 /fd sha1 /v $f" || EXIT /B 1
|
||||
CALL "%PGADMIN_INNOTOOL_DIR%\ISCC.exe" "%WD%\pkg\win32\installer.iss" "/DSIGNED" "/SpgAdminSigntool=%PGADMIN_SIGNTOOL_DIR%\signtool.exe sign /sm /n $q%PGADMIN_WINDOWS_CSC%$q /tr http://timestamp.digicert.com /td sha256 /fd sha256 /v $f" || EXIT /B 1
|
||||
) ELSE (
|
||||
CALL "%PGADMIN_INNOTOOL_DIR%\ISCC.exe" "%WD%\pkg\win32\installer.iss" || EXIT /B 1
|
||||
)
|
||||
@@ -512,6 +504,61 @@ REM Main build sequence Ends
|
||||
EXIT /B 0
|
||||
|
||||
|
||||
REM Sign one or more files, passed as quoted arguments. Signing is done in a
|
||||
REM single signtool invocation as the hardware token prompts for a PIN on the
|
||||
REM first signature of a session.
|
||||
:SIGN_FILES
|
||||
IF "%PGADMIN_WINDOWS_CSC%" == "" EXIT /B 0
|
||||
|
||||
CALL "%PGADMIN_SIGNTOOL_DIR%\signtool.exe" sign /sm /n "%PGADMIN_WINDOWS_CSC%" /tr http://timestamp.digicert.com /td sha256 /fd sha256 /v %*
|
||||
REM IF ERRORLEVEL, not IF %ERRORLEVEL%: the latter would be expanded before
|
||||
REM signtool had run were this ever moved inside a parenthesised block.
|
||||
IF ERRORLEVEL 1 (
|
||||
ECHO.
|
||||
ECHO ************************************************************
|
||||
ECHO * Failed to sign one or more files
|
||||
ECHO ************************************************************
|
||||
EXIT /B 1
|
||||
)
|
||||
|
||||
EXIT /B 0
|
||||
|
||||
|
||||
REM Sign the components that we build ourselves: the runtime executable, and
|
||||
REM the PostgreSQL and Kerberos utilities and libraries obtained from the
|
||||
REM winpgbuild project, all of which are staged into the runtime directory by
|
||||
REM :CREATE_RUNTIME_ENV. The Electron, Python and VC++ runtime components are
|
||||
REM deliberately left alone, as they are third party binaries that we do not
|
||||
REM build, and signing them would replace any signature of their own. Names
|
||||
REM are matched as patterns as some of the libraries include version numbers,
|
||||
REM and some of them are optional.
|
||||
:SIGN_COMPONENTS
|
||||
IF "%PGADMIN_WINDOWS_CSC%" == "" (
|
||||
ECHO Skipping code signing ^(PGADMIN_WINDOWS_CSC is not set^)...
|
||||
EXIT /B 0
|
||||
)
|
||||
|
||||
ECHO Attempting to sign the pgAdmin, PostgreSQL and Kerberos components...
|
||||
|
||||
SETLOCAL EnableDelayedExpansion
|
||||
SET "COMPONENTS="
|
||||
FOR %%p IN (pgAdmin4.exe libpq.dll libcrypto-*-x64.dll libssl-*-x64.dll libintl-*.dll libiconv-*.dll liblz4.dll libzstd.dll zlib1.dll pg_dump.exe pg_dumpall.exe pg_restore.exe psql.exe kinit.exe krb5_64.dll comerr64.dll k5sprt64.dll gssapi64.dll krbcc64.dll ccapiserver.exe) DO (
|
||||
FOR /F "delims=" %%f IN ('DIR /B "%BUILDROOT%\runtime\%%p" 2^>nul') DO SET "COMPONENTS=!COMPONENTS! "%BUILDROOT%\runtime\%%f""
|
||||
)
|
||||
IF "!COMPONENTS!" == "" (
|
||||
ECHO.
|
||||
ECHO ************************************************************
|
||||
ECHO * No components were found to sign
|
||||
ECHO ************************************************************
|
||||
ENDLOCAL
|
||||
EXIT /B 1
|
||||
)
|
||||
CALL :SIGN_FILES !COMPONENTS! || EXIT /B 1
|
||||
ENDLOCAL
|
||||
|
||||
EXIT /B 0
|
||||
|
||||
|
||||
:USAGE
|
||||
ECHO Invalid command line options.
|
||||
ECHO Usage: "Make.bat [clean]"
|
||||
|
||||
Reference in New Issue
Block a user