Verify remote SSL certificate during HTTPS request - c#

When making HTTPS request to remote web server, I use WebRequest, which establishes secure connection with remote web server. During development, I use self-signed cert on server, and WebRequest fails to establish secure connection, since cert is not valid, which is expected behavior.
I have found this code that "remotes" cert check, activated by calling SetCertificatePolicy() method in following code.
public static void SetCertificatePolicy()
{
ServicePointManager.ServerCertificateValidationCallback
+= RemoteCertificateValidate;
}
/// <summary>
/// Remotes the certificate validate.
/// </summary>
private static bool RemoteCertificateValidate(
object sender, X509Certificate cert,
X509Chain chain, SslPolicyErrors error)
{
// trust any certificate!!!
System.Console.WriteLine("Warning, trust any certificate");
return true;
}
I am wondering, if it is possible to do special checks on remote SSL cert (using above code, for instance), so that I can verify that remote web server uses valid SSL cert, and not just any valid cert, but exactly the one I want? For instance, I want to make sure that I'm talking to www.someplace.com website, cert issued to ACME Inc, with fingerprint 00:11:22:.....
What is a "best practice" approach for this scenario?
Thanks!

If you really want to nail it down to one particular certificate, you can compare the certificate data (in DER format) to the byte[] in certificate.GetRawCertData().
You can also use GetCertHashString() and Subject on the certificate parameter in RemoteCertificateValidate to get the information you're after. The hostname ought to be in the subject alternative name of the certificate or, if there isn't a subject alternative name, in the CN of the subject (distinguished) name. Considering the way .NET formats the subject string, this ought to be the first CN= you find there.
You'll also get more data if certificate is an instance of X509Certificate2. You'll then be able to get SubjectName as an X500PrincipalName and also the Extensions (to check the subject alternative name extension). It might be useful to use tools such as BouncyCastle for parsing the subject name.
You're also likely to get more information about the hostname you're trying to contact in the sender, depending on its runtime type.

Related

Server certificate/domain custom validation when connecting via TLS/SSL on production

The error below occurs when a web service request is sent to a remote web service:
Could not establish trust relationship for the SSL/TLS secure channel
The remote certificate is invalid according to the validation procedure.
My question:
1 Is the code below combined with TLS/SSL still safe to use on production given the server is in our control. Think of attack like man-in-the-middle, and others.
2 What about if the server is not in our control, can the validation prove secure?
System.Net.ServicePointManager.ServerCertificateValidationCallback += delegate (
object sender,
X509Certificate cert,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors == SslPolicyErrors.None)
{
return true; //Is valid
}
//validate Server's certificate or Server's domain name or IP
if (IsValidServerCertificate(cert) ||
IsValidServerIP(cert))
{
return true;
}
return false;
};
public bool IsValidServerCertificate(X509Certificate cert){
return cert.GetCertHashString() == "server's public certificate thumbprint")
}
public bool IsValidServerIP(){
//compare server's ip address from the request with the address we are given"
}
The proper way is to login using server's public certificate from client side, but what validation this method provides that my custom validation need to add?
Update:
For whose who voted down the ticket: If you know the solution, why cannot you provide it. There is no solution in code on SO.
I believe this ticket can benefit someone else in the future, if a simple solution with code sample can be found.
... safe to use on production given the server is in our control. Think of attack like man-in-the-middle, and others.
Man in the middle attacks are not attacks at the client nor at the server but in the middle. Therefore it does not matter at all if the server is in your control. You could only be sure that no man in the middle attacks occur if everything between client and server would be in your control too. And to achieve this you have to make sure that the client is even using the path you control: things like DNS spoofing might return in a different destination IP address and thus in a different path than you expect.
In other words: the code is unsafe, no matter if you control the server or not. It should not be used in production. Instead of trying to work around broken setups you should fix the cause of the problem. These are usually broken certificates, incomplete chain or broken setup of trust anchors.

Root Certificate Pinning in C#/.NET

