x509Chain.build fails, certutil -verify passes - c#

I have a root certificate and a leaf. The leaf has a CRL URL OID extension which points to a valid online location. Doing this:
certutil -verify .\leaf.cer
fails with
ERROR: Verifying leaf certificate revocation status returned The revocation function was unable to check revocation because the revocation server was offline. 0x80092013 (-2146885613 CRYPT_E_REVOCATION_OFFLINE)
If I do this:
certutil -verify .\leaf.cer .\root.cer
Then verification passes, and I see the CRL getting pulled from online in Fiddler.
In my C# code, I do this:
X509Chain childCertChain = new X509Chain();
childCertChain.ChainPolicy.ExtraStore.Add(rootCert);
childCertChain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
childCertChain.ChainPolicy.UrlRetrievalTimeout = TimeSpan.FromSeconds(10);
if (!childCertChain.Build(childCert))
{
// The root cert is not in the windows certificate store, that is fine
if (childCertChain.ChainStatus.Length != 1 || childCertChain.ChainStatus.First().Status != X509ChainStatusFlags.UntrustedRoot)
{
throw new Exception("Certificate validation error.");
}
}
This will hit my exception, and even though chainElements will be correctly filled with the 2 certs, ChainStatus will show:
OfflineRevocation, RevocationStatusUnknown
I also will not see any web requests in Fiddler. I can programmatically download the CRL given the URL so it's not my debug environment AFAIK. Any ideas how to get x509Chain.Build to succeed?

I've read your code from PC and figured what's up: your root certificate is not trusted on your system. Windows CryptoAPI throws CERT_E_UNTRUSTEDROOT when root certificate is not trusted. Along with this, CryptoAPI terminates revocation checking and throw two addition errors: CRYPT_E_NO_REVOCATION_CHECK and CERT_E_UNTRUSTEDROOT, because revocation checking was skipped. In addition with untrusted root, CryptoAPI skips other checks (such as constraints, for example), but do not report them. Everything is correct and as expected.
You are trying to allow untrusted root, but do it incorrectly, because this way you skip other checks (as mentioned in previous paragraph) and your logic is vulnerable.
You can exclude untrusted root exception in chain settings and force CryptoAPI to continue validation and return success if no other errors found, but I strongly recommend to not do that, because then you are open to any kind of MITM.
You have two options:
make root certificate trusted by a machine (local system). This may not be possible.
call CertCreateCertificateChainEngine function directly and put your root certificate in hRestrictedTrust member of CERT_CHAIN_ENGINE_CONFIG structure.
If you are on Windows, 2nd approach is the most secure and reliable. As of now, .NET's X509Chain doesn't implement this functionality. It requires extra coding, but there are no much alternatives.

Related

Using C#, how to programmatically determine if a certificate in a Windows certificate store has been disabled

I'm currently working on refining communications between mutually authenticated client/server applications using HTTPS. I am currently building validation logic into a C# client to help identify configuration issues when a TLS connection fails. In verifying the connection, I validate that the root CA certificate presented by the server is installed on the client, in the appropriate store, and is valid. I'm using X509Store to pull the X509Certificate2, and validating it using X509Chain.
My issue is that the certificate will report as valid even if the certificate has been disabled via MMC. So the TLS connection will fail, despite the chain reporting as valid.
It's an unlikely case, but one I'd like to handle by reporting something like "Could not connect because root CA is disabled."
Could anyone point me in the direction of a .NET or Win32 call that could be made to determine the value of "Certificate Purposes" for a certificate? Or to read the "Certificate Status" for a cert?
I read through MSDN's listing of what's in the System.Security.Cryptography namespace, and started looking into CryptoAPI and CNG, but didn't find anything so far.
Thanks!
That dialog does not "disable" a certificate it disables it "for all purposes". What this means is it counts as having an empty Enhanced Key Usage extension for purposes of EKU validation.
Normally a root certificate (or an intermediate CA certificate) will not have an EKU extension, so if you do a chain build with any ApplicationPolicy value it will be a match. Once you set it to Disable for all purposes you'll get a chain error X509ChainStatusFlags.NotValidForUsage.
If you want to build something for validating TLS you'd check either the Server Authentication or Client Authentication EKUs (depending on what you're checking):
// Server EKU or Client EKU
Oid eku = forServer ? new Oid("1.3.6.1.5.5.7.3.1") : new Oid("1.3.6.1.5.5.7.3.2");
// Test if it's valid for that purpose
chain.ChainPolicy.ApplicationPolicy.Add(eku);
If you want to "Disable" a root CA altogether, add a copy of the certificate to the Disallowed store (called "Untrusted Certificates" in the UI). That will result in a chain build producing X509ChainStatusFlags.ExplicitDistrust.

