具备HTTPS支持的Httplistener

79
关于使.NET HTTPListener支持HTTPS,有很多混乱的,有时矛盾的信息。据我了解,如下:
  • 你的C#代码需要在端口号前面加上https前缀(例如https://*:8443),以便监听器理解它需要在该端口服务SSL请求。

  • 实际的SSL握手在后台进行,并由Windows机器上的http.sys处理。C#代码不必显式地管理SSL握手,因为它是在后台完成的。

  • 你需要在httpListener的机器上拥有一个“X.509信任证书”,并且该证书需要绑定到端口8443(以此为例)。

我的理解是否正确?如果不是,请给我指点一下。

关于X.509证书,我的理解是:

  • 使用makecert创建X.509证书。此证书存储在个人存储中,并需要移动到Trusted Store(这是HTTP监听器将查找的地方)。似乎我可以使用certMgr进行移动,或者我可以使用mmc来影响移动。看起来有不止一种X.509证书格式(DERBase64pks,pwsd受保护,pks私有等)。是否有首选的格式我应该使用?

将证书放入可信存储后,需要将其绑定到TCP端口。我在Windows 7上:应该使用httpcfg还是netsh呢?

6个回答

96

我完成了一堆作业,并使其工作起来了。为.NET HttpListener添加SSL支持的步骤如下:

  1. Update C# application code to include the https prefix. Example:

    String[] prefixes = { "http://*:8089/","https://*:8443/" };
    

    That's it from the code aspect.

  2. For the certificate side of things, using the Windows SDK command console or Visual Studio Professional command console

    • Use makecert.exe to create a certificate authority. Example:

      makecert -n "CN=vMargeCA" -r -sv vMargeCA.pvk vMargeCA.cer
      
    • Use makecert.exe to create an SSL certificate

      makecert -sk vMargeSignedByCA -iv vMargeCA.pvk -n "CN=vMargeSignedByCA" -ic vMargeCA.cer vMargeSignedByCA.cer -sr localmachine -ss My
      
    • Use MMC GUI to install CA in Trusted Authority store

    • Use MMC GUI to install an SSL certificate in Personal store
    • Bind certificate to IP address:port and application. Example:

      netsh http add sslcert ipport=0.0.0.0:8443 certhash=585947f104b5bce53239f02d1c6fed06832f47dc appid={df8c8073-5a4b-4810-b469-5975a9c95230}
      

      The certhash is the thumbprint from your SSL certificate. You can find this using mmc. The appid is found in Visual Studio...usually in assembly.cs, look for the GUID value.

可能有其他方法可以完成上述任务,但这个方法对我来说有效。


哎呀,我按照这些提示尝试做了一切,但是我无法通过最后一步 - 它说某个参数无效... - Tomasz Kapłoński
5
我注意到当我将文字复制粘贴到命令行时,有时在“certhash=”和实际密钥之间会出现一个“?”。请仔细检查您的输入。 - jklemmack
2
有没有办法将根CA证书链接到中间证书? - Hugh Jeffner
@Moby04 你是不是使用 PowerShell?看看我的回答,有一个解决方法。 - jpaugh
1
@WalterKelt 只是猜测,但可能是可执行项目属性文件夹中的 AssemblyInfo 文件中的 Guid。 - Chris Klepeis
显示剩余7条评论

47
以下是我按照您的要求翻译的内容:

以下是详细步骤,我使用OpenSSL在Windows上设置独立服务器并为C# HTTPListener应用程序创建自签名证书。如果您需要进一步了解,本文中还包含了许多链接。

  1. Create a stand-alone server in .NET via HttpListener:

     var prefixes = {"http://localhost:8080/app/root", "https://localhost:8443/app/root"};
     var listener = new HttpListener();
     foreach (string s in prefixes)
         listener.Prefixes.Add(s);
     listener.Start();
    
  2. Create self-signed certificate:*

  3. openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365, which will prompt you for the value of each of the certificate's fields on the command line. For the common name, type the domain name (e.g. localhost)

  4. openssl pkcs12 -inkey bob_key.pem -in bob_cert.cert -export -out bob_pfx.pfx, so that it can be imported with its key on the target machine.