I want to implement certificate/public key pinning in my C# application. I already saw a lot of solutions that pin the certificate of the server directly as e.g. in this question. However, to be more flexible I want to pin the root certificate only. The certificate the server gets in the setup is signed by an intermediate CA which itself is signed by the root.
What I implemented so far is a server that loads its own certificate, the private key, intermediate certificate, and the root certificate from an PKCS#12 (.pfx) file. I created the file using the following command:
openssl pkcs12 -export -inkey privkey.pem -in server_cert.pem -certfile chain.pem -out outfile.pfx
The chain.pem file contains the root and intermediate certificate.
The server loads this certificate and wants to authenticate itself against the client:
// certPath is the path to the .pfx file created before
var cert = new X509Certificate2(certPath, certPass)
var clientSocket = Socket.Accept();
var sslStream = new SslStream(
new NetworkStream(clientSocket),
false
);
try {
sslStream.AuthenticateAsServer(cert, false, SslProtocols.Tls12, false);
} catch(Exception) {
// Error during authentication
}
Now, the client wants to authenticate the server:
public void Connect() {
var con = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
con.Connect(new IPEndPoint(this.address, this.port));
var sslStream = new SslStream(
new NetworkStream(con),
false,
new RemoteCertificateValidationCallback(ValidateServerCertificate),
null
);
sslStream.AuthenticateAsClient("serverCN");
}
public static bool ValidateServerCertificate(
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors
)
{
// ??
}
The problem now is that the server only sends its own certificate to the client. Also the chain parameter does not contain further information.
This is somehow plausible as the X509Certificate2 cert (in the server code) only contains the server certificate and no information about the intermediate or root certificate. However, the client is not able to validate the whole chain as (at least) the intermediate certificate is missing.
So far, I did not find any possiblity to make .NET send the whole certificate chain, but I do not want to pin the server certificate iself or the intermediate one as this destroys the flexibility of root certificate pinning.
Therefore, does anyone know a possibility to make SslStream sending the whole chain for authentication or implement the functionality using another approach? Or do I have to pack the certificates differently?
Thanks!
Edit:
I made some other tests to detect the problem. As suggested in the comments, I created a X509Store that contains all the certificates. After that, I built a X509Chain using my server's certificate and the store. On the server itself, the new chain contains all the certificates correctly, but not in the ValidateServerCertificate function..
SslStream will never send the whole chain (except for self-issued certificates). The convention is to send everything except for the root, because the other side either already has and trusts the root or doesn't have (thus/or doesn't trust the root), and either way it was a waste of bandwidth.
But SslStream can only send the intermediates when it understands the intermediates.
var cert = new X509Certificate2(certPath, certPass);
This only extracts the end-entity certificate (the one with the private key), it discards any other certificates in the PFX. If you want to load all of the certificates you need to use X509Certificate2Collection.Import. But... that doesn't really help you either. SslStream only accepts the end-entity certificate, it expects the system to be able to build a functioning chain for it.
In order to build a functioning chain, your intermediate and root certificates need to be in any of:
Provided as manual input via X509Chain.ChainPolicy.ExtraStore
Since the chain in question is built by SslStream you can't really do this here.
CurrentUser\My X509Store
*LocalMachine\My X509Store
CurrentUser\CA X509Store
**LocalMachine\CA X509Store
CurrentUser\Root X509Store
**LocalMachine\Root X509Store
*LocalMachine\ThirdPartyRoot X509Store
An http (not-s) location identified in an Authority Access Identifier extension in the certificate.
The stores marked with * don't exist on .NET Core on Linux. The stores marked with ** do exist on Linux, but cannot be modified by a .NET application.
That's also not quite sufficient, because (at least for SslStream on Linux and probably macOS on .NET Core) it still only sends the intermediates if it built a chain it trusted. So the server needs to actually trust the root certificate for it to send the intermediates. (Or the client needs to trust the root for the client cert)
On the other side, the same rules apply. The difference is that in the callback you can choose to rebuild the chain to add the extra certificates.
private static bool IsExpectedRootPin(X509Chain chain)
{
X509Certificate2 lastCert = chain.ChainElements[chain.ChainElements.Count - 1].Certificate;
return lastCert.RawBytes.SequenceEquals(s_pinnedRootBytes);
}
private static bool ValidateServerCertificate(
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors
)
{
if ((sslPolicyErrors & ~SslPolicyErrors.RemoteCertificateChainErrors) != 0)
{
// No cert, or name mismatch (or any future errors)
return false;
}
if (IsExpectedRootPin(chain))
{
return true;
}
chain.ChainPolicy.ExtraStore.Add(s_intermediateCert);
chain.ChainPolicy.ExtraStore.Add(s_pinnedRoot);
chain.ChainPolicy.VerificationFlags |= X509VerificationFlags.AllowUnknownCertificateAuthority;
if (chain.Build(chain.ChainElements[0].Certificate))
{
return IsExpectedRootPin(chain);
}
return false;
}
Of course, the problem with this approach is that you need to also understand and provide the intermediate on the remote side. The real solution to this is that the intermediates should be available on an HTTP distribution endpoint and the issued certificates should carry the Authority Information Access extension to be able to locate them dynamically.

