支持 Https 的 Localhost HttpListener - 一段时间后停止工作
Localhost HttpListener with Https support - stops working after a period of time
我已经使用自签名证书设置了一个测试 C# Https 侦听器(遵循 中的建议)。
该服务(在本地系统下作为 windows 服务运行)和 最初(启动后)运行良好。
在一段时间(~1.5 小时)后,对 https 端点的调用停止工作,Edge/IE 抱怨:
无法安全连接到此页面
这可能是因为该网站使用了过时或不安全的 TLS 安全设置。如果这种情况持续发生,请尝试联系网站所有者。
Chrome投诉如下:
无法访问此站点
连接被重置。
ERR_CONNECTION_RESET
发生这种情况时,检查证书存储会显示证书(在 Root 和 My 存储中)仍然存在。
正在检查
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);
然后将新生成的证书添加到本地根目录和私有存储:
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}
我遇到的问题如下:
当您在 .NET 中创建自签名证书时,您构建了一个 X509Certificate(2) 的实例。
如果您不指定
X509KeyStorageFlags.PersistKeySet
flag,那么在对您的证书的所有引用都超出我们的范围后,与您的证书对应的私钥最终将被垃圾收集删除(因此问题不是立即重现,而是在一段时间后重现)。
要保留证书的私钥,您需要如上所述指定标志。
自签名证书创建的最终工作代码如下:
// 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);
我已经使用自签名证书设置了一个测试 C# Https 侦听器(遵循 中的建议)。
该服务(在本地系统下作为 windows 服务运行)和 最初(启动后)运行良好。 在一段时间(~1.5 小时)后,对 https 端点的调用停止工作,Edge/IE 抱怨:
无法安全连接到此页面 这可能是因为该网站使用了过时或不安全的 TLS 安全设置。如果这种情况持续发生,请尝试联系网站所有者。
Chrome投诉如下:
无法访问此站点 连接被重置。 ERR_CONNECTION_RESET
发生这种情况时,检查证书存储会显示证书(在 Root 和 My 存储中)仍然存在。
正在检查
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);
然后将新生成的证书添加到本地根目录和私有存储:
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}
我遇到的问题如下:
当您在 .NET 中创建自签名证书时,您构建了一个 X509Certificate(2) 的实例。
如果您不指定
X509KeyStorageFlags.PersistKeySet
flag,那么在对您的证书的所有引用都超出我们的范围后,与您的证书对应的私钥最终将被垃圾收集删除(因此问题不是立即重现,而是在一段时间后重现)。
要保留证书的私钥,您需要如上所述指定标志。
自签名证书创建的最终工作代码如下:
// 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);