*如果您想使用makecert的替代方法,请参考Walter自己的答案

  1. 打开本地计算机的证书管理器。当您运行certmgr.msc时,它会打开当前用户的证书管理器,这不是我们想要的。相反,请运行certlm.msc [感谢@Arkane],如果这样不起作用,则:

    1. 从目标计算机的管理员命令提示符上运行mmc
    2. 按下Ctrl + M,或单击文件>添加/删除控制台
    3. 选择证书,然后单击添加 >
    4. 在出现的对话框中,选择计算机帐户,然后单击下一步
    5. 选择本地计算机。单击完成,然后单击确定
  2. 将证书(pfx)导入目标计算机上的Windows证书存储

  3. 在先前打开的mmc窗口中,展开到证书(本地计算机)>个人

  4. 右键单击个人,然后单击所有任务->导入...

  5. 在出现的对话框的第二个屏幕中,找到并导入您的证书。您将不得不将文件类型筛选器更改为个人信息交换所有文件才能找到它

  6. 在下一个屏幕上,输入您在步骤2.1中选择的密码,并仔细注意第一个复选框。这决定了您的证书存储的安全性以及使用的便捷性

  7. 在最后一个屏幕上,选择将所有证书放入以下存储。验证它是否说个人,然后单击完成

  8. 对于受信任的根证书颁发机构证书部分,请重复上述导入过程。

  9. 为您的应用程序创建端口关联。在Windows Vista及更高版本中,请使用netsh,就像我所做的那样。(对于Windows XP及更早版本,请使用httpcfg

  • From the administrative command line, type the following to set up the SSL binding* to your app, and the appropriate port. NB: This command is easy to get wrong, because (in PowerShell) the braces need to be escaped. The following PowerShell command will work:

        netsh http add sslcert ipport=0.0.0.0:8443 `
            certhash=110000000000003ed9cd0c315bbb6dc1c08da5e6 `
            appid=`{00112233-4455-6677-8899-AABBCCDDEEFF`}
    

    For cmd.exe, the following should be used instead:

        netsh http add sslcert ipport=0.0.0.0:8443 certhash=110000000000003ed9cd0c315bbb6dc1c08da5e6 appid={00112233-4455-6677-8899-AABBCCDDEEFF}
    
    • The ipport parameter will cause the SSL certificate to bind to the port 8443 on every network interface; to bind to a specific interface (only), choose the IP address associated with that network interface.
    • The certhash is simply the certificate thumbprint, with spaces removed
    • The appid is the GUID stored in the Assembly Info of your application. (Sidenote: The netsh mechanism is evidently a COM interface, judging from this question and its answers)

    * Microsoft has redirected the SSL Binding link from here to there.

  1. 启动您的Web服务器,然后就可以开始了!

当我在安装证书的机器上运行netsh命令时,出现了错误“SSL证书添加失败,错误1312-指定的登录会话不存在。它可能已经被终止。”查看这个问题的答案,似乎证书应该在个人存储中才能通过netsh进行安装(它讨论了使用my而不是root运行certutil):https://dev59.com/22cs5IYBdhLWcg3wLAyf#19766650 - Jez
5
在我的Windows电脑上,使用Git-(Bash)-for-Windows生成.pfx文件的命令会出现停顿现象。解决方法是在该命令前添加winpty,参考openssl-hangs-during-pkcs12-export - dwettstein
1
谢谢您的时间,但我自己忘记了我何时评论过它,但我已经为您的答案点赞,所以我猜您的答案对我有用,谢谢 :) - subha
1
对于未来寻找其项目GUID的读者,需要注意的是,使用Visual Studio时,有些项目不再具有程序集信息文件。如果是这种情况,您的项目GUID应该在解决方案文件中。只需在记事本中打开您的.sln文件,查找类似于{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}的内容即可。 - SparkleStep
1
对于第3步,我认为您可以使用 certlm.msc 打开本地机器的证书管理器。(它适用于W10和Windows Server 2016 - 不确定其他版本是否可用) - Arkane
显示剩余4条评论

