How Authenticode to sign ClickOnce with EV SHA2 and avoid "Unknown Publisher"

When signing my ClickOnce deployment using the Signing preferences page in Visual Studio, I specified the SHA2 (SHA256) EV Authenticode certificate and published.

enter image description here

After publishing and trying to run bootstrapper (setup.exe), I am prompted with an "Unknown Publisher" in the ClickOnce dialog box.

enter image description here

EV eToken SafeNet . PE (exe dll) signtool , . ClickOnce. , ClickOnce , (setup.exe) , ".deploy".

enter image description here

, ".application" ".manifest" (, mage Visual Studio), <publisherIdentity> .

enter image description here

Win10, , :

  • -
  • - https
  • " "
  • " ", " "
  • Manifest:
    • URL
    • URL-
  • Windows
  • signtool (, , mageui)
  • ,

, - .

+5
4

:

  • ClickOnce " " SHA2.
  • 1 2016 . Windows SHA1 / Authenticode. , Windows SmartScreen " " SHA1 Authenticode.

catch-22, SHA1 ClickOnce SHA2 SmartScreen. .

(, CA), SHA1 SHA2. DigiCert . , , SHA2, , SHA1 ( vica-versa), , , , , DigiCert , , ( ).

, EV, Visual Studio ClickOnce SHA1. Timestamp .

enter image description here

ClickOnce ClickOnce (setup.exe), SHA2.

signtool.exe sign /tr http://timestamp.digicert.com /td sha256 /fd sha256 /as /sha1 YourCertThumbprintHash "X:\Deployment\ClickOnceCert\setup.exe"

, - MMC . , SHA1 SHA2.

enter image description here

" " .

enter image description here

setup.exe , "URL- " "" Visual Studio, , . , , , , .

enter image description here

+3

Smart Screen. PowerShell script, , , , , SHA256, ClickOnce (.application) SHA1.

SignClickOnceApp.ps1

<#
.SYNOPSIS 
    A PowerShell Script to correctly sign a ClickOnce Application.
.DESCRIPTION 
    Microsoft ClickOnce Applications Signed with a SHA256 Certificate show as Unknown Publisher during installation, ClickOnce Applications signed with a SHA1 Certificate show an Unknown Publisher SmartScreen Warning once installed, this happens because:
    1) The ClickOnce installer only supports SHA1 certificates (not SHA256), but,
    2) Microsoft has depreciated SHA1 for Authenticode Signing.

    This script uses two code signing certificates (one SHA1 and one SHA256) to sign the various parts of the ClickOnce Application so that both the ClickOnce Installer and SmartScreen are happy.
.PARAMETER VSRoot
    The Visual Studio Projects folder, if not provided .\Documents\Visual Studio 2015\Projects will be assumed
.PARAMETER SolutionName
    The Name of the Visual Studio Solution (Folder), if not provided the user is prompted.
.PARAMETER ProjectName
    The Name of the Visual Studio Project (Folder), if not provided the user is prompted.
.PARAMETER SHA1CertThumbprint
    The Thumbprint of the SHA1 Code Signing Certificate, if not provided the user is prompted.
.PARAMETER SHA256CertThumbprint
    The Thumbprint of the SHA256 Code Signing Certificate, if not provided the user is prompted.
.PARAMETER TimeStampingServer
    The Time Stamping Server to be used while signing, if not provided the user is prompted.
.PARAMETER PublisherName
    The Publisher to be set on the ClickOnce files, if not provided the user is prompted.
.PARAMETER Verbose
    Writes verbose output.
.EXAMPLE
    SignClickOnceApp.ps1 -VSRoot "C:\Users\Username\Documents\Visual Studio 2015\Projects" -SolutionName "MySolution" -ProjectName "MyProject" -SHA1CertThumbprint "f3f33ccc36ffffe5baba632d76e73177206143eb" -SHA256CertThumbprint "5d81f6a4e1fb468a3b97aeb3601a467cdd5e3266" -TimeStampingServer "http://time.certum.pl/" -PublisherName "Awesome Software Inc."
    Signs MyProject in MySolution which is in C:\Users\Username\Documents\Visual Studio 2015\Projects using the specified certificates, with a publisher of "Awesome Software Inc." and the Certum Timestamping Server.