How to validate server SSL certificate for LDAP+SSL connection

Our application works with Active Directory users and groups. We are using LDAP on port 389 for Active Directory operations. Now, one of our clients want us add an option for using LDAP + SSL for Active Directory communication.
They told us that they have a local CA installed on their domain and using self signed certificate for LDAPS. They also told us that they will provide the certificate, no mutual-trust needed and we should use Windows certificate store.
I have developed a test application for LDAP+SSL operations and saw that server sends its certificate when a client initiates an LDAP+SSL connection. I can establish the connection only by returning true from the server certificate verification method.
The questions are;
- Which certificate (root, the ceritificate used for LDAP+SSL...) should the customer give us?
What should be the format of the certificate for working on .Net environment?
How should I verify the server's certificate when connecting the server?
What they mean by "we should use Windows certificate store"? Do they want us add the server's certificate automatically to trusted certificate store of the local machine?
Sample code I used for LDAP+SSL connection,
LdapConnection _connection = new LdapConnection(new LdapDirectoryIdentifier(m_DomainName, m_PortNo));
_connection.Timeout = TimeSpan.FromMinutes(10);
_connection.AuthType = AuthType.Basic;
_connection.Credential = new NetworkCredential(m_UserName, m_Password);
_connection.SessionOptions.ProtocolVersion = 3;
_connection.SessionOptions.SecureSocketLayer = true;
_connection.SessionOptions.VerifyServerCertificate = (ldapCon, serverCertificate) =>
{
//TODO: Verify server certificate
return true;
};
_connection.SessionOptions.QueryClientCertificate = (con, trustedCAs) => null;
_connection.Bind();
Which certificate (root, the ceritificate used for LDAP+SSL...) should the customer give us?
The root certificate that signed the LDAP server cert. They can also give you the whole chain in advance, but that will be sent during TLS handshake anyway. You only need to have the root cert in advance.
What should be the format of the certificate for working on .Net environment?
Anything that you can import into certmgr.msc. Pfx is the usual choice on Windows.
How should I verify the server's certificate when connecting the server?
You should not write validation yourself. Certificate validation is tricky business, and it's already done for you. Use the built-in stuff (also see below).
What they mean by "we should use Windows certificate store"? Do they want us add the server's certificate automatically to trusted certificate store of the local machine?
Yes. They send you the root cert they used for signing the ldap server cert, which you can then import as a trusted root. Once this is done, you don't need to do any manual validation, it will just work™ :) with valid certificates and will not work with invalid ones.
Note that once you add their root cert as trusted, they can forge any server certificate for the client their root is installed on, and anything they sign will be considered valid on that client.
Bonus: adding semi-custom validation and debugging certificate errors
One problem that you may face is that error messages are not very helpful. If the certificate cannot be validated, you will get a very generic error message that has no hint about the actual problem. You may want to hook into the validation process for other reasons too.
For this purpose, you can define your own validation:
private bool VerifyServerCertificate(LdapConnection ldapConnection, X509Certificate certificate)
{
X509Certificate2 certificate2 = new X509Certificate2( certificate );
return certificate2.Verify();
}
And then add it to the ldap connection:
_connection.SessionOptions.VerifyServerCertificate =
new VerifyServerCertificateCallback( VerifyServerCertificate );
This way you can catch exceptions on Verify() etc. But again, if the certificate is valid (can be verified by the client), this is not strictly needed, it's done automatically anyway. You only need this if you want something not implemented, like for example you could just return true in VerifyServerCertificate to accept any cert including the invalid ones (this would be a Bad Idea and makes a secure connection useless, but may be good for debugging, etc).
Another thing you could implement in this method is certificate pinning for additional security, but that's beyond the scope of this answer.
BTW: Since introduction of the StartTLS extended operation for LDAP v3 in May 2000 (RFC 2830) LDAPS (made for LDAP v2) is deprecated.

How to Connect to a Web-service that has a faulty security certificate [duplicate]

