Creating a certificate request with the Windows Certificate Enrollment API returns error "The parameter is incorrect. 0x80090027"

0

I'm trying to write a program which can generate a certificate and sign it with a company CA. The code runs fine locally on my Windows 10 machine, but once I deploy the program to a Windows Server 2012R2 server, it keeps returning:

{
  "ClassName": "System.Runtime.InteropServices.COMException",
  "Message": "CertEnroll::CX509Enrollment::_CreateRequest: The parameter is incorrect. 0x80090027 (-2146893785 NTE_INVALID_PARAMETER)",
  "Data": null,
  "InnerException": null,
  "HelpURL": null,
  "StackTraceString": "   at CERTENROLLLib.CX509EnrollmentClass.CreateRequest(EncodingType Encoding)\r\n   at Tools_api.Repository.Certification.CertificationFunc.GenerateCert(String dnsName, String name, Int32 phoneNumber)",
  "RemoteStackTraceString": null,
  "RemoteStackIndex": 0,
  "ExceptionMethod": "8\nCreateRequest\nInterop.CERTENROLLLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\nCERTENROLLLib.CX509EnrollmentClass\nSystem.String CreateRequest(CERTENROLLLib.EncodingType)",
  "HResult": -2146893785,
  "Source": "X509Enrollment.CX509Enrollment",
  "WatsonBuckets": null
}

The certificate generation code is as follows:

    public Object GenerateCert(string dnsName, string name, int phoneNumber)
    {
        using (new Impersonation(_foo, _faa, _pw, LogonType.LogonNewCredentials))
        {
            IX509CertificateRequestPkcs10 objPkcs10 = new CX509CertificateRequestPkcs10Class();
            IX509PrivateKey objPrivateKey = new CX509PrivateKeyClass();
            CCspInformation objCSP = new CCspInformationClass();
            CCspInformations objCSPs = new CCspInformationsClass();
            CX500DistinguishedName objDN = new CX500DistinguishedNameClass();
            IX509Enrollment objEnroll = new CX509EnrollmentClass();
            CObjectIds objObjectIds = new CObjectIdsClass();
            CObjectId objObjectIdClient = new CObjectIdClass();
            CObjectId objObjectIdServer = new CObjectIdClass();
            IX509ExtensionKeyUsage objExtensionKeyUsage = new CX509ExtensionKeyUsageClass();
            IX509ExtensionEnhancedKeyUsage objX509ExtensionEnhancedKeyUsage = new CX509ExtensionEnhancedKeyUsageClass();
            Certificate certificate = new Certificate();

            try
            {
                //  Initialize the csp object using the desired Cryptograhic Service Provider (CSP)
                objCSP.InitializeFromName("Microsoft RSA SChannel Cryptographic Provider");

                //  Add this CSP object to the CSP collection object
                objCSPs.Add(objCSP);

                objPrivateKey.Length = 2048;
                objPrivateKey.KeySpec = X509KeySpec.XCN_AT_KEYEXCHANGE;
                objPrivateKey.KeyUsage = X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_ALL_USAGES;
                objPrivateKey.MachineContext = true;
                objPrivateKey.ExportPolicy = X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_EXPORT_FLAG;
                objPrivateKey.ProviderType = X509ProviderType.XCN_PROV_RSA_SCHANNEL;

                //  Provide the CSP collection object (in this case containing only 1 CSP object)
                //  to the private key object
                objPrivateKey.CspInformations = objCSPs;

                //  Create the actual key pair
                objPrivateKey.Create();

                //  Initialize the PKCS#10 certificate request object based on the private key.
                //  Using the context, indicate that this is a machine certificate request and
                //  provide a template name
                objPkcs10.InitializeFromTemplateName(X509CertificateEnrollmentContext.ContextMachine, "RandomTemplate");

                // Enhanced Key Usage
                objObjectIdClient.InitializeFromValue("1.3.6.1.5.5.7.3.2"); // OID for Client Authentication usage
                objObjectIdServer.InitializeFromValue("1.3.6.1.5.5.7.3.1"); // OID for Server Authentication usage

                objObjectIds.Add(objObjectIdClient);
                objObjectIds.Add(objObjectIdServer);

                objX509ExtensionEnhancedKeyUsage.InitializeEncode(objObjectIds);

                // Encode the name
                objDN.Encode($"CN={dnsName}");

                //Assing the subject name by using the Distinguished Name object initialized above
                objPkcs10.Subject = objDN;

                // Create enrollment request
                objEnroll.CertificateFriendlyName = "RandomFriendlyName";
                objEnroll.InitializeFromRequest(objPkcs10);

                var strRequest = objEnroll.CreateRequest(EncodingType.XCN_CRYPT_STRING_BASE64);

                //Send request to CA. Returns generated certificate string
                var strCert = SendRequest(strRequest, "Issuing CA");
                if (strCert.Length > 0)
                {
                    //Install certificate
                    objEnroll.InstallResponse(InstallResponseRestrictionFlags.AllowUntrustedRoot, strCert, EncodingType.XCN_CRYPT_STRING_BASE64, null);

                    //Create random password
                    var password = Membership.GeneratePassword(12, 1);

                    //Create PFX file
                    string ress = objEnroll.CreatePFX(password, PFXExportOptions.PFXExportEEOnly);

                    //Save file to disk
                    var path = $"C:\\Cert\\{dnsName}.{DateTime.Now.Ticks}.pfx";
                    var fs = new FileStream(path, FileMode.Create);

                    fs.Write(Convert.FromBase64String(ress), 0, Convert.FromBase64String(ress).Length);

                    fs.Close();

                    //Start creating a return object
                    certificate.cert = strRequest;
                    certificate.IsValid = true;
                    certificate.Path = path;

                    //Get the PFX file and save its content to a certificate object
                    var collection = GetPfxFile(path, password);
                    foreach (X509Certificate2 cert in collection)
                    {
                        certificate.ValidFrom = cert.NotBefore;
                        certificate.ValidTo = cert.NotAfter;
                        certificate.Subject = cert.Subject;
                        certificate.FriendlyName = cert.FriendlyName;
                        certificate.Thumbprint = cert.Thumbprint;
                        certificate.SerialNumber = cert.SerialNumber;
                    }

                    SendPassword.SendPasswordSms(name, phoneNumber, password, "CertificatePassword");

                    return certificate;
                }

                return null;
            }
            catch (Exception ex)
            {
                Debug.Write(ex.Message);
                return ex;
            }
        }
    }