.NOTES 
    Author  : Joe Pitt
    License : SignClickOnceApp by Joe Pitt is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/.
.LINK 
    https://www.joepitt.co.uk/Project/SignClickOnceApp/
#>
param (
    [string]$VSRoot, 
    [string]$SolutionName, 
    [string]$ProjectName, 
    [string]$SHA1CertThumbprint, 
    [string]$SHA256CertThumbprint, 
    [string]$TimeStampingServer,
    [string]$PublisherName,
    [switch]$Verbose
)

$oldverbose = $VerbosePreference
if($Verbose) 
{
    $VerbosePreference = "continue" 
}

# Visual Studio Root Path
if(!$PSBoundParameters.ContainsKey('VSRoot'))
{
    $VSRoot = '.\Documents\Visual Studio 2015\Projects\'
}
if (Test-Path "$VSRoot")
{
    Write-Verbose "Using '$VSRoot' for Visual Studio Root"
}
else
{
    Write-Error -Message "VSRoot does not exist." -RecommendedAction "Check path and try again" -ErrorId "1" `
        -Category ObjectNotFound -CategoryActivity "Testing VSRoot Path" -CategoryReason "The VSRoot path was not found" `
        -CategoryTargetName "$VSRoot" -CategoryTargetType "Directory"
    exit 1
}

# Solution Path
if(!$PSBoundParameters.ContainsKey('SolutionName'))
{
    $SolutionName = Read-Host "Solution Name"
}
if (Test-Path "$VSRoot\$SolutionName")
{
    Write-Verbose "Using '$VSRoot\$SolutionName' for Solution Path"
    $SolutionPath = "$VSRoot\$SolutionName"
}
else
{
    Write-Error -Message "Solution does not exist." -RecommendedAction "Check Solution Name and try again" -ErrorId "2" `
        -Category ObjectNotFound -CategoryActivity "Testing Solution Path" -CategoryReason "The Solution path was not found" `
        -CategoryTargetName "$VSRoot\$SolutionName" -CategoryTargetType "Directory"
    exit 2
}

# Project Path
if(!$PSBoundParameters.ContainsKey('ProjectName'))
{
    $ProjectName = Read-Host "Project Name"
}
if (Test-Path "$SolutionPath\$ProjectName")
{
    Write-Verbose "Using '$SolutionPath\$ProjectName' for Project Path"
    $ProjectPath = "$SolutionPath\$ProjectName"
}
else
{
    Write-Error -Message "Project does not exist." -RecommendedAction "Check Project Name and try again" -ErrorId "3" `
        -Category ObjectNotFound -CategoryActivity "Testing Project Path" -CategoryReason "The Project path was not found" `
        -CategoryTargetName "$SolutionPath\$ProjectName" -CategoryTargetType "Directory"
    exit 3
}

# Publish Path
if (Test-Path "$ProjectPath\publish")
{
    Write-Verbose "Using '$ProjectPath\publish' for Publish Path"
    $PublishPath = "$ProjectPath\publish"
}
else
{
    Write-Error -Message "Publish path does not exist." -RecommendedAction "Check Project has been published to \publish and try again" -ErrorId "4" `
        -Category ObjectNotFound -CategoryActivity "Testing Publish Path" -CategoryReason "The publish path was not found" `
        -CategoryTargetName "$ProjectPath\publish" -CategoryTargetType "Directory"
    exit 4
}