7
我们可以使用PowerShell和C#来导入证书(无需手动步骤)。
详情请参见:https://blog.davidchristiansen.com/2016/09/howto-create-self-signed-certificates-with-powershell/ 我正在使用以下代码:
/// <summary>
/// Create and install a self-signed certificate for HTTPS use
/// </summary>
private static void CreateInstallCert(int expDate, string password, string issuedBy)
{
    // Create/install certificate
    using (var powerShell = System.Management.Automation.PowerShell.Create())
    {
        var notAfter = DateTime.Now.AddYears(expDate).ToLongDateString();
        var assemPath = Assembly.GetCallingAssembly().Location;
        var fileInfo = new FileInfo(assemPath);
        var saveDir = Path.Combine(fileInfo.Directory.FullName, "CertDir");
        if (!Directory.Exists(saveDir))
        {
            Directory.CreateDirectory(saveDir);
        }

        // This adds certificate to Personal and Intermediate Certification Authority
        var rootAuthorityName = "My-RootAuthority";
        var rootFriendlyName = "My Root Authority";
        var rootAuthorityScript =
            $"$rootAuthority = New-SelfSignedCertificate" +
            $" -DnsName '{rootAuthorityName}'" +
            $" -NotAfter '{notAfter}'" +
            $" -CertStoreLocation cert:\\LocalMachine\\My" +
            $" -FriendlyName '{rootFriendlyName}'" +
            $" -KeyUsage DigitalSignature,CertSign";
        powerShell.AddScript(rootAuthorityScript);

        // Export CRT file
        var rootAuthorityCrtPath = Path.Combine(saveDir, "MyRootAuthority.crt");
        var exportAuthorityCrtScript =
            $"$rootAuthorityPath = 'cert:\\localMachine\\my\\' + $rootAuthority.thumbprint;" +
            $"Export-Certificate" +
            $" -Cert $rootAuthorityPath" +
            $" -FilePath {rootAuthorityCrtPath}";
        powerShell.AddScript(exportAuthorityCrtScript);

        // Export PFX file
        var rootAuthorityPfxPath = Path.Combine(saveDir, "MyRootAuthority.pfx");
        var exportAuthorityPfxScript =
            $"$pwd = ConvertTo-SecureString -String '{password}' -Force -AsPlainText;" +
            $"Export-PfxCertificate" +
            $" -Cert $rootAuthorityPath" +
            $" -FilePath '{rootAuthorityPfxPath}'" +
            $" -Password $pwd";
        powerShell.AddScript(exportAuthorityPfxScript);

        // Create the self-signed certificate, signed using the above certificate
        var gatewayAuthorityName = "My-Service";
        var gatewayFriendlyName = "My Service";
        var gatewayAuthorityScript =
            $"$rootcert = ( Get-ChildItem -Path $rootAuthorityPath );" +
            $"$gatewayCert = New-SelfSignedCertificate" +
            $" -DnsName '{gatewayAuthorityName}'" +
            $" -NotAfter '{notAfter}'" +
            $" -certstorelocation cert:\\localmachine\\my" +
            $" -Signer $rootcert" +
            $" -FriendlyName '{gatewayFriendlyName}'" +
            $" -KeyUsage KeyEncipherment,DigitalSignature";
        powerShell.AddScript(gatewayAuthorityScript);

        // Export new certificate public key as a CRT file
        var myGatewayCrtPath = Path.Combine(saveDir, "MyGatewayAuthority.crt");
        var exportCrtScript =
            $"$gatewayCertPath = 'cert:\\localMachine\\my\\' + $gatewayCert.thumbprint;" +
            $"Export-Certificate" +
            $" -Cert $gatewayCertPath" +
            $" -FilePath {myGatewayCrtPath}";
        powerShell.AddScript(exportCrtScript);

        // Export the new certificate as a PFX file
        var myGatewayPfxPath = Path.Combine(saveDir, "MyGatewayAuthority.pfx");
        var exportPfxScript =
            $"Export-PfxCertificate" +
            $" -Cert $gatewayCertPath" +
            $" -FilePath {myGatewayPfxPath}" +
            $" -Password $pwd"; // Use the previous password
        powerShell.AddScript(exportPfxScript);

        powerShell.Invoke();
    }
}

需要 PowerShell 4 或更高版本。


我的 VS 中缺少 Path.Combine 和 System.Management。 - Thomas Adrian

5
以下命令生成有效期为10年的本地主机自签名证书,将其导入到本地计算机存储,并在输出中显示Thumbprint(certhash):
powershell -Command "New-SelfSignedCertificate -DnsName localhost -CertStoreLocation cert:\LocalMachine\My -NotAfter (Get-Date).AddYears(10)"

然后您可以从输出中复制Thumbprint,并使用netsh.exe将证书附加到localhost:443,例如:

