在Sn.exe中自动输入密码

时间:2010-06-29 05:36:38

标签: c# signing sn.exe

我需要创建post build事件来执行以下操作:

sn -i MyKey.pfx MyKeyContainerName
tlbimp $(ConfigurationName)\MyCom.tlb /out:$(ConfigurationName)\NETMyCom.dll /keycontainer:MyKeyContainerName
sn -d MyKeyContainerName

当Visual Studio执行第一个语句时,它需要一个密码并等待用户指定并失败。

  

Microsoft(R).NET Framework Strong   名称实用程序版本2.0.50727.42   版权所有(c)Microsoft Corporation。   保留所有权利。

     

输入PKCS#12密钥的密码   file:无法解析PKCS#12   在mykey.pfx中的blob - 句柄是   无效。

我尝试使用sn命令行参数指定密码,但我看不到这样做的方法。

请帮忙。

此致 希尔米。

7 个答案:

答案 0 :(得分:3)

如果像我一样,你没有使用TFS或MSBUILD来构建,那么至少还有其他两种方式:

a)从脚本运行sn.exe,并将密码写入stdin

请参阅here了解C#示例:

注意:使用.NET4版本的sn.exe似乎不可能将其作为外部进程执行(至少在Windows XP上),并将密码写入stdin(我尝试使用python +和C#,以及sn .exe似乎只是退出而不等待密码输入)。

b)使用sn.exe重新签名,使用已安装的pfx。

如果您已经安装了pfx文件,那么您可能知道容器名称(通常Visual Studio使用类似VS_KEY_ABAB1234ABAB1234的名称)

如果像我一样,您不知道或记住容器名称,那么只需重新安装pfx文件:

sn -i myPfxFile VS_KEY_ABAB1234ABAB1234

sn.exe会提示您输入密码,因为它会在pfx文件中安装证书。

然后,您可以让sn.exe重新签名您的程序集,而不提示输入密码:

sn -Rca myAssembly.dll myVSkey

以上内容可以在构建脚本中使用,因为不需要交互: - )

请记住检查签名是否确实有效:

sn -v myAssembly.dll

答案 1 :(得分:2)

我今天遇到了这个问题,我在ClickOnce C#app中使用了一个C ++ DLL。

我在DLL上设置延迟签名,然后使用post-build事件来运行SN,如下所示:

ECHO <your-password-here> | sn.exe -R $(OutDir)$(TargetFileName) $(MSBuildProjectDirectory)<path-to-pfx-file>

喜欢老式批处理方法。 ECHO <your-password-here>打印您的密码,管道|管道输出到SN.exe,后者取密码。

你可能不会像我一样需要延迟签名和-R开关,但你明白了。

编辑:这可能没有更长时间的工作 - 我的回答是从2013年开始的,当时我正在使用VS2010。

答案 2 :(得分:2)

我已经研究了近两天了。我尝试了旧版本的sn.exe(可以追溯到2.0!),但我无法使用echo PASSWORD |技巧。

最后,我这样做了:

[void] [System.Reflection.Assembly]::LoadWithPartialName("'System.Windows.Forms")

Start-Process "C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\sn.exe " -ArgumentList "-i 
`"Certificate.pfx`" VS_KEY_XXXXXXX" -NoNewWindow -Wait
[System.Windows.Forms.SendKeys]::SendWait("PASSWORDHERE~")
  • SendWait()将密钥发送到当前激活的窗口。由于我们使用sn.exe使用Start-Process修饰符启动-NoNewWindow,因此我们的窗口已经过关注。
  • ~是一个特殊字符,表示ENTER按钮。 {ENTER}也是可能的,但这是键盘上小键盘附近的Enter按钮。可能没有任何区别,但我只是想确定。

我使用的来源:https://technet.microsoft.com/en-us/library/ff731008.aspx

由于AppActivate(),我最终不需要Visual Basic的-NoNewWindow

更新:使用-Wait参数更好地工作!

答案 3 :(得分:1)

不幸的是,这里没有提到的方法对我有用。我必须在Docker容器中注册几个pfxs。

所以我重新开发了 使用RSACryptoServiceProvider在C#中使用sn.exe -i <infile> <container>命令。源和应用程序在GitHub中,位于 SnInstallPfx项目。

SnInstallPfx应用程序接受PFX密钥及其密码。它会自动计算密钥容器名称(VS_KEY _ *)(从MSBuild源代码中借用)并将其安装到强名称CSP。

用法:

SnInstallPfx.exe <pfx_infile> <pfx_password>

答案 4 :(得分:0)

我需要将其自动化并发现此问题。在回答this之后(非常感谢@Thomas Rijsewijk),我编写了该版本:

