sslStream.AuthenticateAsServer ISSUES System.ComponentModel.Win32Exception (0x80004005): The credentials supplied to the package were not recognized

0

I am using the MSDN sslStream Class for a TCP Server/Client. I create an X509Certificate2 using bouncy castle NuGet package.

My code first creates the certificate, it then converts it to a .NET certificate and places it into the Local Machine / Personal Certificates Store. Once the certificate is placed in the store, I then programatically export a .cer certifcate to my program installation folder on C Drive.

If i run my application within debug mode in Visual Studio, all steps are performed without any issues and I can run the TCP Server using SSL Stream. In my scenario I'm just using the certificate for validating the server from the TCP Client connection and using it purely for encryption purposes.

However - the production build for my program needs to run as a Windows Service in which I ma using TopShelf NuGet package.

The challenge I have is that when running the same program as a Windows Service, i suffer problems with permissions. If i run the program within Visual Studio and test the TCP Client & Server on the same machine, everything works fine. I'm guessing this is because the SSL Stream class is accessing the X509 certificate from the certificate store using the Windows administrator account and therefore is able to access the private key information.

However, if I run the same program as a Windows Service i.e. initially creating the certificate and then trying to run the TCP Server, I get issues that seem to relate to the access permissions to the private key information from the X509 certicate in the certificates store.

I cant work out what is needed to allow the program to run successfully when its running as a Windows Service. Note adding manual configuration steps to the process is not an option given this program is intended to be installed by the common user with everything setup progrmatically and not left for them to worry about...

The code below is the class I use for creating the certificates, again I stress this does work when running in debug mode whilst logged into Windows as an administraor, its the fact that when running the same program as a Windows Service is where is it all fails...

 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;
    }
}
ssl
ssl-certificate
x509certificate2
sslstream
asked on Stack Overflow Jun 23, 2019 by OJB1

0 Answers

Nobody has answered this question yet.


User contributions licensed under CC BY-SA 3.0