Я использую класс MSDN sslStream для TCP-сервера / клиента.Я создаю X509Certificate2, используя пакет NuGet для надувного замка.
Мой код сначала создает сертификат, затем преобразует его в сертификат .NET и помещает в хранилище локальных компьютеров / личных сертификатов.После того, как сертификат помещен в хранилище, я затем программно экспортирую сертификат .cer в папку установки моей программы на диске C.
Если я запускаю свое приложение в режиме отладки в Visual Studio, все шаги выполняются безпроблемы, и я могу запустить TCP-сервер, используя поток SSL.В моем сценарии я просто использую сертификат для проверки сервера из соединения с клиентом TCP и использую его исключительно для целей шифрования.
Однако - производственная сборка для моей программы должна работать как служба Windows, в которойЯ использую пакет TopShelf NuGet.
Проблема, с которой я столкнулся, заключается в том, что при запуске той же программы, что и служба Windows, у меня возникают проблемы с разрешениями.Если я запускаю программу в Visual Studio и тестирую TCP Client & Server на той же машине, все работает нормально.Я предполагаю, что это потому, что класс SSL Stream обращается к сертификату X509 из хранилища сертификатов с помощью учетной записи администратора Windows и, следовательно, может получить доступ к информации о секретном ключе.
Однако, если я запускаю ту же программукак служба Windows, т. е. изначально создавая сертификат, а затем пытаясь запустить TCP-сервер, у меня возникают проблемы, связанные с разрешениями на доступ к информации личного ключа из сертификата X509 в хранилище сертификатов.
Iне может решить, что необходимо для успешного запуска программы при ее запуске в качестве службы Windows.Обратите внимание, что добавление шагов ручной настройки в процесс не является вариантом, поскольку эта программа предназначена для установки обычным пользователем, и все настройки выполняются программно, и им не нужно беспокоиться о ...
Код нижекласс, который я использую для создания сертификатов, еще раз подчеркиваю, что это работает при работе в режиме отладки, когда вы вошли в Windows как администратор, потому что при запуске той же программы, что и служба Windows, происходит сбой ...
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;
}
}