# Start the sn.exe process
Start-Process $SnExePath -ArgumentList "-i $PfxCertificatePath $LocalContainerName" -NoNewWindow
# Wait for the process to start
Start-Sleep 2
# This workaround allows to forward the password to the standard input
[void] [System.Reflection.Assembly]::LoadWithPartialName("'System.Windows.Forms")
[System.Windows.Forms.SendKeys]::SendWait("$($PfxCertificatePassword){ENTER}")
Start-Sleep 2
# This ENTER is to return from the sn.exe process
[System.Windows.Forms.SendKeys]::SendWait("{ENTER}")

使用-Wait命令的Start-process开关不能解决问题,因为PS脚本在将密码转发给它之前正在等待sn.exe进程终止。

答案 5 :(得分:0)

经过长时间的研究,我可以为各种自动化环境(例如Azure devops)运行sn.ex。我的代码在这里:

Start-Process cmd.exe 
Sleep 3
$WshShell = New-Object -ComObject WScript.Shell
Sleep 3
$WshShell.sendkeys(".\sn.exe -i  $PfxCertificatePath  VS_KEY_10D1C85C6387479B{Enter}");
Sleep 3;
$WshShell.sendkeys("**password**{Enter}");
Sleep 3;
$WshShell.sendkeys("{Enter}");

答案 6 :(得分:0)

使用WinAPI,我们可以这样做:

Param(
    [string]  $Password ,
    [string] $CertFilePath 
)

function ExecuteCommand([string]$message)
{
    try{
    foreach ($char in [char[]]$message) {
    [void][WPIA.ConsoleUtils]::PostMessage($handle, [WPIA.ConsoleUtils]::WM_CHAR, [int]$char, 0) 
    }
    [void][WPIA.ConsoleUtils]::PostMessage($handle, [WPIA.ConsoleUtils]::WM_CHAR, 13, 0)
    Sleep 3
    }catch{
    }
}

Add-Type -Name ConsoleUtils -Namespace WPIA -MemberDefinition @'
[DllImport("user32.dll")]
public static extern int PostMessage(int hWnd, uint Msg, int wParam, int lParam);
public const int WM_CHAR = 0x0102;
'@

$Win32API = Add-Type -Name Funcs -Namespace Win32 -PassThru -MemberDefinition @'
    [DllImport("user32.dll", SetLastError = true)]
    public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

    [DllImport("user32.dll", SetLastError = true)]
    public static extern IntPtr FindWindow(string lpClassName, IntPtr lpWindowName);

    [DllImport("user32.dll", SetLastError = true)]
    public static extern IntPtr FindWindow(IntPtr lpClassName, string lpWindowName);

    [DllImport("user32.dll", SetLastError = true)]
        public static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string className,  string  windowTitle);

    [DllImport("kernel32.dll")]
  public static extern uint GetLastError();
'@

[System.Reflection.Assembly]::Load("Microsoft.Build.Tasks.v4.0, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")
[System.Reflection.Assembly]::Load("Microsoft.Build.Utilities.v4.0, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")
$publish = New-Object Microsoft.Build.Tasks.ResolveKeySource
$publish.KeyFile=$CertFilePath
try
{
        $publish.Execute()
}
catch{
        $VsKey= [regex]::match($error[0].Exception,'VS_KEY_[A-F0-9]+').Value
        $VsKey =$VsKey -replace "`n|`r"
}
$quotedCertPath='"'+$CertFilePath+'"'
Write-Host 'VsKey='$VsKey

start cmd
$proc=Get-Process | Where-Object {$_.Name -like "*cmd*"}
$proc.MainWindowTitle
$handle = $proc.MainWindowHandle
'1:'+$handle
if($handle -eq 0){
    $handle=$Win32API::FindWindow([IntPtr]::Zero, 'C:\WINDOWS\system32\cmd.exe')
    '2:'+$handle
}   
if($handle -eq 0){
    $handle=$Win32API::FindWindow('C:\WINDOWS\system32\cmd.exe',       [IntPtr]::Zero)
    '3:'+$handle
}
if($handle -eq 0){
    $proc2 = Start-Process cmd.exe -PassThru
    $proc2
    Get-Process -id $proc2.Id
    Sleep 3;
    $handle = (Get-Process -id $proc2.Id).MainWindowHandle
    $handle
    $proc.MainWindowHandle
    $proc.Refresh()
    Sleep 3;
    $proc.MainWindowHandle
}


Write-Host 'Handle='$handle

ExecuteCommand 'echo Starting > d:\a\1\s\Authenticode\log.txt'  
$SnCommand=[string]::Format(".\sn.exe -i {0} {1}",$quotedCertPath,$VsKey)
ExecuteCommand $SnCommand
ExecuteCommand $Password