Search code examples
.netwindowscertificatepki

Generating a CSR from an inf file using .NET


I'm using an INF file such as the one below to generate a CSR with "certreq.exe -new -q request.inf request.csr":

[Version]
Signature= "$Windows NT$"

[NewRequest]
Subject = "CN=subject"
KeyLength = 2048
Exportable = TRUE
MachineKeySet = TRUE
SMIME = FALSE
PrivateKeyArchive = FALSE
UserProtected = FALSE
UseExistingKeySet = FALSE
ProviderName = "Microsoft RSA SChannel Cryptographic Provider"
ProviderType = 12
RequestType = CMC
KeyUsage = 0xa0
KeyAlgorithm = RSA
HashAlgorithm = sha256

[EnhancedKeyUsageExtension]
OID=1.3.6.1.5.5.7.3.1

Is there a way that I can do this via .NET classes, and preferably without having to export the CSR to a file once it is generated?

Thanks a lot for your help.


Solution

  • So one way you can do this is with CertEnroll. CertEnroll cannot process an INF file like your example above, that's unique to certreq (though there is nothing stopping you from parsing the INF yourself).

    CertEnroll is a COM library, but still easy enough to use in .NET. First, add a reference to it. Adding a COM reference is a little different. Note though, that CertEnroll is not available on Windows XP or Windows Vista.

    First, in your project, right click "References" and select Add Reference. Under the "COM" menu, select "CertEnroll 1.0 Type Library".

    The meat of the functionality is the CX509CertificateRequestCmc interface. COM interfaces in C# are a bit special, you can "new" them, so this is perfectly valid:

    var req = new CX509CertificateRequestCmc();
    

    So, to translate your INF to CertEnroll, I think it would look something similar to this:

    var req = new CX509CertificateRequestPkcs10();
    req.Initialize(X509CertificateEnrollmentContext.ContextUser);
    
    //Set basic constraints
    var basicConstraints = new CX509ExtensionBasicConstraints();
    basicConstraints.InitializeEncode(false, 0);
    req.X509Extensions.Add((CX509Extension)basicConstraints);
    
    //Set the hash algorithm
    var hashAlgorithm = new CObjectId();
    hashAlgorithm.InitializeFromAlgorithmName(ObjectIdGroupId.XCN_CRYPT_HASH_ALG_OID_GROUP_ID, 0, 0, "sha256");
    req.HashAlgorithm = hashAlgorithm;
    
    //Set the enhanced key usage
    var eku = new CX509ExtensionEnhancedKeyUsage();
    var serverAuth = new CObjectId();
    serverAuth.InitializeFromValue("1.3.6.1.5.5.7.3.1");
    var oids = new CObjectIds();
    oids.Add(serverAuth);
    eku.InitializeEncode(oids);
    eku.Critical = false;
    
    var privateKey = req.PrivateKey;
    //Allow export as the INF does
    privateKey.ExportPolicy = X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_EXPORT_FLAG;
    privateKey.Length = 2048;
    
    //Set the algorithm explicitly
    var rsa = new CObjectId();
    rsa.InitializeFromAlgorithmName(ObjectIdGroupId.XCN_CRYPT_ANY_GROUP_ID, 0, 0, "RSA");
    privateKey.Algorithm = rsa;
    
    privateKey.ProviderName = "Microsoft RSA SChannel Cryptographic Provider";
    privateKey.ProviderType = X509ProviderType.XCN_PROV_RSA_SCHANNEL; //12
    privateKey.KeyUsage = (X509PrivateKeyUsageFlags)0xA0;
    privateKey.Existing = false;
    privateKey.KeyProtection = X509PrivateKeyProtection.XCN_NCRYPT_UI_NO_PROTECTION_FLAG;
    privateKey.MachineContext = true;
    
    var dn = new CX500DistinguishedName();
    dn.Encode("CN=subject", X500NameFlags.XCN_CERT_NAME_STR_NONE);
    req.Subject = dn;
    
    var cmc = new CX509CertificateRequestCmc();
    cmc.InitializeFromInnerRequest(req);
    cmc.ArchivePrivateKey = false;
    cmc.Encode();
    var data = cmc.RawData[EncodingType.XCN_CRYPT_STRING_BASE64REQUESTHEADER];
    Console.WriteLine(data);
    

    The "data" variable at the end contains the request, all without saving it to disk.

    It's quite likely that something is missing from here, but it does generate a certificate request. I would consult the CertEnroll documentation for any further tweaks that need to be made.