Verify Remote Server X509Certificate using CA Certificate File - c#

I've generated a CA and multiple certificates (signed by CA) using OpenSSL and I have a .NET/C# client and server both using SslStream which each have their own certificates/keys, mutual authentication is enabled and revocation is disabled.
I'm using RemoteCertificateValidationCallback for SslStream to validate the remote server's certificate and I was hoping I could just load the CA's public certificate (as a file) in the program and use it to verify the remote certificate rather then actually installing the CA in the Windows Certificate Store. The problem is the X509Chain won't show anything else unless I install the CA into the store, either will the Windows CryptoAPI shell when I open a PEM version of one of the certificates.
My question is, how can I verify a certificate has been signed by my specific CA just by using the CA's public certificate file without using Windows certificate store or WCF when RemoteCertificateValidationCallback, X509Certificate and X509Chain don't seem to give me anything to work with?

Because the CA certificate is NOT in the root certificate store, you will have within the RemoteCertificateValidationCallback() an error flag of SslPolicyErrors.RemoteCertificateChainErrors ; a possibility is to validate explicitely the certificate chain against your own X509Certificate2Collection, since you are not using the local store.
if (sslPolicyErrors == SslPolicyErrors.RemoteCertificateChainErrors)
{
X509Chain chain0 = new X509Chain();
chain0.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
// add all your extra certificate chain
chain0.ChainPolicy.ExtraStore.Add(new X509Certificate2(PublicResource.my_ca));
chain0.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;
isValid = chain0.Build((X509Certificate2)certificate);
}
You can also re-use the chain passed in the callback, add your extra certificate(s) in the ExtraStore collection, and validate with the AllowUnknownCertificateAuthority flag which is needed since you add untrusted certificate(s) to the chain.
You could also prevent the original error by adding programmatically the CA certificate in the trusted root store (of course it opens a popup, for it is a major security problem to globally add a new trusted CA root) :
var store = new X509Store(StoreName.Root, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadWrite);
X509Certificate2 ca_cert = new X509Certificate2(PublicResource.my_ca);
store.Add(ca_cert);
store.Close();
EDIT: For those who want to clearly test the chain with your CA :
Another possibility is to use the library BouncyCastle to build the certificate chain and validate the trust. The options are clear and errors are easy to understand. In cas of success it will build the chain, otherwise an exception is returned. Sample below :
// rootCerts : collection of CA
// currentCertificate : the one you want to test
var builderParams = new PkixBuilderParameters(rootCerts,
new X509CertStoreSelector { Certificate = currentCertificate });
// crls : The certificate revocation list
builderParams.IsRevocationEnabled = crls.Count != 0;
// validationDate : probably "now"
builderParams.Date = new DateTimeObject(validationDate);
// The indermediate certs are items necessary to create the certificate chain
builderParams.AddStore(X509StoreFactory.Create("Certificate/Collection", new X509CollectionStoreParameters(intermediateCerts)));
builderParams.AddStore(X509StoreFactory.Create("CRL/Collection", new X509CollectionStoreParameters(crls)));
try
{
PkixCertPathBuilderResult result = builder.Build(builderParams);
return result.CertPath.Certificates.Cast<X509Certificate>();
...

How can I verify a certificate has been signed by my specific CA just by using the CA's public certificate file without using Windows certificate store or WCF when RemoteCertificateValidationCallback, X509Certificate and X509Chain don't seem to give me anything to work with?
The following code will avoid the Windows certificate stores and validate the chain. Its a little different than JB's code, especially in the use of flags. The code below does not require AllowUnknownCertificateAuthority (but it does use X509RevocationMode.NoCheck since I don't have a CRL).
The name of the function does not matter. Below, VerifyServerCertificate is the same callback as RemoteCertificateValidationCallback in SslStream class. You can also use it for the ServerCertificateValidationCallback in ServicePointManager.
static bool VerifyServerCertificate(object sender, X509Certificate certificate,
X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
try
{
String CA_FILE = "ca-cert.der";
X509Certificate2 ca = new X509Certificate2(CA_FILE);
X509Chain chain2 = new X509Chain();
chain2.ChainPolicy.ExtraStore.Add(ca);
// Check all properties
chain2.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag;
// This setup does not have revocation information
chain2.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
// Build the chain
chain2.Build(new X509Certificate2(certificate));
// Are there any failures from building the chain?
if (chain2.ChainStatus.Length == 0)
return true;
// If there is a status, verify the status is NoError
bool result = chain2.ChainStatus[0].Status == X509ChainStatusFlags.NoError;
Debug.Assert(result == true);
return result;
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
return false;
}
I have not figured out how to use this chain (chain2 below) by default such that there's no need for the callback. That is, install it on the ssl socket and the connection will "just work". And I have not figured out how install it such that its passed into the callback. That is, I have to build the chain for each invocation of the callback. I think these are architectural defects in .Net, but I might be missing something obvious.

Related

How to fully validate a X509 certificate?

I need to validate a certificate(X509Certificate2) the same way it´s validated before it is used for communication.
The X509Certificate2.Verify() will in this case return true while the certificate is not issued to the server it is installed on.
Is there any finished code block to do a full validation of the X509 certificate?
Regards
Edit : This is the code I have tried with :
var certificate = GetServerCertificate(CertificateStore,CertificateLocation,Thumbprint);
if(certificate != null)
{
if(certificate.Verify())
_logger.Log(NLog.LogLevel.Info, $"Yes");
else
_logger.Log(NLog.LogLevel.Info, $"No");
}
The X509Certificate2.Verify() will in this case return true while the certificate is not issued to the server it is installed on.
The Verify method doesn't check anything about hostnames. It verifies that
The certificate is not expired.
The certificate eventually chains to a trusted root authority.
All certificates in the chain have appropriately nested expiration.
The target certificate, unless it's self-issued, has a revocation endpoint, and is not revoked.
Any intermediate certificates have revocation endpoints and are not revoked.
It's exactly equal to
using (X509Chain chain = new X509Chain())
{
// Use the default vales of chain.ChainPolicy including:
// RevocationMode = X509RevocationMode.Online
// RevocationFlag = X509RevocationFlag.ExcludeRoot
// VerificationFlags = X509VerificationFlags.NoFlag
// VerificationTime = DateTime.Now
// UrlRetrievalTimeout = new TimeSpan(0, 0, 0)
bool verified = chain.Build(cert);
for (int i = 0; i < chain.ChainElements.Count; i++)
{
chain.ChainElements[i].Certificate.Dispose();
}
return verified;
}
Is there any finished code block to do a full validation of the X509 certificate?
If "full validation" just means all the things Verify does, then yes. If you also care that it's valid for use as a TLS client certificate (or TLS server certificate) then you would use the longer form (using X509Chain directly) and add an application policy requirement before calling chain.Build:
// See if it's valid as a TLS server
chain.ChainPolicy.ApplicationPolicy.Add(new Oid("1.3.6.1.5.5.7.3.1"));
// Alternatively, if it's valid as a TLS client
chain.ChainPolicy.ApplicationPolicy.Add(new Oid("1.3.6.1.5.5.7.3.2"));
The hostname is much harder. Client certificates don't have validatable names, it's just up to what the server does with it. Server certificates have hostname matching against SAN/Subject-CN, but there's nothing built-in that does that check other than just connecting with TLS (SslStream).

Accept only self signed SSL-Certificate C#

I have a selfprogrammed API running on a Windows Server 2012 with a self signed SSL-Certificate. Now I want to communicate with the webservice via HTTPS.
The comunication is only at a local network, but i still want the conection to be secure.
Is there a way to accept only my self signed certificate? I found a lot of solutions to accept all certificates, but I only want mine to be accepted.
I already thought about adding it to the windows accepted certificates, but since the program consuming the webservice is user by serveral users on diffrent PC's and I do not have administration rights on all off them.
Is it even possible to have a secure connection the way I want it to be?
Yes, as Gusman suggests, you should implement your own method for the ServerCertificateValidationCallback. You can compare the thumbprint of the cert to validate whether it is the one you want to trust. Something like this should work:
public static class CertificateValidator
{
public static string TrustedThumbprint { get; set; }
public static bool ValidateSslCertificate(
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors errors)
{
// Wrap certificate to access thumbprint.
var certificate2 = new X509Certificate2(certificate);
// Only accept certificate with trusted thumbprint.
if (certificate2.Thumbprint.Equals(
TrustedThumbprint, StringComparison.OrdinalIgnoreCase))
{
return true;
}
// In all other cases, don't trust the certificate.
return false;
}
}
In your startup code, include the following to wire this up:
// Read your trusted thumbprint from some secure storage.
// Don't check it into source control! ;-)
CertificateValidator.TrustedThumbprint = SomeSecureConfig.Get["TrustedApiThumbprint"];
// Set the callback used to validate certificates.
ServicePointManager.ServerCertificateValidationCallback += CertificateValidator.ValidateSslCertificate;

SslStream authentication failure

everyone, I'm trying to write something about SSL and here's the question:
I've built things below:
CA certs (a self-made CA)
Server pfx, Server cert, Server key (signed by the self-made CA to "localhost")
Now I'm using .Net SslStream to test the connection:(Client and Server are in different thread, and the TCP connection was built already)
Client:
sslStream.AuthenticateAsClient("localhost");
Server:
sslStream.AuthenticateAsServer(serverCert);
//serverCert is X509Certificate2 built from "server.pfx"
the client's AuthenticateAsClient Method will throw a Exception
"The remote certificate is invalid according to the validation procedure."
I guess the reason is that the Server's certificate is signed by a untrusted CA, so the authentication failed, then how could I add the CA certificate to my trust list?
I tried to add code below in client code, but it won't work
X509Store store = new X509Store(StoreName.TrustedPublisher, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadWrite);
store.Add(new X509Certificate2(Resources.CACertPath));
store.Close();
sslStream.AuthenticateAsClient("localhost");
The following code will avoid the Windows certificate stores and validate the chain.
I see no reason to add the CA certificate needed to verify a chain to the hundreds in the certificate store already. That means Windows will try to verify the chain with "hundreds + 1" certificates rather than the one certificate truly required.
I have not figured out how to use this chain (chain2 below) by default such that there's no need for the callback. That is, install it on the ssl socket and the connection will "just work". And I have not figured out how install it such that its passed into the callback. That is, I have to build the chain for each invocation of the callback. I think these are architectural defects in .Net, but I might be missing something obvious.
The name of the function does not matter. Below, VerifyServerCertificate is the same callback as RemoteCertificateValidationCallback in SslStream class. You can also use it for the ServerCertificateValidationCallback in ServicePointManager.
static bool VerifyServerCertificate(object sender, X509Certificate certificate,
X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
try
{
String CA_FILE = "ca-cert.der";
X509Certificate2 ca = new X509Certificate2(CA_FILE);
X509Chain chain2 = new X509Chain();
chain2.ChainPolicy.ExtraStore.Add(ca);
// Check all properties
chain2.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag;
// This setup does not have revocation information
chain2.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
// Build the chain
chain2.Build(new X509Certificate2(certificate));
// Are there any failures from building the chain?
if (chain2.ChainStatus.Length == 0)
return true;
// If there is a status, verify the status is NoError
bool result = chain2.ChainStatus[0].Status == X509ChainStatusFlags.NoError;
Debug.Assert(result == true);
return result;
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
return false;
}

Can I ensure, using C#, that an X509Certificate was issued by a trusted authority?

If I use X509Certificate.CreateFromSignedFile to get the certificate used to sign a file, can I confirm that it was signed by a trusted authority - and isn't just a "self-signed" cert of some kind?
I want to extract the "Subject" (company) name from the cert to ensure that an unmanaged DLL I'm using is unmolested (I can't checksum it as it's updated frequently and independently) and official.
However, I'm concerned that a fake DLL could be signed with a "self-signed" cert and return the original company's name. So, I want to ensure the the cert was issued by Versign, Thwate or similar (anything installed on the cert repository on the machine will be fine).
How can I do this, if at all, when using X509Certificate.CreateFromSignedFile? Or does it do this automatically (i.e. a "self-signed" cert will fail)?
If it is not valid certificate you will get an exception. What concerns that you want to check the Company name and etc...
Here is the code :
ServicePointManager.ServerCertificateValidationCallback +=
new System.Net.Security.RemoteCertificateValidationCallback(customXertificateValidation);
private static bool customXertificateValidation(
object sender, X509Certificate cert,
X509Chain chain, System.Net.Security.SslPolicyErrors error)
{
// check here 'cert' parameter properties (ex. Subject) and based on the result
// you expect return true or false
return false/true;
}
EDIT : The above code is suitable only when requesting https resource which is got not valid(self-signed, expired...etc) certificate. What concerns extracting signatures from signed files please check here : Extracting Digital Signatures from Signed Files with .NET
Isn't Verify() method enough?

Validating a CAcert certificate in C#

I'm currently creating a C# program which will be fetching some data over https from my server. The server in question is using a CAcert certificate (http://www.cacert.org/), and I need a way of validating the servers certificate (checking the Subject and that it is signed by the cacert root certificate).
I'd like to do this without having to import the CAcert root as a trusted CA into the windows certificate store, some people might not like that, and AFAIK that requires admin.
I'm currently using a TcpClient and SslStream and not the WebRequest/WebResponse classes because I might move from using HTTP to using my own protocol some day, but if the task is easier using the *request classes I'll consider using them.
First you want to use the overloaded SslStream constructor:
SslStream(Stream innerStream, bool leaveInnerStreamOpen, RemoteCertificateValidationCallback userCertificateValidationCallback);
Then the RemoteCertificateValidationCallback method looks something like this:
public bool IsValid(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
... you logic here ...
}
You just need to simply walk the chain and look at the certificates until you find one you are willing to accept by verifying the public key:
foreach(X509ChainElement e in chain.ChainElements)
if( e.Certificate.Subject == "CN=XXX.xx" && e.Certificate.GetPublicKeyString() == "expected public key" )
return true;

Categories