我正在将MSDN sslStream类用于TCP服务器/客户端。 我使用充气城堡NuGet包创建了X509Certificate2。
我的代码首先创建证书,然后将其转换为.NET证书并将其放入本地计算机/个人证书存储中。将证书放入存储区后,我将以编程方式将.cer证书导出到C盘上的程序安装文件夹中。
如果我在Visual Studio中的调试模式下运行应用程序,则所有步骤都将执行而没有任何问题,并且我可以使用SSL流运行TCP Server。在我的方案中,我只是使用证书从TCP客户端连接中验证服务器,并将其纯粹用于加密目的。
但是-我程序的生产版本需要作为Windows服务运行,我可以在其中使用TopShelf NuGet软件包。
我面临的挑战是,当运行与Windows Service相同的程序时,我会遇到权限问题。如果我在Visual Studio中运行该程序并在同一台计算机上测试TCP客户端和服务器,则一切正常。我猜这是因为SSL Stream类正在使用Windows管理员帐户从证书存储区访问X509证书,因此能够访问私钥信息。
但是,如果我运行与Windows Service相同的程序,即首先创建证书,然后尝试运行TCP Server,则会出现问题,该问题似乎与X509证书中对私钥信息的访问权限有关。证书存储。
当程序作为Windows服务运行时,我无法解决使程序成功运行所需的条件。请注意,在该过程中添加手动配置步骤不是一个选择,因为该程序旨在由普通用户安装,并通过编程方式进行所有设置,而不用让他们担心...
下面的代码是我用于创建证书的类,我再次强调,当以管理员身份登录Windows并在调试模式下运行时,它确实可以工作,这是在运行与Windows Service相同的程序时全部失败...
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
// User Added
using System.IO;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Operators;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities;
using Org.BouncyCastle.X509;
using System.Security.Cryptography.X509Certificates;
using Org.BouncyCastle.Crypto.Prng;
namespace WindowsService
{
class X509Certification
{
private static readonly log4net.ILog log = log4net.LogManager.GetLogger("SystemLogsRollingFileAppender");
readonly static string certificateFilePath = System.Configuration.ConfigurationManager.AppSettings["CertificateFilePath"];
// String used to set the subject name of the X509 Certificate.
public static string subjectName = "MyCertificateSubject";
public static void CheckIfCertificateExists()
{
//X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
try
{
var certificates = store.Certificates.Find(X509FindType.FindBySubjectName, subjectName, false);
if (certificates != null && certificates.Count > 0)
{
log.Info("CHECK for X509 Certificate in localmachine certificate store = OK");
}
else
{
X509Certificate2 certificate = GenerateCertificate();
SaveCertificate(certificate);
}
}
catch (Exception ex)
{
log.Error(ex);
SystemEvents.X509CertificateExceptions(ex);
}
}
private static void SaveCertificate(X509Certificate2 certificate)
{
var userStore = new X509Store(StoreName.My, StoreLocation.LocalMachine);
userStore.Open(OpenFlags.ReadWrite);
userStore.Add(certificate);
userStore.Close();
log.Info("X509 Certificate created and added to localmachine certificate store = OK");
// Export a DER encoded binary X.509 (.CER) certificates for SSL Server Validation.
File.WriteAllBytes(certificateFilePath, certificate.Export(X509ContentType.Cert));
log.Info("DER encoded binary X.509 (.CER) certifcate created and added to My Program Windows Directory on C:Drive = OK");
}
/// <summary>
/// Method used to create a self-signed server certificate using C# and the Bouncy Castle .NET API.
/// </summary>
/// <returns></returns>
public static X509Certificate2 GenerateCertificate()
{
// ----- Generating Random Numbers -----
// We’re going to need some random numbers later, so create a RNG first.
var randomGenerator = new CryptoApiRandomGenerator();
var random = new SecureRandom(randomGenerator);
// ----- The Certificate Generator -----
// Then we need a certificate generator:
var certificateGenerator = new X509V3CertificateGenerator();
// ---- Serial Number -----
// The certificate needs a serial number. This is used for revocation, and usually should be an incrementing
// index (which makes it easier to revoke a range of certificates).
var serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(Int64.MaxValue), random);
certificateGenerator.SetSerialNumber(serialNumber);
// ----- Issuer and Subject Name -----
// We have to specify the issuer name and subject name. Since this is a self-signed certificate, these are the same.
//var subjectDN = new X509Name(subjectName);
var subjectDN = new X509Name($"C=NL, O=SomeCompany, CN={subjectName}");
var issuerDN = subjectDN;
certificateGenerator.SetIssuerDN(issuerDN);
certificateGenerator.SetSubjectDN(subjectDN);
// Note that Bouncy Castle allows you to omit the subject name, provided you specify a Subject Alternative Name (SAN).
// ----- Certificate Validation Period -----
// We need to specify a date range for which this certificate is valid:
certificateGenerator.SetNotBefore(DateTime.UtcNow.Date);
certificateGenerator.SetNotAfter(DateTime.UtcNow.Date.AddYears(20));
// ----- Subject Public Key -----
// We need to generate the important bit: the subject’s key pair. The public key goes into the certificate, and the private
// key remains private. In this example, strength is the key length, in bits. For RSA, 2048-bits should be considered the
// minimum acceptable these days.
const int strength = 2048;
var keyGenerationParameters = new KeyGenerationParameters(random, strength);
var keyPairGenerator = new RsaKeyPairGenerator();
keyPairGenerator.Init(keyGenerationParameters);
var subjectKeyPair = keyPairGenerator.GenerateKeyPair();
certificateGenerator.SetPublicKey(subjectKeyPair.Public);
// ----- Set the Signature Algorithmn & Generating the Certificate -----
// To generate the certificate, we need to provide the issuer’s private key.
// Because this is a self-signed certificate, this is the same as the subject private key.
// For certificates, the current recommendation seems to be SHA-256 or SHA-512.
var issuerKeyPair = subjectKeyPair;
const string signatureAlgorithm = "SHA256WithRSA";
var signatureFactory = new Asn1SignatureFactory(signatureAlgorithm, issuerKeyPair.Private);
var bouncyCert = certificateGenerator.Generate(signatureFactory);
// Note that you’ll still see “SHA1” for the “Thumbprint Algorithm” property.
// This is expected: the thumbprint is not the same as the signature.
// From the above, we’ve generated an X509v3 certificate using the Bouncy Castle libraries.
// ----- Convert to a .NET Certificate -----
// Next we need to convert the Bouncy Castle Certificate to a .NET Certificate. To do this we create a PKCS12 file.
// On Windows, these are more commonly known as .PFX files.
// Lets convert it to X509Certificate2
X509Certificate2 certificate;
Pkcs12Store store = new Pkcs12StoreBuilder().Build();
store.SetKeyEntry($"{subjectName}_key", new AsymmetricKeyEntry(subjectKeyPair.Private), new[] { new X509CertificateEntry(bouncyCert) });
string exportpw = Guid.NewGuid().ToString("x");
// Now we’ve got a Pkcs12Store, we can copy it to a stream.
using (var ms = new MemoryStream())
{
store.Save(ms, exportpw.ToCharArray(), random);
// At this point, we can convert it to a .NET X509Certificate2 object.
certificate = new X509Certificate2(ms.ToArray(), exportpw, X509KeyStorageFlags.Exportable);
}
// Now we’ve got a Pkcs12Store, we can copy it to a stream. For this part, we need to specify a password:
const string password = "password";
var stream = new MemoryStream();
store.Save(stream, password.ToCharArray(), random);
// Return the newly created certificate back to the callin method.
return certificate;
}
}