# Application Files Path
if (Test-Path "$PublishPath\Application Files")
{
    Write-Verbose "Using '$PublishPath\Application Files' for Application Files Path"
    $AppFilesPath = "$PublishPath\Application Files"
}
else
{
    Write-Error -Message "Application Files path does not exist." -RecommendedAction "Check Project has been published to \publish and try again" -ErrorId "5" `
        -Category ObjectNotFound -CategoryActivity "Testing Application Files Path" -CategoryReason "The Application Files path was not found" `
        -CategoryTargetName "$PublishPath\Application Files" -CategoryTargetType "Directory"
    exit 5
}

# Target Path
$TargetPath = Convert-Path "$AppFilesPath\${ProjectName}_*"
if ($($TargetPath.Length) -ne 0)
{
    Write-Verbose "Using $TargetPath for Target Path"
}
else
{
    Write-Error -Message "No versions." -RecommendedAction "Check Project has been published to \publish and try again" -ErrorId "6" `
        -Category ObjectNotFound -CategoryActivity "Searching for published version path" -CategoryReason "No Application has been published using ClickOnce" `
        -CategoryTargetName "$AppFilesPath\${ProjectName}_*" -CategoryTargetType "Directory"
    exit 6
}

# SHA1 Certificate
if(!$PSBoundParameters.ContainsKey('SHA1CertThumbprint'))
{
    $SHA1CertThumbprint = Read-Host "SHA1 Certificate Thumbprint"
}
if ("$SHA1CertThumbprint" -notmatch "^[0-9A-Fa-f]{40}$")
{
    Write-Error -Message "SHA1 Thumbprint Malformed" -RecommendedAction "Check the thumbprint and try again" -ErrorId "7" `
        -Category InvalidArgument -CategoryActivity "Verifying Thumbprint Format" -CategoryReason "Thumbprint is not a 40 character Base64 string" `
        -CategoryTargetName "$SHA1CertThumbprint" -CategoryTargetType "Base64String"
    exit 7
}
$SHA1Found = Get-ChildItem -Path Cert:\CurrentUser\My | where {$_.Thumbprint -eq "$SHA1CertThumbprint"} | Measure-Object
if ($SHA1Found.Count -eq 0)
{
    Write-Error -Message "SHA1 Certificate Not Found" -RecommendedAction "Check the thumbprint and try again" -ErrorId "8" `
        -Category ObjectNotFound -CategoryActivity "Searching for certificate" -CategoryReason "Certificate with Thumbprint not found" `
        -CategoryTargetName "$SHA1CertThumbprint" -CategoryTargetType "Base64String"
    exit 8
}

# SHA256 Certificate
if(!$PSBoundParameters.ContainsKey('SHA256CertThumbprint'))
{
    $SHA256CertThumbprint = Read-Host "SHA256 Certificate Thumbprint"
}
if ("$SHA256CertThumbprint" -notmatch "^[0-9A-Fa-f]{40}$")
{
    Write-Error -Message "SHA256 Thumbprint Malformed" -RecommendedAction "Check the thumbprint and try again" -ErrorId "9" `
        -Category InvalidArgument -CategoryActivity "Verifying Thumbprint Format" -CategoryReason "Thumbprint is not a 40 character Base64 string" `
        -CategoryTargetName "$SHA256CertThumbprint" -CategoryTargetType "Base64String"
    exit 9
}
$SHA256Found = Get-ChildItem -Path Cert:\CurrentUser\My | where {$_.Thumbprint -eq "$SHA256CertThumbprint"} | Measure-Object
if ($SHA256Found.Count -eq 0)
{
    Write-Error -Message "SHA256 Certificate Not Found" -RecommendedAction "Check the thumbprint and try again" -ErrorId "10" `
        -Category ObjectNotFound -CategoryActivity "Searching for certificate" -CategoryReason "Certificate with Thumbprint not found" `
        -CategoryTargetName "$SHA256CertThumbprint" -CategoryTargetType "Base64String"
    exit 10
}