We are setting up a new SharePoint for which we don't have a valid SSL certificate yet. I would like to call the Lists web service on it to retrieve some meta data about the setup. However, when I try to do this, I get the exception:
The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel.
The nested exception contains the error message:
The remote certificate is invalid according to the validation procedure.
This is correct since we are using a temporary certificate.
My question is: how can I tell the .Net web service client (SoapHttpClientProtocol) to ignore these errors?
Alternatively you can register a call back delegate which ignores the certification error:
...
ServicePointManager.ServerCertificateValidationCallback = MyCertHandler;
...
static bool MyCertHandler(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors error)
{
// Ignore errors
return true;
}
Like Jason S's answer:
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
I put this in my Main and look to my app.config and test if (ConfigurationManager.AppSettings["IgnoreSSLCertificates"] == "True") before calling that line of code.
I solved it this way:
Call the following just before calling your ssl webservice that cause that error:
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
/// <summary>
/// solution for exception
/// System.Net.WebException:
/// The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel. ---> System.Security.Authentication.AuthenticationException: The remote certificate is invalid according to the validation procedure.
/// </summary>
public static void BypassCertificateError()
{
ServicePointManager.ServerCertificateValidationCallback +=
delegate(
Object sender1,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
return true;
};
}
The approach I used when faced with this problem was to add the signer of the temporary certificate to the trusted authorities list on the computer in question.
I normally do testing with certificates created with CACERT, and adding them to my trusted authorities list worked swimmingly.
Doing it this way means you don't have to add any custom code to your application and it properly simulates what will happen when your application is deployed. As such, I think this is a superior solution to turning off the check programmatically.
I was having same error using DownloadString; and was able to make it works as below with suggestions on this page
System.Net.WebClient client = new System.Net.WebClient();
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
string sHttpResonse = client.DownloadString(sUrl);
ServicePointManager.ServerCertificateValidationCallback +=
(mender, certificate, chain, sslPolicyErrors) => true;
will bypass invaild ssl . Write it to your web service constructor.
For newbies,
you can extend your partial service class in a separate cs file and add the code the code provided by "imanabidi" to get it integrated
To further expand on Simon Johnsons post - Ideally you want a solution that will simulate the conditions you will see in production and modifying your code won't do that and could be dangerous if you forget to take the code out before you deploy it.
You will need a self-signed certificate of some sort. If you're using IIS Express you will have one of these already, you'll just have to find it. Open Firefox or whatever browser you like and go to your dev website. You should be able to view the certificate information from the URL bar and depending on your browser you should be able to export the certificate to a file.
Next, open MMC.exe, and add the Certificate snap-in. Import your certificate file into the Trusted Root Certificate Authorities store and that's all you should need. It's important to make sure it goes into that store and not some other store like 'Personal'. If you're unfamiliar with MMC or certificates, there are numerous websites with information how to do this.
Now, your computer as a whole will implicitly trust any certificates that it has generated itself and you won't need to add code to handle this specially. When you move to production it will continue to work provided you have a proper valid certificate installed there. Don't do this on a production server - that would be bad and it won't work for any other clients other than those on the server itself.

Validation of certificate for client

I am creating an application which uses EWS (Exchange Web Service) to move mails. I am able to run my application by bypassing certificate validation. I use this code to bypassing certificate validation:
ServicePointManager.ServerCertificateValidationCallback =
delegate(object s, X509Certificate certificate,
X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
return true;
};
To verify certificate I followed this post which was very helpful in finding the certificate and checking the certificate.
While using that post ValidateCert method was throwing exception
chain.ChainPolicy.RevocationMode = X509RevocationMode.Online | X509RevocationMode.Offline;
The exception is:
System.ArgumentException: Illegal enum value: value.
at
System.Security.Cryptography.X509Certificates.X509ChainPolicy.
set_RevocationMode(X509RevocationMode value)
I am using Visual Studio 2013.
I am open to any other approach to do the same task.
You must choose either Online or Offline:
Online: always tries to contact the CDP or OCSP url specified in the certificate to get the CRL list to check the revocation
Offline: tries to use an existing cached copy of the CRL
The first option can be slow, if the CDP or OCSP server is not responding, if there are connectivity problems, and so on. It can even fail if this problem is permanent.
I'd recommend using the second option, for most cases. The only reason not to use the cached copy of the CRL is that there has been recent certificate revocations and that they have just been published to the CRL. Besides, the CRL have expiration dates, which will force to get a new copy if they have expired.
You can also clear the CRL cache before starting the process with the offline option. Google it: there are different options. You'll need to use certutil.exe.

Categories

Resources