Generate and sign certificate in different machines C# - c#

I need to generate certificates to be used in secure communication between agents. Each agent generates a certificate and must send it to the system CA, in another machine, to be signed (and trusted by the other agents). I am doing it using C# with the following code for the agent:
//generate certificate
ECDsa elipticCurveNistP256Key = ECDsa.Create(ECCurve.CreateFromValue("1.2.840.10045.3.1.7")); // nistP256 curve
CertificateRequest certificateRequest = new CertificateRequest("CN=" + agentId, elipticCurveNistP256Key, HashAlgorithmName.SHA256);
certificateRequest.CertificateExtensions.Add(
new X509BasicConstraintsExtension(false, false, 0, false));
certificateRequest.CertificateExtensions.Add(
new X509KeyUsageExtension(
X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.NonRepudiation,
false));
// Add the SubjectAlternativeName extension
var sanBuilder = new SubjectAlternativeNameBuilder();
sanBuilder.AddIpAddress(IPAddress.Parse(agentIpAddress));
certificateRequest.CertificateExtensions.Add(sanBuilder.Build());
certificateRequest.CertificateExtensions.Add(
new X509EnhancedKeyUsageExtension(
new OidCollection
{
new Oid("1.3.6.1.5.5.7.3.8")
},
true));
certificateRequest.CertificateExtensions.Add(
new X509SubjectKeyIdentifierExtension(certificateRequest.PublicKey, false));
And the following code for the CA system:
X509Certificate2 signedCertificate = certificateRequest.Create(
caCertificatePFX,
DateTimeOffset.UtcNow.AddDays(-1),
DateTimeOffset.UtcNow.AddDays(30),
new byte[] {1, 2, 3, 4});
Of course, I use also code for communication between the machines that I do not show here. But I have at least two problems:
I would like to have a complete separation between certificate generation and signing but even with lots of tries this was the only code that I could manage to get to work. If I am not mistaken this code has the certificate creation at the CA system which is not the ideal scenario (CA has access to agent private key) but if I didn't find a better one it's something I can accept.
The second problem is that even if I accept the first problem I still need to send the CertificateRequest object from one machine to another and CertificateRequest is not serializable. I have found the method CreateSigningRequest() that "Creates an ASN.1 DER-encoded PKCS#10 CertificationRequest value representing the state of the current object." however I have not found a way to then make it be a CertificateRequest object again so that I can run the CA system code.
Does anyone know how I can do this? Hopefully to completely separate certificate generation and certificate signing, but if that is not possible at least to create CertificateRequest object back.
I am running .Net Framework 4.7.2 that I need to maintain in order to use previously developed Windows Forms.
Thanks

