How to get Thumbprint or Public Key of Issuer Certificate? - c#

We have created a self signed CA certificate which we use to sign other certificates for SSL purposes. These certificates will be installed in other servers which we do not have access to and will be strictly to communicate with other clients like mobile applications.
When these clients (written in .NET) make a request to the servers using HTTPS we get the "Invalid certificate received from server" error because the CA cert is not a trusted CA on that client.
We want to bypass this security using the ServicePointManager.ServerCertificateValidationCallback, but only if the certificate being used was signed by our CA certificate.
I can check the certificate.Issuer, but that can easily be spoofed by anyone. How can I get the Thumbprint or Public Key of the Issuer certificate of the invalid certificate? If I can get access to that I can easily compare it to the one I know is valid and ignore the certificate error and continue on with the request.
UPDATE
I think I am getting closer. It looks like what we're looking to do is not doable so went a slightly different direction.
Using the X509Chain we can verify whether the certificate is a child of the CA using the code below:
var caCert = new X509Certificate2(#"[path]\MyCA.cer");
var newChain = new X509Chain();
newChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
newChain.ChainPolicy.ExtraStore.Add(caCert);
var res = newChain.Build(certInQuestion);
Build() still returns false (as expected because the CA is not trusted on the client), but now newChain.ChainStatus[0].Status is returning UntrustedRoot. Based on my testing this means the chain validated because if I supply a different CA Certificate it fails with InvalidChain.
In conclusion, that tells me that if the Status is UntrustedRoot, the certificate was created with our CA certificate and thus it's valid, anything else it's a fake one!
Are my assumptions correct?

That's the wrong solution. You should install your self-signed certificate as a trusted CA certificate in all clients, or better still just get it signed by a CA that is already trusted. Don't write code for this.

Here are some links with info and some tools to generate free public and private keys:
https://www.igolder.com/pgp/
https://www.igolder.com/pgp/generate-key/
I think EJP's solution is the most acceptable. #EJP, can you give us some examples of applications that can help us become a "tiny CA".

I'm not entirely sure this this is what you're looking for but it might push you in the right direction. Here's a PowerShell script I use to find a cert that I just created and extracted using MAKECERT.EXE & CERTMGR.EXE:
# get certificate thumbprint
$appCertificate = Get-PfxCertificate -FilePath $certificateFullPath
Write-Host " .. adding certificate to local machine root" -ForegroundColor Gray
& $ExeCertManager /add $certificateFullPath /s /r localMachine root
Write-Host " Certificate installed on local machine" -ForegroundColor Gray
Write-Host " .. exporting private key for certificate" -ForegroundColor Gray
Get-ChildItem cert:\\localmachine\my | Where-Object {$_.Thumbprint -eq $appCertificate.Thumbprint} | ForEach-Object {
$CertPfxName = (Get-Item -Path $certificateFullPath).BaseName
}

It seems that a possible solution would be that you could email the certificate, according to this answer: https://stackoverflow.com/a/4473799/674700.

Related

x509Chain.build fails, certutil -verify passes

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.

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.

Self-signed certificate for 127.0.0.1 not accepted by Chrome

Before Chrome 58, I was able to use a self-signed certificate with CN=127.0.0.1 and have Chrome accept it as long as it was in my Trusted Root certificate store.
Chrome 58 stopped accepting self-signed certificates that do not have alternative names. From my searching it seems that providing the alternative name should fix the issue, so I used this PowerShell command:
New-SelfSignedCertificate -Subject "Testing" -DnsName "127.0.0.1", "localhost" -CertStoreLocation "cert:\LocalMachine\My" -NotAfter (Get-Date).AddYears(50)
I then placed a copy of that certificate in Trusted Root Authorities.
Now, if I browse to https://127.0.0.1 I get "NET::ERR_CERT_COMMON_NAME_INVALID", but it does accept the certificate if I browse to https://localhost.
I'm wondering if there is any way to generate a certificate from New-SelfSignedCertificate that will work the way it used to before Chrome 58, ie. just by adding it to Trusted Root.
Mainly I'm trying to avoid either having to add the certificate manually in Chrome's own certificate list, having to change any settings in Chrome to allow insecure localhost certificates, or having to create 2 certificates (1 to be root and 1 to be signed by root). I'm guessing I can get it to work with one of those workarounds but those were not necessary before, so I'd just like to confirm if Chrome 58 changed something else (other than requiring the subject alternative names) that breaks the process I was used to.

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

Why does makecert not make a *valid* certificate?

I want to create an X509 certificate for testing purposes. This certificate has to be shared by 3 developers on their local machines (i.e. all share the same certificate so we can use the same thumbprint).
So the private key in this certificate has to be exportable.
I create a certificate with the following command:
makecert -r -pe -n "CN=mytestsite.local" -b 01/01/2000 -e 01/01/2036 -ss my -sr localMachine -sky exchange localhost.cer
This certificate works fine, but the trouble is that the isValid argument has to be false when calling Certificates.Find...
var store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
var cert = store.Certificates.Find(
X509FindType.FindByThumbprint,
Config.PdfCertificateThumbprint,
false //********************* This has to be false.
).OfType<X509Certificate>().FirstOrDefault();
As soon as I set that IsValid property to True, my certificate is no longer returned by the Find method. Why would makecert generate an "invalid" certificate? Or how do I figure out why the certificate is deemed invalid?
Well, it's because it's not issued by a "Trusted Certificate Authority" like the "real" ssl certificates used on the internet. (for example issued by VeriSign)
What you can do locally to work is to add the certificate manually in the Trusted Certificates for your user and/or local machine. But this procedure must be done for everyone using it until you will obtain a valid SSL certificate issued by a CA (certificate authority).
But your question points to the scenario where it's for dev purposes only so what you can do is either manually add the certificate to Trusted or you can override the certificate validation mechanism in .Net and write code that will consider your certificate valid.
You might want to experiment with the following setting that can be used in client config to bypass the certificate validation process:
<serviceCertificate>
<authentication certificateValidationMode="None"
revocationMode="NoCheck" />
</serviceCertificate>

Categories

Resources