How to check the certificate used to sign QDT binary

Tip

Starting with the 0.39.0 version, the released binary for Windows is signed with a self-signed certificate linked to Oslandia as subject. See how it’s done behind the scenes for a full transparency report.

Certificate materials

Since we use a self-signed certificate, we publish the material used to show QDT’s worth:

  • the Certificate Signing Request (CSR): builder/code_signing_certificate_request.csr which ships:

    • identity (DN : CN, O, etc)

    • the public key extracted from the private key used to sign the certificate

  • the Certificate itself (CRT): builder/code_signing_certificate.crt which ships:

    • identity (DN : CN, O, etc)

    • the public key extracted from the private key used to sign the certificate

    • extensions (ELU, validity dates…)

    • digital sign

Windows

Extract certificate information from the binary

Run this command in a PowerShell terminal:

Get-AuthenticodeSignature .\dist\qdt.exe | Format-List *

That should return the following:

SignerCertificate      : [Subject]
                           E="qgis+qdt@oslandia.com", CN=oslandia.com, O=Oslandia, L=Paris, S=Ãle-de-France, C=FR

                         [Issuer]
                           E="qgis+qdt@oslandia.com", CN=oslandia.com, O=Oslandia, L=Paris, S=Ãle-de-France, C=FR

                         [Serial Number]
                           5A3010F0288B365E02251F02C25CBEF2E52199F1

                         [Not Before]
                           22/09/2025 18:38:28

                         [Not After]
                           20/09/2035 18:38:28

                         [Thumbprint]
                           8D4D002E279490E07D0782CF62824A524D142968

TimeStamperCertificate : [Subject]
                           CN=DigiCert SHA256 RSA4096 Timestamp Responder 2025 1, O="DigiCert, Inc.", C=US

                         [Issuer]
                           CN=DigiCert Trusted G4 TimeStamping RSA4096 SHA256 2025 CA1, O="DigiCert, Inc.", C=US

                         [Serial Number]
                           0A80EF184B8DF10582D1C476A7957468

                         [Not Before]
                           04/06/2025 02:00:00

                         [Not After]
                           04/09/2036 01:59:59

                         [Thumbprint]
                           DD6230AC860A2D306BDA38B16879523007FB417E

Status                 : UnknownError
StatusMessage          : Une chaîne de certificats a été traitée mais sest terminée par un certificat racine qui nest pas approuvé
                         par le fournisseur dapprobation
Path                   : C:\Users\username\Downloads\dist\Windows_QGISDeploymentToolbelt_0-39-0.exe
SignatureType          : Authenticode
IsOSBinary             : False

Import the certificate in your local store

  1. Download the CRT file from official QDT repository

  2. Right-clic on the downloaded file and select “Install certificate…” to launch the Certificate Import Wizard.

    Windows - CRT contect menu

  3. Choose the certificate scope: Active directory, Local Machine or current user. Depending on your choice, it may require administrator privileges. Then click Next.

    Windows - Certificat import wizard

  4. Select Place all certificates in the following store, then click Browse…

  5. Choose the certificate store Trusted Publishers:

    Windows - Certificat import wizard - Select store

  6. Click OK, then Next.

  7. Click Finish to complete the import.

A security warning may appear for self-signed certificates; confirm by clicking Yes to trust the certificate.

Check it in certificate management console

  1. In your start menu, search for “certificat” and click on “Manage user certificates” to open the Certificate Manager (certmgr).

  2. Expand Certificates, then expand the folder corresponding to the store you imported into (e.g., Trusted Publishers > Certificates).

Windows - Certificate manager

Automate import with a PowerShell script

<#
.Synopsis
   Download the certificate use to sign the QDT executable and import it to the local store.
.DESCRIPTION
   This script downloads a CRT certificate file from a GitHub repository, imports it into a specified certificate store on the local machine,
and sets a friendly name and description for the certificate.
.PARAMETER storeName
    The name of the Windows certificate store where the certificate will be imported. Default: "TrustedPublisher"
.PARAMETER storeScope
    The scope of the certificate store. Can be "CurrentUser" or "LocalMachine". Default: "CurrentUser".
.PARAMETER friendlyName
    The friendly name to assign to the certificate in the Windows certificate store.
.PARAMETER description
    The description to assign to the certificate (stored as a certificate property).
.EXAMPLE
    .\import_cert.ps1 -storeName Root -storeScope LocalMachine
.NOTES
    Requires to be run as administrator to import into machine-level stores.
.LICENSE
   SPDX-License-Identifier: Apache-2.0
#>


# -- VARIABLES
param(
    [string]$storeName = "TrustedPublisher",  # Default store is Trusted Publishers
    [string]$storeScope = "CurrentUser",      # default scope is CurrentUser; use "LocalMachine" for machine-wide store (requires admin rights)
    [string]$friendlyName = "QDT Code Signing Certificate",
    [string]$description = "Certificate issued by Oslandia for signing QDT executables. See online documentation: https://qgis-deployment.github.io/qgis-deployment-toolbelt-cli/."
)
# remote CRT URL
$certUrl = "https://raw.githubusercontent.com/qgis-deployment/qgis-deployment-toolbelt-cli/refs/heads/main/builder/code_signing_certificate.crt"
# local path where storing the downloaded certificate
$localCertPath = "$env:TEMP\qdt_code_signing_certificate.crt"

# -- MAIN
# validate store scope
if ($storeScope -notin @("CurrentUser","LocalMachine")) {
    Write-Error "Invalid store scope. Use 'CurrentUser' or 'LocalMachine'."
    exit 1
}

# if LocalMachine scope is requested, ensure the script is running with admin rights
if ($storeScope -eq "LocalMachine") {
    $currentUser = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
    if (-not $currentUser.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)) {
        Write-Warning "This script requires administrator privileges for LocalMachine scope. Relaunching with elevation..."
        Start-Process -FilePath "powershell" -ArgumentList "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`" $args" -Verb RunAs
        exit
    }
}

Invoke-WebRequest -Uri $certUrl -OutFile $localCertPath
Write-Host "Certificate downloaded to $localCertPath"

# Load the certificate
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($localCertPath)

# Add metadata to improve integration with Windows certificate manager
$cert.FriendlyName = "QDT Code Signing Certificate"
$cert.Extensions.Add(
    New-Object System.Security.Cryptography.X509Certificates.X509Extension("2.5.29.17", [System.Text.Encoding]::ASCII.GetBytes($description), $false)
)

# Open the specified certificate store (LocalMachine)
$store = New-Object System.Security.Cryptography.X509Certificates.X509Store($storeName, $storeScope)
$store.Open("ReadWrite")

# Import the certificate
$store.Add($cert)
$store.Close()


Write-Host "Certificate imported into $storeName store with scope $storeScope and Friendly Name: '$friendlyName'"