How to proper validate SSL certificate with X509Certificate2 on Mono and multiple platforms

I have to validate several SSL certificates in a none-browser application for HttpRequests and websocket connections which should run on IOS, Android and Linux.
When a connection via HTTPS happens I receive an array of X509Certificate2 objects where the bottom one is the server certificate and the most top one the root CA (hopefully). As an example, when I connect to https://google.com I receive 3 X509Certificate2 with the following SubjectName.Name:
0 : "CN=google.com, O=Google Inc, L=Mountain View, S=California, C=US"
1 : "CN=Google Internet Authority G2, O=Google Inc, C=US"
2 : "CN=GeoTrust Global CA, O=GeoTrust Inc., C=US"
I now need to verify the certificate validation with the given information and the following verifications:
Chain-of-trust verification
Hostname verification
Certificate revocation verification
What I tried and not understood and failed:
When I call the X509Certificate2.Verify() method on each certificate independently it returns false everytime. I also do not understand why it can return anything else then false because the verification happens independently. Instead the complete chain, meaning all certificates, should be checked as far as I understood the theory.
I then used the X509Chain class:
foreach (X509Certificate2 cert in allthreecerts)
{
X509Chain chain = new X509Chain();
X509ChainPolicy chainPolicy = new X509ChainPolicy()
{
RevocationMode = X509RevocationMode.Offline,
RevocationFlag = X509RevocationFlag.EntireChain
};
chain.ChainPolicy = chainPolicy;
if (!chain.Build(cert))
{
foreach (X509ChainElement chainElement in chain.ChainElements)
{
foreach (X509ChainStatus chainStatus in chainElement.ChainElementStatus)
{
Debug.WriteLine(chainStatus.StatusInformation);
}
}
}
}
This prints out RevocationStatusUnknown and OfflineRevocation for each certificate.
Again, I do not understand why this should work since a chain is build which each certificate independently. Should not be the parent certificate be the Issue of the child certificate all down to the root CA?
What I think I need somehow but do not know how.
In order to verify the certificate revocation all clients need to provide a certificate revocation list. Where do I get such a list and where to I load it and tell the chain to use this local list?
The same problem is the Chain-of-trust verification since the last certification should be a root certification and must be one of the clients trusted root CA's. So I have to load, for example, all root CA which are shipped with Firefox and check if the root CA is one of them? What is the best way to do that?
[Update]
I made a copy paste mistake in the google.com example where I pasted two identical certificate subject names.
When a connection via HTTPS happens I receive an array of X509Certificate2 objects where the bottom one is the server certificate and the most top one the root CA (hopefully).
If you really did get root ca certificate from SSL server then the SSL server is not configured correctly. It should return its server certificate and the whole chain Except the root CA certificate.
When I call the X509Certificate2.Verify() method on each certificate independently it returns false everytime.
As stated in the documentation of X509Certificate2.Verify method This method builds a simple chain for the certificate and applies the base policy to that chain. Now what is the base policy I do not know. But on of the default values of ChainPolicy is RevocationMode = X509RevocationMode.Online. As for Mono the source of this method can be found here. The source of Mono implementation of X509Chain can be found here.
This prints out RevocationStatusUnknown and OfflineRevocation for each certificate.
What else should it print when you specified RevocationMode = X509RevocationMode.Offline and there are no CRLs or OCSP responses in cache (probably)?
In order to verify the certificate revocation all clients need to provide a certificate revocation list. Where do I get such a list and where to I load it and tell the chain to use this local list?
Each certificate (well except root ca certificate) in the chain contains a link (or links) to CRL. .NET and most likely Mono too has an implementation that will find link to CRL in the certificate, will download it and will check if the certificate is not revoked.
The same problem is the Chain-of-trust verification since the last certification should be a root certification and must be one of the clients trusted root CA's. So I have to load, for example, all root CA which are shipped with Firefox and check if the root CA is one of them? What is the best way to do that?
No, RevocationFlag = X509RevocationFlag.EntireChain will do that for you using some store that mono uses. I don't know if it is the Firefox store but on Linux it has its own store and you can import root ca certificates from Firefox store. Check ChainElements and see for yourself what certificates did it find.
I would suggest that you build the chain with SSL server certificate only (as this will check all certs in the chain upwards) with RevocationMode = X509RevocationMode.Online and RevocationFlag = X509RevocationFlag.EntireChain. I would also try to set ExtraStore property of X509Chain to a list of certificate you got from SSL server. After the Build method I would chceck ChainStatus property of X509Chain object which is an array of statuses. I would pick all statuses that have not set X509ChainStatus.Status to NoError. If there will be any such status I would throw and log each X509ChainStatus.Status and X509ChainStatus.StatusInformation.
HTH

