如何使用EV SHA2证书对Authenticode签名ClickOnce部署,并避免" Unknown Publisher"

时间:2016-09-16 18:59:44

标签: clickonce authenticode

通过Visual Studio的项目签署我的ClickOnce部署"签署"设置页面我指定了SHA2(SHA256)EV Authenticode证书并发布。

enter image description here

发布并尝试运行引导程序(setup.exe)后,我出现了" Unknown Publisher"在ClickOnce对话框中。

enter image description here

有问题的EV证书有效且在eToken硬件令牌上运行,使用SafeNet客户端工具与令牌进行通信。使用signtool签署常规PE文件(exe和dll)始终生成完全有效的程序集,并且发布者是已知的。 这只是ClickOnce部署的一个问题。此外,ClickOnce部署的各个文件看起来完全有效,因为文件属性对话框的数字签名选项卡正确列出了引导程序(setup.exe)和后缀为" .deploy"的程序集文件。

enter image description here

此外,&#34; .application&#34;和&#34; .manifest&#34;文件被适当地改变(可能通过Visual Studio的mage)来包含<publisherIdentity>元素以及正确设置的算法。

enter image description here

签名机正在运行Win10,我已经尝试了我能想象到的每一种排列:

  • 有无时间戳
  • 有无强名称签名
  • 有无在线发布
  • 使用和不使用https在线发布
  • 有无特定&#34;更新位置&#34;通过发布页面
  • 有没有&#34;出版商名称&#34;通过发布页面中的描述设置
  • 使用Manifest选项的每个组合:
    • 排除部署提供商网址
    • 阻止应用程序通过URL激活
    • 使用应用程序清单获取信任信息
  • 各种版本的Windows上的多台计算机
  • 通过mage和signtool进行手动清单签名和程序集签署(也可以是mageui)
  • 确保证书服务提供商未撤销证书

似乎有someone else experiencing this

4 个答案:

答案 0 :(得分:3)

发生这种情况的原因是几个因素

  1. ClickOnce显示&#34; Unknown Publisher&#34;使用SHA2 Authenticode证书时。
  2. 2016年1月1日,Windows弃用SHA1进行Authenticode签名/代码签名。 Windows SmartScreen技术因此显示&#34; Unknown Publisher&#34;使用SHA1 Authenticode证书时。
  3. 这实际上是一个catch-22 ,您需要SHA1用于ClickOnce发布者验证,SHA2用于SmartScreen。 尼斯

    与证书提供商(希望是真正的CA)合作,为您提供SHA1 SHA2证书。 DigiCert的人很棒。在大多数情况下,您必须与CA一起工作,因为即使您已经拥有自己的SHA2证书并且与他们合作也获得SHA1证书(或反之亦然),它可能会自动撤销您拥有的任何现有证书。在DigiCert的情况下,当我解释我想要尝试的内容时,他们能够阻止自动撤销(双重签名)。

    在EV令牌上安装这些令牌后,配置Visual Studio以使用SHA1证书对ClickOnce舱单进行签名。理想情况下,您还将在同一对话框中提供时间戳服务器,以便最终证书到期。

    enter image description here

    在本地发布ClickOnce部署之后,在分发之前,通过附加SHA2证书对您的ClickOnce引导程序(setup.exe)进行双重签名。

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

    注意,查找证书指纹的一种方法是通过证书MMC管理单元。是的,对于SHA2证书,指纹应该是SHA1。

    enter image description here

    现在,bootstapper在文件属性对话框的“数字签名”选项卡中显示了两个证书。

    enter image description here

    从指定为&#34;安装文件夹URL&#34;的位置运行setup.exe时在Visual Studio中的“发布”页面中,您应该将发布者视为受信任的。理解安装文件夹很重要,因为如果您要从其他位置运行应用程序,那么您应该期望不受信任,因为引导程序将调用已知的安装文件夹来检索申请文件。

    enter image description here

答案 1 :(得分:1)

无论如何,对我来说,明确的答案会导致智能屏幕警告。您可能对我编写的PowerShell脚本感兴趣,该脚本通过使用SHA256证书进行签名然后使用SHA1证书对ClickOnce(.application)文件进行签名来解决这两个问题。

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"}

答案 2 :(得分:1)

似乎是因为Visual Studio 15.7.5(或者可能是以前的版本,我没有检查过),所以在使用SHA2 EV代码签名证书签名时,setup.exe和应用程序二进制文件都对ClickOnce有效(无需询问)您的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

答案 3 :(得分:1)

如果您正在寻找更适合Azure DevOps CI / CD管道的东西,我已接过Joe Pitt的工作并将其重构为我的管道。在github here

您可以向脚本传递pmx文件路径和密码,它将调整证书,安装证书并签署可执行文件,设置文件,清单文件和应用程序文件。

请帮助我做得更好:)