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!
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;
}
}
}
User contributions licensed under CC BY-SA 3.0