Issue with X509RevocationMode.Online in revoke certificate validation

I am validating the certificate revocation in online mode but the url mentioned in CRL Distribution Point is not getting hit if the CRL is already cached in memory. I am using fiddler to verify if the URL is accessed or not. I am following these steps.
Run the fiddler.
Start certificate validation in X509RevocationMode.Online
Verify fiddler, the url mentioned in CRL Distribution Point is not caught.
Clear the crl from memory by the command certutil -urlcache CRL delete
Start certificate validation in X509RevocationMode.Online
Now the Fiddler caught the URL mentioned in CRL Distribution Point.
From above steps it is clear that the CRL's url will be hit only if the CRL is not cached. Now my questions are:
What are the scenarios when URL mention in CRL Distribution Point is accessed in online mode?
If CRL is already cached, how X509Certificate validated that the CRL is updated or not without hitting the URL?
Am I missing the concept of CRL?
Here is my code
private void BuildCertificateChain(X509Certificate2 certificate)
{
string error = null;
X509Chain certificateChain = new X509Chain();
certificateChain.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain;
certificateChain.ChainPolicy.VerificationTime = DateTime.Now;
certificateChain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
certificateChain.ChainPolicy.UrlRetrievalTimeout = new TimeSpan(0, 0, 15);
try
{
if (certificateChain.Build(certificate))
{
foreach (X509ChainElement element in certificateChain.ChainElements)
{
Trace.WriteLine(string.Format("Issuer = {0}\nSubject = {1}", element.Certificate.Issuer, element.Certificate.Subject));
element.Certificate.Verify();
}
}
else
{
error = string.Format("File {0} digital signature seems to be not valid due to a certificate in certificate chain being revoked. Revocation reasons are:\n", filename);
foreach (X509ChainStatus status in certificateChain.ChainStatus)
{
error += status.StatusInformation;
}
}
}
catch (Exception ex)
{
error = string.Format("Exception building certificate chain for executing application {0}. The error is {1}", _executingAppFileName, ex.Message);
}
if (!string.IsNullOrEmpty(error))
{
//SetError(error);
}
}
}
Using a cached version and not re-retrieving the CRL is usually a feature, not a bug.
What should happen:
The CRL issuing website should be using proper caching instructions to the https client who is retrieving the CRL for verification.
The cache on your machine should be implemented according to the instructions from the server. (By the way, disks are usually used for cached internet files, not memory.)
But, neither of the above may be true. If you want to be paranoid, you could flush the internet doc cache in the OS.
Re your questions:
What are the scenarios when URL mention in CRL Distribution Point is accessed in online mode? [When the CRL is not in the cache]
If CRL is already cached, how X509Certificate validated that the CRL is updated or not without hitting the URL? [The cache controls of https are used to assume that the cached version of the CRL is the same as the version on the remote server.]
Am I missing the concept of CRL? [Perhaps. The CRL process is not meant to be a gonzo-realtime synchronized multi-machine system. The idea is that most of the time, certs naturally expire from their expiration date. The revocation / CRL process should not be a normal process, should be more of an exception process. Your questions imply that the CRL is being updated on a second by second basis--so quickly that normal web caching techniques are not acceptable. Why do you believe this? What are you trying to protect yourself against? Are humans making the decision to revoke the certs before their normal expiration time or machines?]
To put it another way, if the CRL is being updated all the time, then it should be sent with the caching headers set accordingly. In that case, you should test that your OS is correctly not caching the result. If you're worried that the OS is wrong, then you should explicitly delete the cache.
Added:
A blog entry about examining malware's digital certificates.

WCF Error : 'It is likely that certificate 'my cert' may not have a private key that is capable of key exchange