# TimeStamping Server
if(!$PSBoundParameters.ContainsKey('TimeStampingServer'))
{
    $TimeStampingServer = Read-Host "TimeStamping Server URL"
}
if ("$TimeStampingServer" -notmatch "^http(s)?:\/\/[A-Za-z0-9-._~:/?#[\]@!$&'()*+,;=]+$")
{
    Write-Error -Message "SHA256 Thumbprint Malformed" -RecommendedAction "Check the TimeStamp URL and try again" -ErrorId "11" `
        -Category InvalidArgument -CategoryActivity "Verifying TimeStamping URL" -CategoryReason "TimeStamping URL is not a RFC Compliant URL" `
        -CategoryTargetName "$TimeStampingServer" -CategoryTargetType "URL"
    exit 11
}

# Publisher Name
# Project Path
if(!$PSBoundParameters.ContainsKey('PublisherName'))
{
    $PublisherName = Read-Host "Publisher Name"
}

# Sign setup.exe and application.exe with SHA256 Cert
Write-Verbose "Signing '$PublishPath\Setup.exe' (SHA256)"
Start-Process "$PSScriptRoot\signtool.exe" -ArgumentList "sign /fd SHA256 /td SHA256 /tr $TimeStampingServer /sha1 $SHA256CertThumbprint `"$PublishPath\Setup.exe`"" -Wait -NoNewWindow
Write-Verbose "Signing '$TargetPath\$ProjectName.exe.deploy' (SHA256)"
Start-Process "$PSScriptRoot\signtool.exe" -ArgumentList "sign /fd SHA256 /td SHA256 /tr $TimeStampingServer /sha1 $SHA256CertThumbprint `"$TargetPath\$ProjectName.exe.deploy`"" -Wait -NoNewWindow

# Remove .deploy extensions
Write-Verbose "Removing .deploy extensions"
Get-ChildItem "$TargetPath\*.deploy" -Recurse | Rename-Item -NewName { $_.Name -replace '\.deploy','' } 

# Sign Manifest with SHA256 Cert
Write-Verbose "Signing '$TargetPath\$ProjectName.exe.manifest' (SHA256)"
Start-Process "$PSScriptRoot\mage.exe" -ArgumentList "-update `"$TargetPath\$ProjectName.exe.manifest`" -ch $SHA256CertThumbprint -if `"Logo.ico`" -ti `"$TimeStampingServer`"" -Wait -NoNewWindow

# Sign ClickOnces with SHA1 Cert
Write-Verbose "Signing '$TargetPath\$ProjectName.application' (SHA1)"
Start-Process "$PSScriptRoot\mage.exe" -ArgumentList "-update `"$TargetPath\$ProjectName.application`"  -ch $SHA1CertThumbprint -appManifest `"$TargetPath\$ProjectName.exe.manifest`" -pub `"$PublisherName`" -ti `"$TimeStampingServer`"" -Wait -NoNewWindow
Write-Verbose "Signing '$PublishPath\$ProjectName.application' (SHA1)"
Start-Process "$PSScriptRoot\mage.exe" -ArgumentList "-update `"$PublishPath\$ProjectName.application`" -ch $SHA1CertThumbprint -appManifest `"$TargetPath\$ProjectName.exe.manifest`" -pub `"$PublisherName`" -ti `"$TimeStampingServer`"" -Wait -NoNewWindow

# Readd .deply extensions
Write-Verbose "Re-adding .deploy extensions"
Get-ChildItem -Path "$TargetPath\*"  -Recurse | Where-Object {!$_.PSIsContainer -and $_.Name -notlike "*.manifest" -and $_.Name -notlike "*.application"} | Rename-Item -NewName {$_.Name + ".deploy"}
+1

, , Visual Studio 15.7.5 (, , , ), setup.exe ClickOnce SHA2 EV ( SHA-1). Windows 10 (10.0.16299.492), Windows 8, . , Visual Studio SmartScreen. ClickOnce , .

: enter image description here

" ":

enter image description here

ClickOnce setup.exe

enter image description here

ClickOnce *.exe.deploy

enter image description here

, :

enter image description here

+1

- Azure DevOps CI/CD, . github

pmx, , , , .

, :)

0

Source: https://habr.com/ru/post/1654852/


All Articles