As you noted, there's not a way to read back the PKCS#10 request. That's largely because too many of the things are missing to be an "OK" Certificate Authority, so having a reader would just make for a lot of "bad" Certificate Authorities. (Since your CA doesn't support revocation it's also a "bad" CA, but you're mitigating that with short lifetime certificates.)
The PKCS#10 request contains:
A data format version
A name (presumably the one that the requester wants)
A public key
Attributes
The requested extensions comes here (EKUs, Subject Alternative Name, etc)
A signature, to prove that the requester has the private key.
The data format version is irrelevant if you're not using the data format, and the signature is not really important for "closed" issuers (CAs that only issue certificates to directly-known parties). So you just need to transport the public key and whatever other data you need for the request (looking at your current code, the agent ID and IP address).
The only tricky part is sending the public key... but with .NET Core 3.0+ you can normalize all of the keys to their SubjectPublicKeyInfo format:
byte[] spki = elipticCurveNistP256Key.ExportSubjectPublicKeyInfo();
While it would have been exceedingly clever for the PublicKey type to have an ImportSubjectPublicKeyInfo method, that hasn't happened yet. For generic parsing you'd want to try all the major key types, but since you're a closed CA on the other side you can know a priori that it's ECDSA:
using (ECDsa clientPub = ECDsa.Create())
{
clientPub.ImportSubjectPublicKeyInfo(transmittedSpki, out _);
// the rest of your code goes here.
}

I would strongly suggest to use CA software to sign certificate requests. Period.
Any attempt to roll own your CA code will make the solution unreliable, fragile and error-prone in many aspects. There are several options, starting with Microsoft ADCS (Windows) and EJBCA (Windows/Linux). Any other design will be simply bad.

Related

C# X509 certificate validation, with Online CRL check, without importing root certificate to trusted root CA certificate store

I'm trying to validate an X509 certificate chain without importing the root CA certificate into the trusted root CA certificate store (in production this code will run in an Azure Function, and you can't add certificates to the trusted root CA certificate store on Azure App Services).
We also need to perform an online CRL check on this certificate chain.
I've searched on this and I see many others are facing the same problem, but none of the suggestions seem to work. I've followed the approach outlined in this SO post, which echoes the suggestions from issue #26449 on the dotnet/runtime GitHub. Here's a small console application (targetting .NET Core 3.1) reproducing the problem:
static void Main(string[] args)
{
var rootCaCertificate = new X509Certificate2("root-ca-cert.cer");
var intermediateCaCertificate = new X509Certificate2("intermediate-ca-cert.cer");
var endUserCertificate = new X509Certificate2("end-user-cert.cer");
var chain = new X509Chain();
chain.ChainPolicy.ExtraStore.Add(rootCaCertificate);
chain.ChainPolicy.ExtraStore.Add(intermediateCaCertificate);
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;
chain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
chain.Build(endUserCertificate);
chain.Build(new X509Certificate2(endUserCertificate));
var errors = chain.ChainStatus.ToList();
if (!errors.Any())
{
Console.WriteLine("Certificate is valid");
return;
}
foreach (var error in errors)
{
Console.WriteLine($"{error.Status.ToString()}: {error.StatusInformation}");
}
}
When ran this returns three errors:
UntrustedRoot: A certificate chain processed, but terminated in a root certificate which is not trusted by the trust provider.
RevocationStatusUnknown: The revocation function was unable to check revocation for the certificate.
OfflineRevocation: The revocation function was unable to check revocation because the revocation server was offline.
However, if I add the root CA certificate to the trusted root CA certificate store then all three errors disappear.
Questions
Is this something wrong with my implementation, or is what I'm trying to do not possible?
What are my options to try to achieve this? A bit of Googling suggests the X509ChainPolicy.CustomTrustStore offered in .NET 5 might save the day. Is Bouncy Castle another option for achieving this?
A bit of Googling suggests the X509ChainPolicy.CustomTrustStore offered in .NET 5 might save the day
Yep.
Instead of putting rootCaCertificate into ExtraStore, put it into CustomTrustStore, then set chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;. Now your provided root is the only root valid for the chain. You can also remove the AllowUnknownCertificateAuthority flag.
OfflineRevocation
This error is slightly misleading. It means "revocation was requested for the chain, but one or more revocation responses is missing". In this case it's missing because the builder didn't ask for it, because it didn't trust the root. (Once you don't trust the root you can't trust the CRLs/OCSP responses, so why ask for them at all?)
RevocationStatusUnknown
Again, the unknown is because it didn't ask for it. This code is different than OfflineRevocation because technically a valid OCSP response is (effectively) "I don't know". That'd be an online/unknown.
UntrustedRoot
Solved by the custom trust code above.
Other things of note: The correct way to determine the certificate is valid is to capture the boolean return value from chain.Build. For your current chain, if you had disabled revocation (chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck) then Build would have returned true... but the UntrustedRoot error would still be present in the ChainStatus output. The boolean return from Build is false if there are any errors that the VerificationFlags didn't say to ignore.
You also only need to call Build once :).
static void Main(string[] args)
{
var rootCaCertificate = new X509Certificate2("root-ca-cert.cer");
var intermediateCaCertificate = new X509Certificate2("intermediate-ca-cert.cer");
var endUserCertificate = new X509Certificate2("end-user-cert.cer");
var chain = new X509Chain();
chain.ChainPolicy.CustomTrustStore.Add(rootCaCertificate);
chain.ChainPolicy.ExtraStore.Add(intermediateCaCertificate);
chain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
bool success = chain.Build(endUserCertificate);
if (success)
{
return;
}
foreach (X509ChainStatus error in chain.ChainStatus)
{
Console.WriteLine($"{error.Status.ToString()}: {error.StatusInformation}");
}
}
Well, not a full answer, but probably it will get you going.
We've built before an Azure Web App (not a function app, but I guess wouldn't matter) which did exactly what you want. Took us a week or so. Full answer would be posting the code of the whole app, we I obviously cannot do. Some hints though.
We walked around the certificate problem by uploading certificates to the app (TLS settings) and accessing them in code through WEBSITE_LOAD_CERTIFICATES setting (you put thumbprints there, it's also mentioned in the link you posted), than you can get them in code and build your own certificate store:
var certThumbprintsString = Environment.GetEnvironmentVariable("WEBSITE_LOAD_CERTIFICATES");
var certThumbprints = certThumbprintsString.Split(",").ToList();
var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
for (var i = 0; i < certThumbprints.Count; i++)
{
store.Certificates.Find(X509FindType.FindByThumbprint, certThumbprint, false);
}
Than we implemented a number of validators: for subject, time, certificate chain (you basically mark your root somehow and go from the certificate you validate up the chain in your store and see if you end up in your root) and CRL.
For CRL Bouncy Castle offers support:
// Get CRL from certificate, fetch it, cache it
var crlParser = new X509CrlParser();
var crl = crlParser.ReadCrl(data);
var isRevoked = crl.IsRevoked(cert);
Getting CRL from the certificate is tricky, but doable (I followed this for the purpose, more or less https://learn.microsoft.com/en-us/archive/blogs/joetalksmicrosoft/pki-authentication-as-a-azure-web-app).

How exactly is a Certificate chain built?

As the title states i want to know how the certificate chain is build internaly.
I have a self signed root certificate (CA) und an incoming user certificate, which supposedly has been signed by my root, which is what i have to test. Testing a X509Certificate2 can be done with the X509Certificate2.Verify() method, which uses the X509Chain with some standard settings. To have full control over the process i am using the X509Chain directly. As my root is not installed on the machine (not in trusted root certificate store) i am adding it to the ExtraStore of the ChainPolicy.
The whole magic happens within X509Chain.Build() method, where i pass my 'to be tested' user certificate. The chain gets build, starting with the passed certificate and ending with the self signed root. But how exactly? How does one test wether a certificate has been signed by one or another root? Does it use digital signatures internally?
I tried to debug the whole thing but ended at an DLL import, which uses the CertGetCertificateChain method from Libraries.Crypt32. That is C++ stuff, which exceeds my domain of knowledge.
Simplified code so far:
using (var chain = new X509Chain
{
ChainPolicy =
{
RevocationMode = X509RevocationMode.NoCheck,
RevocationFlag = X509RevocationFlag.ExcludeRoot,
VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority,
VerificationTime = DateTime.Now,
UrlRetrievalTimeout = new TimeSpan(0, 0, 0)
}
})
{
chain.ChainPolicy.ExtraStore.Add(authority);
if (!chain.Build(signedCertificate))
{
//Errorhandling
}
//Root certificate should always be last in the X509ChainElements collection
var isSignedByCorrectRootCert =
chain.ChainElements
.Cast<X509ChainElement>()
.Last()
.Certificate.Thumbprint == authority.Thumbprint;
if (!isSignedByCorrectRootCert)
{
//Errorhandling
}
}
Does it use digital signatures internally?
exactly.
I tried to debug the whole thing but ended at an DLL import, which uses the CertGetCertificateChain method from Libraries.Crypt32.
yes, .NET X509Chain class is nothing else than plain wrapper over CryptoAPI native (C++) functions. I would say that 95+% of cryptography stuff in .NET are wrappers over CryptoAPI functions.
Years ago I wrote a blog post that explains how chain building is performed in Microsoft Windows: Certificate Chaining Engine — how it works. This post explains how chaining engine builds the chain and bind certificates in the chain before sending it to validation routine. Chain validation is a much more complex process. Validation logic is described in RFC 5280, §6

Sign a Pdf Document using EvoPdf

I‘m trying to sign a existing PDF with a certificate that’s located in my local PCs store. Based on this article I identified he correct certificate of type X509Certificate2.
For signing I want to use EvoPdf (unfortnately I can't use iTextSharp here; this would make things easier) and the demo for signing uses a DigitalCertificate which doesn’t provide the information (especially the subject property which is almost totally different in the two classes) I need to identify the correct certificate (I could manually identify the correct certificate from DigitalCertificatesStore.GetCertificates(CertSystemStore.PersonalCertificates) but that doesn’t help me much since the certificate might change and I need a robust and lasting solution.
What I did right now is 1st: identify the correct certificate from X509Store(StoreName.My) and keep the SerialNumber (of type String) and compare it to the SerialNumber (of type Byte[]) of the DigitalCertificate which is the best change to get a match:
foreach (DigitalCertificate cert in DigitalCertificatesStore.GetCertificates(CertSystemStore.PersonalCertificates))
{
var serialNumber = String.Empty;
foreach (var b in cert.SerialNumber)
{
serialNumber = $"{b:X2}{serialNumber}";
}
if (serialNumber != x509Cert.SerialNumber)
{
continue;
}
this.pdfCertificate = cert;
break;
}
Anyone got a better solution for this? (How) can I use the x509 cert with EvoPdf directly?
In .NET 5/6 I'm using code like this:
// Get Personal Certificates with EvoPDF API
var evoCerts = DigitalCertificatesStore.GetCertificates();
byte[] evoSerNum = Convert.FromHexString(x509Cert.SerialNumber).Reverse().ToArray();
// Match x509Cert and evoCert by SerialNumber
this.pdfCertificate = evoCerts.GetCertBySerialNumber(evoSerNum);

C# m2mqtt to connect to AWS broker using Root CA, key, and certificate

I am trying to use M2MQtt library to connect to AWS MQTT broker using a root CA, client certificate and key. I am using the following C# client connection code
MqttClient client = new MqttClient(
endPoint,
MqttSettings.MQTT_BROKER_DEFAULT_SSL_PORT,
true,
new X509Certificate2(#"ca.pem"),
new X509Certificate2(#"certificate.pem"),
MqttSslProtocols.TLSv1_2
);
client.Connect(Guid.NewGuid().ToString());
however, this fails with a FormatException error. It's probably related to the fact that I don't know where to pass in the private key for this connection. This is something that I already have working, prototyped in Python using AWSIoTPythonSDK (see below)
from AWSIoTPythonSDK.MQTTLib import AWSIoTMQTTClient
f = open('mqttEndpoint.txt', 'r')
awsHost = f.read()
f.close()
myAWSIoTMQTTClient = AWSIoTMQTTClient('foo')
myAWSIoTMQTTClient.configureEndpoint(awsHost, 8883)
myAWSIoTMQTTClient.configureCredentials('ca.pem', 'id_rsa', 'certificate.pem')
Does anyone know how this is supposed to work?
I figured out my problem. The clue was the fact that to properly authenticate against AWS, you need to provide both the certificate (a PEM in my case) as well as the private key, which I could not figure out how to pass into MqttClient() constructor, because it takes only one "certificate".
The solution is to use a PFX/P12 certificate, which includes inside it both a PEM and a private key (thank you, Microsoft, for being different). There are many resources that explain how to create a PFX from a PEM+key (i.e. here, here, here, here, etc). Then you have to use a X509Certificate2() class to pull in the PFX file (that '2' is
MqttClient client = new MqttClient(
endPoint,
MqttSettings.MQTT_BROKER_DEFAULT_SSL_PORT,
true,
rootCa,
new X509Certificate2(#"certificate.pfx", #""); // My PFX was created with a blank password, hence empty string as 2nd arg
MqttSslProtocols.TLSv1_2
);
client.Connect(Guid.NewGuid().ToString());

How to check if a X509 certificate has "Extended Validation" switched on?

I'm struggling to find a reliable way to check from my C# (.Net 4.0) application if an X509Certificate (or X509Certificate2) has the "Extended Validation" (EV) flag set. Does anyone know the best method?
You could check if the X509Certificate contains one of these OIds. Additionally you can check Chromium's Source for a list of implemented OIds. You can find the Source here. If you'd like to stick to Firefox, you can grab the implementation here.
I now updated my source and tested it. I've written a small method to validate a X509Certificate2 against the OId-List from Wikipedia/Chromium. In this method I am using the Wikipedia-List, it might be better to take the Chromium-List instead.
How is the OId saved?
Each CAhas one or more ObjectIds OIds. They are not saved as an Extension as you might guess, they are saved as an entry within the Policy Extensions. To get the exact Extension it's recommended to use the Oid of the Policy Extension itself rather then using a Friendly Name. The OId of the Policy Extensions is 2.5.29.32.
Extracting the Information
To get the inner content of the Policy Extensions we can use System.Security.Cryptography.AsnEncodedData to convert it to a readable string. The string itself contains the policies we need to match against our string[] to ensure if it contains one of the OIds of an EV Certificate.
Source
/// <summary>
/// Checks if a X509Certificate2 contains Oids for EV
/// </summary>
/// <param name="certificate"></param>
/// <returns></returns>
private static bool IsCertificateEV(X509Certificate2 certificate)
{
// List of valid EV Oids
// You can find correct values here:
// http://code.google.com/searchframe#OAMlx_jo-ck/src/net/base/ev_root_ca_metadata.cc&exact_package=chromium
// or in Wikipedia
string[] extendedValidationOids =
{
"1.3.6.1.4.1.34697.2.1",
"1.3.6.1.4.1.34697.2.2",
"1.3.6.1.4.1.34697.2.1",
"1.3.6.1.4.1.34697.2.3",
"1.3.6.1.4.1.34697.2.4",
"1.2.40.0.17.1.22",
"2.16.578.1.26.1.3.3",
"1.3.6.1.4.1.17326.10.14.2.1.2",
"1.3.6.1.4.1.17326.10.8.12.1.2",
"1.3.6.1.4.1.6449.1.2.1.5.1",
"2.16.840.1.114412.2.1",
"2.16.528.1.1001.1.1.1.12.6.1.1.1",
"2.16.840.1.114028.10.1.2",
"1.3.6.1.4.1.14370.1.6",
"1.3.6.1.4.1.4146.1.1",
"2.16.840.1.114413.1.7.23.3",
"1.3.6.1.4.1.14777.6.1.1",
"1.3.6.1.4.1.14777.6.1.2",
"1.3.6.1.4.1.22234.2.5.2.3.1",
"1.3.6.1.4.1.782.1.2.1.8.1",
"1.3.6.1.4.1.8024.0.2.100.1.2",
"1.2.392.200091.100.721.1",
"2.16.840.1.114414.1.7.23.3",
"1.3.6.1.4.1.23223.2",
"1.3.6.1.4.1.23223.1.1.1",
"1.3.6.1.5.5.7.1.1",
"2.16.756.1.89.1.2.1.1",
"2.16.840.1.113733.1.7.48.1",
"2.16.840.1.114404.1.1.2.4.1",
"2.16.840.1.113733.1.7.23.6",
"1.3.6.1.4.1.6334.1.100.1",
};
// Logic:
// Locate Certificate Policy Extension
// Convert to AsnEncodedData (String)
// Check if any of the EV Oids exist
return (
from X509Extension ext in certificate.Extensions
where ext.Oid.Value == "2.5.29.32"
select new AsnEncodedData(ext.Oid, ext.RawData).Format(true))
.Any(asnConvertedData => extendedValidationOids.Where(asnConvertedData.Contains).Any()
);
}
If you need some source to get started:
static void Main(string[] args)
{
// Create Delegate for analysis of X509Certificate
ServicePointManager.ServerCertificateValidationCallback = ValidateServerCertificate;
// Make sample request to EV-Website to get Certificate
var wc = new WebClient();
wc.DownloadString("https://startssl.com"); // EV
wc.DownloadString("https://petrasch.biz"); // Not EV
Console.ReadLine();
}
public static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
var cert = (X509Certificate2) certificate;
Console.WriteLine("Certificate: " + cert.GetNameInfo(X509NameType.SimpleName, true) + " -> " + IsCertificateEV(cert));
return true;
}
If someone knows a better way to achieve this goal, please let us know.
I thought I would post a more complete answer even though this question is quite old. I won't piggy back off of the existing answer so that this one is complete.
An EV certificate has a few checks that need to pass in order for a browser to consider that the certificate is EV.
That the certificate has a Policy Identifier that is known to be an EV policy.
The certificate's root's thumbprint matches a pinned policy identifier.
The certificate must pass online revocation checking.
If the certificate's notBefore (issuance date) is after 1/1/2015, the certificate must support Certificate Transparency.
The certificate must be issued by a trusted root.
That all chains are valid if there are multiple trust paths.
Let's dissect each of these.
Policy Identifier
A certificate has an extension called policy identifiers. Extensions can be accessed from X509Certificate2.Extensions property. The policy identifier extension has an Object Identifier ("OID") of 2.5.29.32. So we can get the raw extension using something like this:
var extension = certificate.Extensions["2.5.29.32"]
If this returns null, meaning there is no policy at all, you can right off the bat assume this is not an EV certificate.
More likely though the certificate has some kind of policy. In this case, you need to decode the data. The attribute will give it to you in raw ASN.1, we need to make sense out of it.
Unfortunately there is nothing in .NET that can do it out of the box today. However CryptDecodeObjectEx can do it if you use platform invoke. The specifics on doing so I'll leave out, but there is plenty of information around to show how to call this function. You'll want to call it with the lpszStructType parameter set to a value of (IntPtr)16. This will give you back an CERT_POLICIES_INFO structure, which has a count and pointer to an array of CERT_POLICY_INFO structures. This structure has a field on it called pszPolicyIdentifier. It's this policy OID that we are interested in.
Every certificate authority has one or more OIDs they use to make a certificate as EV. Every CA documents them on their policies page. However, the best place to get an up-to-date list of this is probably Chromium's Source Code.
If the certificate has a policy that matches one of those OIDs, then we can move on to the next check.
Root Fingerprint
If you look at the Chromium Source in the above link, you'll see in addition to policy identifiers, it also keeps the SHA256 fingerprint of the root.
This is because in addition to the certificate having the proper OID, it must be issued by a CA whose fingerprint matches. In the Chromium source, we see something like this:
{{0x06, 0x3e, 0x4a, 0xfa, 0xc4, 0x91, 0xdf, 0xd3, 0x32, 0xf3, 0x08,
0x9b, 0x85, 0x42, 0xe9, 0x46, 0x17, 0xd8, 0x93, 0xd7, 0xfe, 0x94,
0x4e, 0x10, 0xa7, 0x93, 0x7e, 0xe2, 0x9d, 0x96, 0x93, 0xc0}},
{
// AC Camerfirma uses the last two arcs to track how the private key
// is managed - the effective verification policy is the same.
"1.3.6.1.4.1.17326.10.14.2.1.2", "1.3.6.1.4.1.17326.10.14.2.2.2",
}
So the certificate must have either the "1.3.6.1.4.1.17326.10.14.2.1.2" or "1.3.6.1.4.1.17326.10.14.2.2.2" policy identifiers, but the root must have a SHA1 fingerprint of the binary seen above.
This prevents a rogue CA from ever using a policy ID it doesn't own.
Revocation Checking
If the browser is unable to check if the certificate is revoked, then it will not be considered an EV certificate. Online revocation checking must be done, though the client may cache the result.
You can perform revocation checking when using X509Chain.Build by setting the appropriate flags on the chain before calling Build.
Certificate Transparency
This one is a bit harder to check, but Google has appropriate documentation on the Certificate Transparency website. If the certificate was issued after 1/1/2015, certificate transparency is required. Some certificates are also whitelisted by Chrome as indicated on the Chromium Project Page.
Trusted Root
This one is fairly straight forward, but the certificate must belong to a trusted root. If the certificate is self signed, it cannot be EV. This can be checked again when calling X509Chain.Build().
Multiple Trust Paths
It is possible for a certificate to have multiple trust paths, say if the certificate was issued by a root that was cross-signed. If there are multiple trust paths, all paths must be valid. Likewise revocation checking must be done with all paths. If any of the paths show the certificate as revoked, then the certificate is not valid.
Unfortunately .NET and even Win32 do not have a great means of checking all certificate chains or even getting more than one chain, as far as I know.
Combining all of these, if they all pass, then the certificate can be considered to be an EV certificate.

Categories