As said, this code runs fine when I run it locally. I run Visual Studio as Administrator as this seems necessary. I then sync it to the company TFS which compiles the code with MSBuild, and deploy it to the test-server with Octopus. When the program is deployed, I make sure that the IIS site the programs runs in has Administrator rights (if I don't the program returns an "Access denied" error). When I now run the program I get the mentioned error thrown.

To be precise, it fails on the following line:

var strRequest = objEnroll.CreateRequest(EncodingType.XCN_CRYPT_STRING_BASE64);

What causes this? I guess it has something to do with the environment the program runs in, but I am not sure what.

Any suggestions?

Thank you in advance!

c#
.net
windows
certificate
x509certificate
asked on Stack Overflow Jan 21, 2018 by Sigve • edited Jan 21, 2018 by Sigve

1 Answer

1

Ended up doing all the CSR creating in Bouncy Castle to get around the bug. Code as follows for anybody interested:

using CERTCLILib;
using Microsoft.Extensions.Options;
using OpenShare.Net.Library.Common;
using OpenShare.Net.Library.Common.Types;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.Pkcs;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.X509;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security;
using System.Web.Security;
using netCertificate = System.Security.Cryptography.X509Certificates;

public Object GeneratePkcs10(string dnsName, string name, int phoneNumber)
        {
            string privateKey = null;
            const string TemplateOid = "1.3.6.1.4 etc etc"; // CA Template OID
            const int majorVersion = 100;
            const int minorVersion = 4;

            Certificate returnCertificate = new Certificate();

            using (new Impersonation(_foo, _faa, _pw, LogonType.LogonNewCredentials))
            {
                try
                {
                    // Structuring the ASN1 schema to accept a CA template
                    DerObjectIdentifier certificateTemplateExtensionOid = new DerObjectIdentifier("1.3.6.1.4.1.311.21.7");
                    DerSequence certificateTemplateExtension = new DerSequence(new DerObjectIdentifier(TemplateOid), new DerInteger(majorVersion), new DerInteger(minorVersion));
                    Dictionary<DerObjectIdentifier, X509Extension> extensionsDictionary = new Dictionary<DerObjectIdentifier, X509Extension>
                    {
                        [certificateTemplateExtensionOid] = new X509Extension(DerBoolean.False, new DerOctetString(certificateTemplateExtension))
                    };

                    // Creating keys
                    var genParam = new RsaKeyGenerationParameters(BigInteger.ValueOf(0x10001), new SecureRandom(), (int)RootLenght.RootLength2048, 128);
                    var rsaKeyPairGenerator = new RsaKeyPairGenerator();
                    rsaKeyPairGenerator.Init(genParam);
                    AsymmetricCipherKeyPair pair = rsaKeyPairGenerator.GenerateKeyPair();

                    // Attatching attributes
                    var attrs = new Dictionary<DerObjectIdentifier, string> {{X509Name.CN, dnsName}};
                    var subject = new X509Name(attrs.Keys.ToList(), attrs);

                    // Creating the request
                    var pkcs10CertificationRequest = new Pkcs10CertificationRequest(PkcsObjectIdentifiers.Sha256WithRsaEncryption.Id, subject, pair.Public, new DerSet(new DerSequence(PkcsObjectIdentifiers.Pkcs9AtExtensionRequest, new DerSet(new X509Extensions(extensionsDictionary)))), pair.Private);
                    var csr = Convert.ToBase64String(pkcs10CertificationRequest.GetEncoded());

                    // Sending the request
                    string strCert = SendRequest(csr, "Insert path to CA");

                    if (strCert.Length > 0) // We have a valid certificate!
                    {
                        // Create Bouncy castle certificate
                        X509CertificateParser parser = new X509CertificateParser();
                        X509Certificate x509Cert = parser.ReadCertificate(Convert.FromBase64String(strCert));

                        // Create .NET certificate (this makes getting the certificate thumbprint a lot easier)
                        netCertificate.X509Certificate2 microsoftCertificate = new netCertificate.X509Certificate2();
                        microsoftCertificate.Import(x509Cert.GetEncoded());

                        var path = $"C:\\Cert\\{dnsName}.{DateTime.Now.Ticks}.pfx";
                        var password = CreatePfxFile(x509Cert, pair.Private, path);

                        //Start creating a return object
                        returnCertificate.cert = strCert;
                        returnCertificate.IsValid = x509Cert.IsValidNow;
                        returnCertificate.Path = path;
                        returnCertificate.SerialNumber = x509Cert.SerialNumber.ToString();
                        returnCertificate.Subject = x509Cert.SubjectDN.ToString();
                        returnCertificate.Thumbprint = microsoftCertificate.Thumbprint;
                        returnCertificate.ValidFrom = x509Cert.NotBefore;
                        returnCertificate.ValidTo = x509Cert.NotAfter;
                        returnCertificate.FriendlyName = microsoftCertificate.FriendlyName;

                        // Send password to user
                        SendPassword.SendPasswordSms(name, phoneNumber, password, "CertificatePassword");

                        return returnCertificate;
                    }
                    return null;
                }
                catch (Exception ex)
                {
                    Debug.WriteLine(ex);
                    return ex;
                }
            }
        }
answered on Stack Overflow Jan 25, 2018 by Sigve

User contributions licensed under CC BY-SA 3.0