netsh http add sslcert ipport=localhost:443 certhash=110000000000003ed9cd0c315bbb6dc1c08da5e6 appid={00112233-4455-6677-8899-AABBCCDDEEFF}

适用于 Windows 8 或更高版本。需要管理员权限。


1
这听起来非常高效和简单。问题 - 什么是appid?-我从哪里获取它? - Gogu CelMare
1
@GoguCelMare 这个答案可能会对你有所帮助 https://dev59.com/nHRB5IYBdhLWcg3wuZU1 - Dima Fomin
1
我发现我必须使用0.0.0.0作为主机名,而不是localhost,否则一切都运行得很好。 - crazyhor77

1
由于在答案中制作自己的自签名证书对我没有用,而且问题明确要求使.NET HTTPListener具备HTTPS功能并提供任何提示/建议,因此我想分享我的方法。
您需要一个主机名,比如www.made-up.com,它需要指向您的WAN IP地址(例如,询问您的主机提供者获取说明),并将其端口(例如443)转发到您的本地计算机。不要忘记在本地计算机的防火墙中打开该入站443端口。
我使用了https://letsencrypt.org/。在Windows上,这并不像在Linux上那样容易,因为没有官方的certbot ACME客户端适用于Windows。但是,您可以使用https://github.com/Lone-Coder/letsencrypt-win-simple,其中也有二进制文件。但是“目前仅支持IIS”。但是,您可以轻松地欺骗它,在您的计算机上创建证书,以便您可以通过SSL方式访问您的HTTP监听器:
  1. 安装IIS(通过Windows功能开/关),在IIS中创建一个网站并分配主机名。同时,将其设置为安全网站(443端口)。
  2. 运行letsencrypt-win-simple EXE文件(我使用的是1.9.1版本)。回答问题以让它生成证书。
  3. 之后,您可以停止IIS服务器。

我认为您需要注意生成的刷新任务,因为我不确定在几个月后它是否会成功(您可能需要重新启动IIS以更新证书)。


Certbot可以与Cygwin和IIS一起使用,如果您能够添加“。”->“text / html” MIME类型。执行“pip3 install certbot”,然后将Web根目录挂载到标准的POSIX路径中,最后运行“certbot certonly”。虽然不是最顺畅的方法,但它确实有效。 - David Dombrowsky

0
以下是使其正常工作的简单步骤(在c#7.0控制台应用程序、vs2022、win10中测试通过)

使用HTTPListener与HTTPS

  • VS:c# httplistener项目,添加listener.Prefixes.Add($"https://192.168.1.xxx:4443/"); *或其他ip/port
  • 浏览器:尝试连接,出现错误:"安全连接失败:错误代码:PR_CONNECT_RESET_ERROR"
  • PowerShell(以管理员身份运行)使用下面的代码片段获取certhash:
$certs = Get-ChildItem -Path Cert:\LocalMachine\My
foreach ($cert in $certs) {
    $thumbprint = $cert.Thumbprint
    $appid = [System.Guid]::NewGuid()
    Write-Host "Certificate certhash: $thumbprint"
    #Write-Host "Application ID (appid): $appid"
}

选择其中一个(通常第二个是正确的?) "appid"来自您的VS Studio *.sln解决方案文件,SolutionGuid = 命令提示符(以管理员身份):netsh http add sslcert ipport=0.0.0.0:4443 certhash=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx appid={xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} 浏览器测试重新加载:https://192.168.1.xxx:4443 浏览器:关于不安全证书的警告(ERR_CERT_AUTHORITY_INVALID),点击高级,接受并继续 完成!
可选项:
打开防火墙端口以接受传入的TCP 4443(如果您希望其他局域网中的计算机连接到您的Web服务器)
卸载:
命令提示符(以管理员身份):netsh http delete sslcert ipport=0.0.0.0:4443
故障排除:
SSL证书添加失败,错误:1312 指定的登录会话不存在。可能已经被终止。
使用netsh命令时,certhash不正确。尝试使用返回列表中的其他值。
参数不正确。
在netsh命令中,不要使用引号或单引号来表示appid='{...}'。
安全连接失败:错误代码:证书类型未经批准用于应用程序,SEC_ERROR_INADEQUATE_CERT_TYPE。
使用错误的appid(从powershell列表中获取),请使用另一个。
资源
链接到gist https://gist.github.com/unitycoder/ec217d20eecc2dfaf8d316acd8c3c5c5

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接