I have a WCF service I'm trying to host on our production web server (IIS6). I've set the web up and tied our cert to the web. When I try to browse to the service url, I receive the following error in the event log :
The exception message is: It is likely that certificate
'CN=.mydomain, OU=Secure Link SSL Wildcard, OU=I.T., C=US' may not
have a private key that is capable of key exchange or the process may
not have access rights for the private key. Please see inner exception
for detail.. ---> System.ArgumentException: It is likely that
certificate 'CN=.mydomain.com, OU=Secure Link SSL Wildcard,
OU=I.T., O=mydomain, C=US' may not have a private key that is capable
of key exchange or the process may not have access rights for the
private key. Please see inner exception for detail. --->
System.Security.Cryptography.CryptographicException: The handle is
invalid.
I've confirmed ASP.Net 1.1, 2, and 4 are all set to 'Allow' in 'Web Service Extensions'. I've also confirmed the cert is set up in iis and it shows 'You have a private key that corresponds to this certificate'. Also, Execute Permissions are set to 'Script and Executables'.
I had this problem, and it turned out that the account the service was running under did not have permissions to access the certificate's private key.
Here are the steps I used to solve it:
Start the Cetificate manager. Do this by running MMC, activate [File]-[Add/Remove Snap-in...], then add "Certificates", selecting "Computer Account" and "Local Computer" in the ensuing wizard dialogs.
In the certificate manager, right-click on the relevant certificate and activate [All Tasks]-[Manage Private Keys]
This gives you a permissions window. Click Add
Add the account name or group that this service runs under.
Seems like your certificate was created for signatures and not key exchange, what I suppose to be normal for SSL certificates.
If you look at the makecert documentation, you can see that the -sky switch lets you specify whether the certificate should be used for signatures or key exchange. You can try to create a self-signed certificate with type exchange and test whether the exception still occurs. Don't forget to put the self-signed certificate into the machine's trusted root certification authority folder in order to avoid exceptions that the certificate is not valid.
Ensure also that the account name or group that needs to access the certificate ALSO has access to the folder hierarchy that the certificate resides in. If your certificate is hiding in, for example, 'C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys', and the account that needs to access it is 'NETWORK SERVICE', then 'NETWORK SERVICE' needs access to that full path. Just assigning rights to the file is not enough.
Same problem here. To fix the problem I added the following line to the <system.web> node of the web.config.
<httpRuntime targetFramework="4.7.2"/>
I had this issue today and it was on a server cloned from another server. I had to uninstall the certs, reinstall the certs, and grant access to the certs (same manner as described in accepted answer).

Can a .NET assembly be signed using an X509Certificate?

I believe I have a foundational understanding of the X509Certificate and have created certificates and private keys for my Development environment. Getting more acquainted now with the X509Certificate Class and other relevant permissions also.
So in the process, I decided to test a certificate after having installed it on my system. Then using the following code, I attempted to validate the check process for certification authentication:
const string x509Cert = #"\PathToMyCertificate\LMC.cer";
var cert = new X509Certificate(x509Cert);
var pmc = new PublisherMembershipCondition(cert);
if(pmc.Check(Assembly.GetCallingAssembly().Evidence))
{
Console.WriteLine("Assembly belongs to publisher");
}
Of course as expected, the inner block of code doesn't execute. So then I figured to simply sign my assembly using the certificate key, but "simply" wasn't as simple as I'd anticipated.
I used the following code in effort of assigning the certificate to my applications Evidence:
var publisher = new Publisher(X509Certificate.CreateFromCertFile(x509Cert));
var evidence = Assembly.GetCallingAssembly().Evidence;
evidence.AddHost(publisher);
// Create an identity permission based on publisher evidence.
var x509Permission = (PublisherIdentityPermission)publisher.CreateIdentityPermission(evidence);
x509Permission.Demand();
But this didn't seem to work either. Next, I checked the properties of my project to see if there was any way to sign it there using the X509Certificate key but nothing. The only option I see that comes close is to sign with Click Once manifests; but the "Select from file" option is looking for a .pfx extension. So I think maybe this method only works to support certificates generated by Click-Once?
Per BOL, "If you have a key file or certificate in another format, store it in the Windows certificate store and select the certificate is described in the previous procedure." I installed my X509Certificate in the Trusted Root Certificate Authorities store. Wouldn't that be a Windows certificate store? Because nothing shows up in the Certificate Store window.
Searching online resources didn't yield much either unless I am using the wrong combination of keywords. Now I could create another X509Certificate and key ensuring that the extension of the key is .pfx but I wanted to make certain that I am on the right course of resolve before spinning my wheels for nothing and I don't believe that would be the answer.
So, can a .NET assembly be signed using an X509Certificate? If so, what documentation is available to assist in performing this task?
Publisher* classes are associated with Authenticode(tm).
Look for the command-line tools:
* signcode (or signtool)
* chktrust
for how you can sign any .exe, .dll (and .cab, .ocx) using a valid code-signing certificate. A google search on Authenticode or "code-signing certificate" can also be helpful.
The question is what you want to do. There exist .NET signing (using RSA keypair) used for strong-naming the assemblies, and there exists Authenticode which lets you sign any file in PE format including assemblies in DLL files. Note, that Authenticode is not .NET-specific and knows nothing about .NET. It signs PE structure.
As said by poupou, for Authenticode signing (using X.509 certificates suitable for Code Signing) you can use SignTool.exe tool. .NET will verify the signature when it loads the assembly, but in some cases such verification can take extra seconds (if the OS performs CRL and OCSP checking of certificates in the chain), slowing down assembly loading.
So you need to choose the right tool and define what you want to achieve (signing is a method, not a goal).

Categories

Resources