具有Https支持的Localhost HttpListener - 在一段时间后停止工作

时间:2017-10-08 23:44:53

标签: c# ssl self-signed httplistener netsh

我已经使用自签名证书设置了一个测试C#Https Listener(遵循Httplistener with https support中的建议)。

该服务(在本地系统下作为Windows服务运行)和最初(启动后)运行良好。 经过一段时间(约1.5小时)后,呼叫https端点停止使用Edge / IE抱怨:

无法安全地连接到此页面 这可能是因为该站点使用过时或不安全的TLS安全设置。如果发生这种情况,请尝试联系网站所有者。

Chrome抱怨如下:

无法访问此网站 连接被重置。 ERR_CONNECTION_RESET

发生这种情况时,检查证书存储区会显示证书(在Root和My stores中)仍然存在。

检查

  

netsh http show sslcert

同时显示证书到端口的注册仍然存在。

重新启动应用程序(重新创建,重新安装并将证书重新绑定到C#http(s)侦听器侦听的端口)会有所帮助,但直到下一次可能发生的打嗝(~1.5。 .2小时?)。

我知道侦听器线程仍处于活动状态,因为不安全端口上的请求仍然有效。

在此期间发生了一些事情,我无法弄清楚是什么......

代码:

所有这些都从一代自签名证书开始:

        // create DN for subject and issuer
        var dn = new CX500DistinguishedName();
        dn.Encode("CN=localhost");

        // create a new private key for the certificate
        var privateKey = new CX509PrivateKey
        {
            ProviderName = "Microsoft Base Cryptographic Provider v1.0",
            MachineContext = false,
            Length = 2048,
            KeySpec = X509KeySpec.XCN_AT_SIGNATURE,
            KeyUsage = X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_SIGNING_FLAG,
            FriendlyName = "Application Testing Key",
            ExportPolicy = X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_EXPORT_FLAG
        };
        privateKey.Create();

        var hashobj = new CObjectId();
        hashobj.InitializeFromAlgorithmName(ObjectIdGroupId.XCN_CRYPT_HASH_ALG_OID_GROUP_ID,
            ObjectIdPublicKeyFlags.XCN_CRYPT_OID_INFO_PUBKEY_ANY,
            AlgorithmFlags.AlgorithmFlagsNone, "SHA256");

        // Create the self signing request
        var certificateRequest = new CX509CertificateRequestCertificate();
        certificateRequest.InitializeFromPrivateKey(X509CertificateEnrollmentContext.ContextUser, privateKey, String.Empty);
        certificateRequest.Subject = dn;
        certificateRequest.Issuer = dn; // the issuer and the subject are the same
        certificateRequest.NotBefore = DateTime.UtcNow.AddDays(-1);
        certificateRequest.NotAfter = DateTime.UtcNow.AddYears(10);
        certificateRequest.HashAlgorithm = hashobj;

        // Set up the Subject Alternative Names extension.
        var nameslist = new CAlternativeNames();
        var alternativeName = new CAlternativeName();
        alternativeName.InitializeFromString(AlternativeNameType.XCN_CERT_ALT_NAME_DNS_NAME, "localhost");
        nameslist.Add(alternativeName);
        var subjectAlternativeNamesExtension = new CX509ExtensionAlternativeNames();
        subjectAlternativeNamesExtension.InitializeEncode(nameslist);
        certificateRequest.X509Extensions.Add((CX509Extension)subjectAlternativeNamesExtension);

        var skiExtension = new CX509ExtensionSubjectKeyIdentifier();
        skiExtension.InitializeEncode(EncodingType.XCN_CRYPT_STRING_BASE64, Convert.ToBase64String(StringToByteArray(certSKI)));
        certificateRequest.X509Extensions.Add((CX509Extension)skiExtension);

        certificateRequest.Encode();

        // Do the final enrollment process
        var enroll = new CX509Enrollment();
        enroll.InitializeFromRequest(certificateRequest); // load the certificate
        enroll.CertificateFriendlyName = "Application Testing Cert";
        var csr = enroll.CreateRequest(); // Output the request in base64
        var pwd = Guid.NewGuid().ToString();
        enroll.InstallResponse(InstallResponseRestrictionFlags.AllowUntrustedCertificate, csr, EncodingType.XCN_CRYPT_STRING_BASE64, pwd); // and install it back as the response
        var base64encoded = enroll.CreatePFX(pwd, PFXExportOptions.PFXExportChainWithRoot);

        // instantiate the target class with the PKCS#12 data 
        return new X509Certificate2(Convert.FromBase64String(base64encoded), pwd);

然后将新生成的证书添加到localhost Root和Private store:

InstallCertificateToCertStore(cert, new X509Store(StoreName.Root, StoreLocation.LocalMachine));
InstallCertificateToCertStore(cert, new X509Store(StoreName.My, StoreLocation.LocalMachine));

然后将新创建和安装的证书注册到Http Listener侦听的端口:

netsh.exe http add sslcert ipport=0.0.0.0:{port} certhash={certThumbprint} appid={appid_guid} 

1 个答案:

答案 0 :(得分:0)

我遇到的问题如下:

在.NET中创建自签名证书时,您构造了X509Certificate(2)的实例。

如果您未指定

  

X509KeyStorageFlags.PersistKeySet

标志,那么与证书相对应的私钥最终将在您对证书的所有引用进入我们的范围之后通过垃圾收集来删除(因此问题不是立即复制,而是在一段时间之后)。

要保留证书的私钥,您需要指定上面提到的标志。

这是微软在这里提到的: https://support.microsoft.com/en-us/help/950090/installing-a-pfx-file-using-x509certificate-from-a-standard--net-appli

自签名证书创建的最终工作代码如下:

        // create DN for subject and issuer
        var dn = new CX500DistinguishedName();
        dn.Encode("CN=localhost");

        // create a new private key for the certificate
        var privateKey = new CX509PrivateKey
        {
            ProviderName = "Microsoft Base Cryptographic Provider v1.0",
            MachineContext = true,
            Length = 2048,
            KeySpec = X509KeySpec.XCN_AT_SIGNATURE,
            KeyUsage = X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_ALL_USAGES,
            FriendlyName = "App Testing Key",
            ExportPolicy = X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_EXPORT_FLAG
        };
        privateKey.Create();

        var hashobj = new CObjectId();
        hashobj.InitializeFromAlgorithmName(ObjectIdGroupId.XCN_CRYPT_HASH_ALG_OID_GROUP_ID,
            ObjectIdPublicKeyFlags.XCN_CRYPT_OID_INFO_PUBKEY_ANY,
            AlgorithmFlags.AlgorithmFlagsNone, "SHA256");

        // Create the self signing request
        // also see: https://security.stackexchange.com/a/103362
        var certificateRequest = new CX509CertificateRequestCertificate();
        certificateRequest.InitializeFromPrivateKey(X509CertificateEnrollmentContext.ContextMachine, privateKey, String.Empty);
        certificateRequest.Subject = dn;
        certificateRequest.Issuer = dn; // the issuer and the subject are the same
        certificateRequest.NotBefore = DateTime.UtcNow.AddDays(-1);
        certificateRequest.NotAfter = DateTime.UtcNow.AddYears(10);
        certificateRequest.HashAlgorithm = hashobj;

        // Set up the Subject Alternative Names extension.
        var nameslist = new CAlternativeNames();
        var alternativeName = new CAlternativeName();
        alternativeName.InitializeFromString(AlternativeNameType.XCN_CERT_ALT_NAME_DNS_NAME, "localhost");
        nameslist.Add(alternativeName);
        var subjectAlternativeNamesExtension = new CX509ExtensionAlternativeNames();
        subjectAlternativeNamesExtension.InitializeEncode(nameslist);
        certificateRequest.X509Extensions.Add((CX509Extension)subjectAlternativeNamesExtension);

        var skiExtension = new CX509ExtensionSubjectKeyIdentifier();
        skiExtension.InitializeEncode(EncodingType.XCN_CRYPT_STRING_BASE64, Convert.ToBase64String(StringToByteArray(certSKI)));
        certificateRequest.X509Extensions.Add((CX509Extension)skiExtension);

        certificateRequest.Encode();

        // Do the final enrollment process
        var enroll = new CX509Enrollment();
        enroll.InitializeFromRequest(certificateRequest); // load the certificate
        enroll.CertificateFriendlyName = "App Testing Cert";
        var csr = enroll.CreateRequest(); // Output the request in base64
        var pwd = Guid.NewGuid().ToString();
        enroll.InstallResponse(InstallResponseRestrictionFlags.AllowUntrustedCertificate, csr, EncodingType.XCN_CRYPT_STRING_BASE64, pwd); // and install it back as the response
        var base64encoded = enroll.CreatePFX(pwd, PFXExportOptions.PFXExportChainWithRoot);

        // instantiate the target class with the PKCS#12 data 
        return new X509Certificate2(Convert.FromBase64String(base64encoded), pwd, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);