I'm trying to check the certificate with its revocation list (crl-file). In BouncyCustle library there is a method x509Crl.IsRevoked(), that should be used for this. The point is that it gets x509Certificate object as a parameter, but I can't understand how to create this x509Certificate object.
I used DotNetUtilities.FromX509Certificate() for converting from System.Security.Cryptography.X509Certificates.x509Certificate2 object to Org.BouncyCastle.X509.X509Certificate object, but I faced the problem - method IsRevoked() always returns true - for all crl's I tested.
Question: how to create Org.BouncyCastle.X509.X509Certificate object directly from binary without converting from System.Security.Cryptography.X509Certificates.x509Certificate2?
My code for checking certificate with it's crl-file:
static public void RevocationChecker(string certPath, string crlPath)
{
X509Certificate2 cert = new X509Certificate2();
cert.Import(File.ReadAllBytes(certPath));
Org.BouncyCastle.X509.X509Certificate bouncyCert = DotNetUtilities.FromX509Certificate(cert);
X509CrlParser crlParser = new X509CrlParser();
X509Crl crl = crlParser.ReadCrl(File.ReadAllBytes(crlPath));
bool rezult = crl.IsRevoked(bouncyCert);
Console.WriteLine(rezult);
}
Give this a shot:
System.Security.Cryptography.X509Certificates.X509Certificate cert = new System.Security
.Cryptography.X509Certificates.X509Certificate(File.ReadAllBytes(certPath));`
Org.BouncyCastle.X509.X509Certificate bouncyCert = new Org.BouncyCastle.X509
.X509CertificateParser().ReadCertificate(cert.GetRawCertData());
Related
I've been give a private key, public key and a certificate to try and generate a signature for an SSO application, I've been struggling with this for a while now and I've think I've finally managed to get some code close to working as needed. I made a post a while back here: iDP connecting to SP SAML / SSO which has helped me get in the right direction.
However I am still unsure on how I meant to be signing this signature, all the code I seem to come by says I need to use the .Net class X509Certificate which usually tried to load in another file, however the certificate is in the assertion file itself.
<ds:X509Data>
<ds:X509Certificate>X509Certificate Goes Here</ds:X509Certificate>
</ds:X509Data>
I have this method here:
private SignedXml SignSignature(XmlDocument assertionDoc, string uri, string digest)
{
CspParameters cspParams = new CspParameters();
cspParams.KeyContainerName = "XML_DSIG_RSA_KEY";
RSACryptoServiceProvider rsaKey = new RSACryptoServiceProvider(cspParams);
SignedXml signedXml = new SignedXml(assertionDoc);
Reference reference = new Reference();
signedXml.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NTransformUrl;
signedXml.SignedInfo.SignatureMethod = SignedXml.XmlDsigRSASHA1Url;
reference.Uri = uri;
reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
reference.AddTransform(new XmlDsigExcC14NTransform());
reference.DigestMethod = SignedXml.XmlDsigSHA1Url;
reference.DigestValue = Encoding.ASCII.GetBytes(digest);
signedXml.AddReference(reference);
signedXml.SigningKey = rsaKey;
HMACSHA256 key = new HMACSHA256(Encoding.ASCII.GetBytes(PrivateKey));
signedXml.ComputeSignature(key);
return signedXml;
}
Which is what I am using to sign SignatureValue of the document, however I am only making use of the private key in the SHA256 class and not the certificate, I'm not even sure if I am using the private key correctly, overall I think I am making this more complicated than it needs to be and hopefully someone from here can assist me.
When you sign an assertion, you only need to use the private key of a certificate which is the case of your code.
However, usually the public key of the certificate is inserted as keyinfo into the signature to notify a receiver. This ends up as the ds:X509Data section you mentioned above. In order to do that, you need to add some more lines of code into the method above. You can find sample code at: https://github.com/Safewhere/CHTestSigningService/blob/86a66950d1ffa5208b8bf80d03868a073ba29f12/Kombit.Samples.CHTestSigningService/Code/TokenSigningService.cs#L344 (notice line 361, 362, and 368).
I have a bunch of root and intermediate certificates given as byte arrays, and I also have end user certificate. I want to build a certificate chain for given end user certificate. In .NET framework I can do it like this:
using System.Security.Cryptography.X509Certificates;
static IEnumerable<X509ChainElement>
BuildCertificateChain(byte[] primaryCertificate, IEnumerable<byte[]> additionalCertificates)
{
X509Chain chain = new X509Chain();
foreach (var cert in additionalCertificates.Select(x => new X509Certificate2(x)))
{
chain.ChainPolicy.ExtraStore.Add(cert);
}
// You can alter how the chain is built/validated.
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.IgnoreWrongUsage;
// Do the preliminary validation.
var primaryCert = new X509Certificate2(primaryCertificate);
if (!chain.Build(primaryCert))
throw new Exception("Unable to build certificate chain");
return chain.ChainElements.Cast<X509ChainElement>();
}
How to do it in BouncyCastle? I tried with code below but I get PkixCertPathBuilderException: No certificate found matching targetContraints:
using Org.BouncyCastle;
using Org.BouncyCastle.Pkix;
using Org.BouncyCastle.Utilities.Collections;
using Org.BouncyCastle.X509;
using Org.BouncyCastle.X509.Store;
static IEnumerable<X509Certificate> BuildCertificateChainBC(byte[] primary, IEnumerable<byte[]> additional)
{
X509CertificateParser parser = new X509CertificateParser();
PkixCertPathBuilder builder = new PkixCertPathBuilder();
// Separate root from itermediate
List<X509Certificate> intermediateCerts = new List<X509Certificate>();
HashSet rootCerts = new HashSet();
foreach (byte[] cert in additional)
{
X509Certificate x509Cert = parser.ReadCertificate(cert);
// Separate root and subordinate certificates
if (x509Cert.IssuerDN.Equivalent(x509Cert.SubjectDN))
rootCerts.Add(new TrustAnchor(x509Cert, null));
else
intermediateCerts.Add(x509Cert);
}
// Create chain for this certificate
X509CertStoreSelector holder = new X509CertStoreSelector();
holder.Certificate = parser.ReadCertificate(primary);
// WITHOUT THIS LINE BUILDER CANNOT BEGIN BUILDING THE CHAIN
intermediateCerts.Add(holder.Certificate);
PkixBuilderParameters builderParams = new PkixBuilderParameters(rootCerts, holder);
builderParams.IsRevocationEnabled = false;
X509CollectionStoreParameters intermediateStoreParameters =
new X509CollectionStoreParameters(intermediateCerts);
builderParams.AddStore(X509StoreFactory.Create(
"Certificate/Collection", intermediateStoreParameters));
PkixCertPathBuilderResult result = builder.Build(builderParams);
return result.CertPath.Certificates.Cast<X509Certificate>();
}
Edit: I added the line that fixed my problem. It's commented with all caps. Case closed.
I've done this in Java a number of times. Given that the API seems to be a straight port of the Java one I'll take a stab.
I'm pretty sure when you add the store to the builder, that collection is expected to contain all certs in the chain to be built, not just intermediate ones. So rootCerts and primary should be added.
If that doesn't solve the problem on its own I would try also specifying the desired cert a different way. You can do one of two things:
Implement your own Selector that always only matches your desired cert (primary in the example).
Instead of setting holder.Certificate, set one or more criteria on holder. For instance, setSubject, setSubjectPublicKey, setIssuer.
Those are the two most common problems I had with PkixCertPathBuilder.
The code below does not answer your question (it's a pure Java solution). I only just realized now after typing out everything that it doesn't answer your question! I forgot BouncyCastle has a C# version! Oops.
It still might help you roll your own chain builder. You probably don't need any libraries or frameworks.
Good luck!
http://juliusdavies.ca/commons-ssl/src/java/org/apache/commons/ssl/X509CertificateChainBuilder.java
/**
* #param startingPoint the X509Certificate for which we want to find
* ancestors
*
* #param certificates A pool of certificates in which we expect to find
* the startingPoint's ancestors.
*
* #return Array of X509Certificates, starting with the "startingPoint" and
* ending with highest level ancestor we could find in the supplied
* collection.
*/
public static X509Certificate[] buildPath(
X509Certificate startingPoint, Collection certificates
) throws NoSuchAlgorithmException, InvalidKeyException,
NoSuchProviderException, CertificateException {
LinkedList path = new LinkedList();
path.add(startingPoint);
boolean nodeAdded = true;
// Keep looping until an iteration happens where we don't add any nodes
// to our path.
while (nodeAdded) {
// We'll start out by assuming nothing gets added. If something
// gets added, then nodeAdded will be changed to "true".
nodeAdded = false;
X509Certificate top = (X509Certificate) path.getLast();
if (isSelfSigned(top)) {
// We're self-signed, so we're done!
break;
}
// Not self-signed. Let's see if we're signed by anyone in the
// collection.
Iterator it = certificates.iterator();
while (it.hasNext()) {
X509Certificate x509 = (X509Certificate) it.next();
if (verify(top, x509.getPublicKey())) {
// We're signed by this guy! Add him to the chain we're
// building up.
path.add(x509);
nodeAdded = true;
it.remove(); // Not interested in this guy anymore!
break;
}
// Not signed by this guy, let's try the next guy.
}
}
X509Certificate[] results = new X509Certificate[path.size()];
path.toArray(results);
return results;
}
Requires these two additional methods:
isSelfSigned():
public static boolean isSelfSigned(X509Certificate cert)
throws CertificateException, InvalidKeyException,
NoSuchAlgorithmException, NoSuchProviderException {
return verify(cert, cert.getPublicKey());
}
And verify():
public static boolean verify(X509Certificate cert, PublicKey key)
throws CertificateException, InvalidKeyException,
NoSuchAlgorithmException, NoSuchProviderException {
String sigAlg = cert.getSigAlgName();
String keyAlg = key.getAlgorithm();
sigAlg = sigAlg != null ? sigAlg.trim().toUpperCase() : "";
keyAlg = keyAlg != null ? keyAlg.trim().toUpperCase() : "";
if (keyAlg.length() >= 2 && sigAlg.endsWith(keyAlg)) {
try {
cert.verify(key);
return true;
} catch (SignatureException se) {
return false;
}
} else {
return false;
}
}
Is there any way to convert a Org.BouncyCastle.X509.X509Certificate to System.Security.Cryptography.X509Certificates.X509Certificate2?
The inverse operation is easy, combining Org.BouncyCastle.X509.X509CertificateParser with
System.Security.Cryptography.X509Certificates.X509Certificate2.Export().
Easy!!
using B = Org.BouncyCastle.X509; //Bouncy certificates
using W = System.Security.Cryptography.X509Certificates;
W.X509Certificate2 certificate = new W.X509Certificate2(pdfCertificate.GetEncoded());
And now I can validate certificate chain in the server:
W.X509Chain ch = new W.X509Chain();
ch.ChainPolicy.RevocationMode = W.X509RevocationMode.NoCheck;
if (!ch.Build(certificate))
res |= ErroresValidacion.CAInvalida;
Useful to validate pdf certifcates extracted with iTextSharp.
From https://github.com/dotnet/corefx/wiki/ApiCompat :
Most users of X509Certificate and X509Certificate2 objects assume that
the object is immutable except for the Reset()/Dispose() methods, the
use of Import violates this assumption.
In other words, trying to use import throws an exception in .net core. You should now use:
new X509Certificate(cert.GetEncoded());
but, according to the .net API analyzer (https://learn.microsoft.com/en-us/dotnet/standard/analyzers/api-analyzer),
warning PC001: X509Certificate2.X509Certificate2(byte[]) isn't supported on macOS
I guess that is the best answer:
var cert = pdf.Certificates[0];//Org.BouncyCastle.X509.X509Certificate
var cert50 = new X509Certificate();
cert50.Import(cert.GetEncoded());
Let's say I have three certificates (in Base64 format)
Root
|
--- CA
|
--- Cert (client/signing/whatever)
How can I validate the certs and certificate path/chain in C#?
(All those three certs may not be in my computer cert store)
Edit: BouncyCastle has the function to verify. But I'm trying not to use any third-party library.
byte[] b1 = Convert.FromBase64String(x509Str1);
byte[] b2 = Convert.FromBase64String(x509Str2);
X509Certificate cer1 =
new X509CertificateParser().ReadCertificate(b1);
X509Certificate cer2 =
new X509CertificateParser().ReadCertificate(b2);
cer1.Verify(cer2.GetPublicKey());
If the cer1 is not signed by cert2 (CA or root), there will be exception. This is exactly what I want.
The X509Chain class was designed to do this, you can even customize how it performs the chain building process.
static bool VerifyCertificate(byte[] primaryCertificate, IEnumerable<byte[]> additionalCertificates)
{
var chain = new X509Chain();
foreach (var cert in additionalCertificates.Select(x => new X509Certificate2(x)))
{
chain.ChainPolicy.ExtraStore.Add(cert);
}
// You can alter how the chain is built/validated.
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.IgnoreWrongUsage;
// Do the validation.
var primaryCert = new X509Certificate2(primaryCertificate);
return chain.Build(primaryCert);
}
The X509Chain will contain additional information about the validation failure after Build() == false if you need it.
Edit: This will merely ensure that your CA's are valid. If you want to ensure that the chain is identical you can check the thumbprints manually. You can use the following method to ensure that the certification chain is correct, it expects the chain in the order: ..., INTERMEDIATE2, INTERMEDIATE1 (Signer of INTERMEDIATE2), CA (Signer of INTERMEDIATE1)
static bool VerifyCertificate(byte[] primaryCertificate, IEnumerable<byte[]> additionalCertificates)
{
var chain = new X509Chain();
foreach (var cert in additionalCertificates.Select(x => new X509Certificate2(x)))
{
chain.ChainPolicy.ExtraStore.Add(cert);
}
// You can alter how the chain is built/validated.
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.IgnoreWrongUsage;
// Do the preliminary validation.
var primaryCert = new X509Certificate2(primaryCertificate);
if (!chain.Build(primaryCert))
return false;
// Make sure we have the same number of elements.
if (chain.ChainElements.Count != chain.ChainPolicy.ExtraStore.Count + 1)
return false;
// Make sure all the thumbprints of the CAs match up.
// The first one should be 'primaryCert', leading up to the root CA.
for (var i = 1; i < chain.ChainElements.Count; i++)
{
if (chain.ChainElements[i].Certificate.Thumbprint != chain.ChainPolicy.ExtraStore[i - 1].Thumbprint)
return false;
}
return true;
}
I am unable to test this because I don't have a full CA chain with me, so it would be best to debug and step through the code.
The X509Chain does not work reliably for scenarios where you do not have the root certificate in the trusted CA store on the machine.
Others will advocate using bouncy castle. I wanted to avoid bringing in another library just for this task, so I wrote my own.
As see in RFC3280 Section 4.1 the certificate is a ASN1 encoded structure, and at it's base level is comprised of only 3 elements.
The "TBS" (to be signed) certificate
The signature algorithm
and the signature value
Certificate ::= SEQUENCE {
tbsCertificate TBSCertificate,
signatureAlgorithm AlgorithmIdentifier,
signatureValue BIT STRING
}
C# actually has a handy tool for parsing ASN1, the System.Formats.Asn1.AsnDecoder.
Using this, we can extract these 3 elements from the certificate to verify the chain.
The first step was extracting the certificate signature, since the X509Certificate2 class does not expose this information and it is necessary for the purpose of certificate validation.
Example code to extract the signature value part:
public static byte[] Signature(
this X509Certificate2 certificate,
AsnEncodingRules encodingRules = AsnEncodingRules.BER)
{
var signedData = certificate.RawDataMemory;
AsnDecoder.ReadSequence(
signedData.Span,
encodingRules,
out var offset,
out var length,
out _
);
var certificateSpan = signedData.Span[offset..(offset + length)];
AsnDecoder.ReadSequence(
certificateSpan,
encodingRules,
out var tbsOffset,
out var tbsLength,
out _
);
var offsetSpan = certificateSpan[(tbsOffset + tbsLength)..];
AsnDecoder.ReadSequence(
offsetSpan,
encodingRules,
out var algOffset,
out var algLength,
out _
);
return AsnDecoder.ReadBitString(
offsetSpan[(algOffset + algLength)..],
encodingRules,
out _,
out _
);
}
The next step is to extract the TBS certificate. This is the original data which was signed.
example code to extract the TBS certificate data:
public static ReadOnlySpan<byte> TbsCertificate(
this X509Certificate2 certificate,
AsnEncodingRules encodingRules = AsnEncodingRules.BER)
{
var signedData = certificate.RawDataMemory;
AsnDecoder.ReadSequence(
signedData.Span,
encodingRules,
out var offset,
out var length,
out _
);
var certificateSpan = signedData.Span[offset..(offset + length)];
AsnDecoder.ReadSequence(
certificateSpan,
encodingRules,
out var tbsOffset,
out var tbsLength,
out _
);
// include ASN1 4 byte header to get WHOLE TBS Cert
return certificateSpan.Slice(tbsOffset - 4, tbsLength + 4);
}
You may notice that when extracting the TBS certiifcate I needed to include the ASN1 header in the data, this is because the signature of the TBS Certificate INCLUDES this data (this annoyed me for a while).
For the first time in history, the Microsoft does not impede us with their API design, and we are able to obtain the Signature Algorithm directly from the X509Certificate2 object. Then we just need to decide to what extend we are going to implement different hash algorithms.
var signature = signed.Signature();
var tbs = signed.TbsCertificate();
var alg = signed.SignatureAlgorithm;
// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gpnap/a48b02b2-2a10-4eb0-bed4-1807a6d2f5ad
switch (alg)
{
case { Value: var value } when value?.StartsWith("1.2.840.113549.1.1.") ?? false:
return signedBy.GetRSAPublicKey()?.VerifyData(
tbs,
signature,
value switch {
"1.2.840.113549.1.1.11" => HashAlgorithmName.SHA256,
"1.2.840.113549.1.1.12" => HashAlgorithmName.SHA384,
"1.2.840.113549.1.1.13" => HashAlgorithmName.SHA512,
_ => throw new UnsupportedSignatureAlgorithm(alg)
},
RSASignaturePadding.Pkcs1
) ?? false;
case { Value: var value } when value?.StartsWith("1.2.840.10045.4.3.") ?? false:
return signedBy.GetECDsaPublicKey()?.VerifyData(
tbs,
signature,
value switch
{
"1.2.840.10045.4.3.2" => HashAlgorithmName.SHA256,
"1.2.840.10045.4.3.3" => HashAlgorithmName.SHA384,
"1.2.840.10045.4.3.4" => HashAlgorithmName.SHA512,
_ => throw new UnsupportedSignatureAlgorithm(alg)
},
DSASignatureFormat.Rfc3279DerSequence
) ?? false;
default: throw new UnsupportedSignatureAlgorithm(alg);
}
As shown in the code above, https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gpnap/a48b02b2-2a10-4eb0-bed4-1807a6d2f5ad is a good resource to see the mapping of algorithms and OIDs.
Another thing you should be aware of is that there are some articles out there that claim that for elliptical curve algorithms, microsoft expects a R,S formatted key instead of a DER formatted key. I tried to convert the key to this format but it ultimately didn't work. What I discovered was that it was necessary to use the DSASignatureFormat.Rfc3279DerSequence parameter.
Additional certificate checks, like "not before" and "not after", or CRL and OCSP checks can be done in addition to the chain verification.
I have this problem, I wrote C# code for:
Generating CSR programmatically
Submit the CSR to Microsoft Certificate Services
Receive the certificate and save as pfx.
The code works great, but instead of creating CSR programmatically, when I use the CSR created using IIS, I get the above error.
What might be the reason please?
I am able to create the certificate in Microsoft Certificate services(by calling CCertRequestClass.Submit method and can see it in the issued certificates), but it is that I am not able to install it. The error happens when I call CX509EnrollmentClass.InstallResponse. Below is my CSR generation code:
private static CCspInformations CreateCSP()
{
CCspInformation csp = new CCspInformationClass();
CCspInformations csps = new CCspInformationsClass();
string cspAlgorithmName = "Microsoft Enhanced Cryptographic Provider v1.0";
// Initialize the csp object using the desired Cryptograhic Service Provider (CSP)
csp.InitializeFromName(cspAlgorithmName);
// Add this CSP object to the CSP collection object
csps.Add(csp);
return csps;
}
private static CX509PrivateKey CreatePrivateKey(CCspInformations csps)
{
CX509PrivateKey csrPrivateKey = new CX509PrivateKeyClass();
// Provide key container name, key length and key spec to the private key object
csrPrivateKey.Length = 1024;
csrPrivateKey.ExportPolicy = X509PrivateKeyExportFlags.XCN_NCRYPT_ALLOW_EXPORT_FLAG;
csrPrivateKey.KeySpec = X509KeySpec.XCN_AT_SIGNATURE;
csrPrivateKey.KeyUsage = X509PrivateKeyUsageFlags.XCN_NCRYPT_ALLOW_ALL_USAGES;
csrPrivateKey.MachineContext = false;
// Provide the CSP collection object (in this case containing only 1 CSP object)
// to the private key object
csrPrivateKey.CspInformations = csps;
// Create the actual key pair
csrPrivateKey.Create();
return csrPrivateKey;
}
private static CX509ExtensionKeyUsage CreateExtensionKeyUsage()
{
CX509ExtensionKeyUsage extensionKeyUsage = new CX509ExtensionKeyUsageClass();
// Key Usage Extension
extensionKeyUsage.InitializeEncode(
CERTENROLLLib.X509KeyUsageFlags.XCN_CERT_DIGITAL_SIGNATURE_KEY_USAGE |
CERTENROLLLib.X509KeyUsageFlags.XCN_CERT_NON_REPUDIATION_KEY_USAGE |
CERTENROLLLib.X509KeyUsageFlags.XCN_CERT_KEY_ENCIPHERMENT_KEY_USAGE |
CERTENROLLLib.X509KeyUsageFlags.XCN_CERT_DATA_ENCIPHERMENT_KEY_USAGE
);
return extensionKeyUsage;
}
private static CX509ExtensionEnhancedKeyUsage CreateExtensionEnhancedKeyUsage()
{
CObjectIds objectIds = new CObjectIdsClass();
CObjectId objectId = new CObjectIdClass();
CX509ExtensionEnhancedKeyUsage extensionEnhancedKeyUsage = new CX509ExtensionEnhancedKeyUsageClass();
string clientAuthOid = "1.3.6.1.5.5.7.3.2";
string serverAuthOid = "1.3.6.1.5.5.7.3.1";
// Enhanced Key Usage Extension
objectId.InitializeFromValue(clientAuthOid); // OID for Client Authentication usage
objectIds.Add(objectId);
extensionEnhancedKeyUsage.InitializeEncode(objectIds);
return extensionEnhancedKeyUsage;
}
private static CX500DistinguishedName CreateDN(string subject)
{
CX500DistinguishedName distinguishedName = new CX500DistinguishedNameClass();
if (String.IsNullOrEmpty(subject))
{
subject = "CN=Suresh,C=IN,L=Bangalore,O=McAfee,OU=EMM,S=Karnataka";
}
// Encode the name in using the Distinguished Name object
distinguishedName.Encode(subject, X500NameFlags.XCN_CERT_NAME_STR_NONE);
return distinguishedName;
}
/// <summary>
/// Creates CSR
/// </summary>
/// <returns></returns>
public static string CreateRequest()
{
CX509CertificateRequestPkcs10 pkcs10Request = new CX509CertificateRequestPkcs10Class();
CX509Enrollment certEnroll = new CX509EnrollmentClass();
// Initialize the PKCS#10 certificate request object based on the private key.
// Using the context, indicate that this is a user certificate request and don't
// provide a template name
pkcs10Request.InitializeFromPrivateKey(
X509CertificateEnrollmentContext.ContextUser,
CreatePrivateKey(CreateCSP()),
string.Empty
);
pkcs10Request.X509Extensions.Add((CX509Extension)CreateExtensionKeyUsage());
pkcs10Request.X509Extensions.Add((CX509Extension)CreateExtensionEnhancedKeyUsage());
// Assing the subject name by using the Distinguished Name object initialized above
pkcs10Request.Subject = CreateDN(null);
// Create enrollment request
certEnroll.InitializeFromRequest(pkcs10Request);
return certEnroll.CreateRequest(EncodingType.XCN_CRYPT_STRING_BASE64);
}
I also faced the same issue.
This code will work if you replace CX509CertificateRequestPkcs10 to the CX509CertificateRequestCertificate.