I've written a .NET application in C# that contacts a few web services. Everything works fine when I reference the web services on a non-secure URL; but when I reference the same web services on a secure HTTPS URL, I get an exception with the message "Could not establish trust relationship for the SSL/TLS secure channel".
I was pointed to at this web page http://www.codemeit.com/wcf/wcf-could-not-establish-trust-relationship-for-the-ssltls-secure-channel-with-authority.html. And added the code to my application. This resolves the issue and I can now use the secure web services as expected.
I notice the code snippet above says "not intended to be used in a production environment" and it currently trusts any certificate. In order to make it more secure for a production environment, should I be checking it for our own certificate? How should I check this, the 'cert' parameter in the method RemoteCertificateValidate() has a lot of information in it so I'm not sure how or what I should be checking.
TIA
It is highly likely there is a problem with the certificate used by the server to identity itself when your client is establishing the secure SSL connection. In my experience this is primarily caused by the certificate that the server returns is either not signed by a trusted root authority or one or more certificates in the chain is not valid (e.g. they have expired or are for a domain other than the one you are connecting to).
To help identify the problem you could try browsing to the service endpoint URL and seeing whether the browser reports any issues. Modern browsers will warn you if a server certificate is not valid and will sometimes tell you why. If you are using Chrome and there is a problem with the certificate you will see a red cross through the padlock icon at the left end of the address bar (or omnibox). If you click on the padlock Chrome will tell you why it doesn't like the certificate.
As for the code snippet you found, this is an example of how to implement validation of server certificates as a client in .Net. However, the example itself will never reject a connection to the server even if there is a problem with the servers certificate, hence the comment about not using the code in production. Outside of a test scenario you never want to be communicating with a server who's identity you cannot trust due to a bad certificate. To provide a "production" implementation you must check the value of the "error" parameter to see if there are any problems with the certificate and only return true if there are none.
using System;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
// Attach my certificate validation delegate.
ServicePointManager.ServerCertificateValidationCallback += ValidateRemoteCertificate;
System.Net.HttpWebRequest request = WebRequest.CreateHttp("https://www.google.com");
HttpWebResponse response = request.GetResponse() as HttpWebResponse;
}
private static bool ValidateRemoteCertificate(object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors error)
{
if (error == SslPolicyErrors.None)
{
return true;
}
return false;
}
}
}
In this example I am only returning true from my ServerCertificateValidationCallback delegate if the value of error is equal to SslPolicyErrors.None, otherwise I return false which generates an exception. To make this example work I used Fiddler to proxy the request and return an untrusted certificate to my client code. If you navigate to https://www.google.com under normal conditions you should not see any certificate errors as Google plays nice!!
PKI with certificates can be a gnarly domain to work in. In terms of documentation Wikipedia has a good article on PKI that covers how trusted authorities are used to sign certificates used in SSL so that clients can trust the identity of a server they are communicating with.
Related
I'm trying to connect to Java web service with C# client. I don't have physical access to that service, but I was told by it's developers that I need to provide client certificate AND intermediate CA in my requests.
I tried using Service Reference and WebClient but I only managed to send just client certificate.
protected override WebRequest GetWebRequest(Uri address)
{
HttpWebRequest request = (HttpWebRequest)base.GetWebRequest(address);
request.ClientCertificates.Add(m_TransportCertificate);
//m_TransportCertificate holds client certificate with key and rest of the cert chain (intermediate and root)
return request;
}
How to send two client certificates in C#? As far as i know it isn't normal behaviour - client should only send one certificate.
I can't make my app to do it - it always sends one certificate.
Wireshark screenshot
However it works in SoapUI (the second one is intermediate CA)
Wireshark screenshot
I found a solution. Intermediate CA was automatically installed in Intermediate Certification Authorities but it needs to be in Trusted Root Certification Authorities.
Just moved it there and it worked.
Context
The client code have to connect to a remote web api. SSL is mandatory because of sensitive information exchanged on the wire. We have our self issued certificate a
In the project there is no currently what is properly configured in the remote IIS, and everything tested OK, except the certificate is not trusted.
I know I can install the certificate on the local machine and trust in it. However for some reasons (for example, there are Xamarin Android clients too) it is not a suitable solutions.
Current workaround is to simply ignore the certificate error in code, which is working both on desktop both on Android Xamarin:
ServicePointManager.ServerCertificateValidationCallback =
(s, certificate, chain, sslPolicyErrors) =>
{
// Here I would like to check against that the certificate
// is the specific one I issued, and only that case return with true
// if (what is the most suitable to write here?)
return true;
};
Question
Note: this is development/testing phase. In production there will be a certificate installed created by a trusted certificate authority.
Now I have bad feeling about accepting anything, so I would like to narrow it.
How to check against that the certificate is the specific one I issued, and only that case return with true.
Is that a working idea to run the code, place a breakpoint, and get the certificate.Thumbprint, then write an if to check against it?
(edit)
...or better... get the PublicKeyString and checking against it?
Checking against thumbprint is IMHO an OK option. With thumbprint you can narrow the trust to exactly one certificate.
Checking against public key or Subject Key Identifier is also OK but you will expand the trust to the same key pair. There can be several certificates issued on the same public key (with or without the same Distinguished Name).
As #bartonjs stated another option is to check raw data. This approach has the same power as checking against thumbprint. The benefit of this would IMO be that when you look at the code a year from now you will still know what certificate you trust :)
Anyway when checking against the exact certificate keep in mind that the certificate will expire someday (i.e. let's encrypt issues a 3 month certificate if I remember correctly) and your application will have to be redeployed with a new certificate check. You would have to keep track of expiration date of the certificate.
While working on this question, I identified that the problem is slightly different than initially stated, so I am changing title and description
I'm trying to authenticate myself against WebService using my client certificate. I am using WebRequest for that purpose. I need to use self-signed certificate, without registering it in Windows X509 trusted root store.
The way I know if client certificate is presented or not is by examining request object on the server
I tried to use code from this question as a guidance. It doesn't work on .NET. Here is my code:
var certificate = new X509Certificate2(Properties.Resources.MyCert);
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(host);
req.ClientCertificates.Add(certificate);
WebResponse resp = req.GetResponse();
var stream = resp.GetResponseStream();
What I observe is that even though req.ClientCertificates does contain certificate with a valid private key, that certificate is never presented to server. I get no indication from WebClient that certificate is not used during handshake.
If I put certificate into "trusted root", the code will work (even when certificate is not in "personal").
My questions are:
Since certificate is usable when it's placed in "trusted root", I assume it is likely due to policy or something of that kind. Is it possible to coerce .NET to ignore policy settings and use supplied client certificate during TLS negotiation?
If abovementioned coercion is not possible, is there a call which will tell me ahead of time, that certificate I am about to use is not usable, and will be ignored? Alternatively, if such call is not available, could I make WebClient fail indicating a certificate error, instead of silently skipping over?
NOTE: I am aware that configuring certificates as described by Microsoft will work. This is NOT what I am looking for. I don't want to register potentially insecure certificate in trusted root, because this is potentially security hazard. I want to use cert on client without registering in store, or at least to get an exception indicating that certificate cannot be used. I realize that there can be multiple reasons why certificate cannot be used for a session, but there must be an exception, or at least some sort of indication on the client side that it cannot use specified cert. Instead, client simply doesn't present one.
When you instantiate your X509Certificate2, is the PrivateKey property set? If it is null, you are missing the private key, meaning the SSL/TLS client will be unable to authenticate you.
Make sure you are loading the certificate from a PFX file (or similar) instead of a CER. These contain the private key, too. They are usually password protected for that purpose. See How to retrieve certificates from a pfx file with c#? for more info.
I'm trying to develop an ASP.net site that reads the clientCertificate to ensure a smart card was used to access the website (trying to do away with username/password login).
The process I have in my mind is:
User registers an account and C# records user's clientCertificate (public).
The user can then log in the next time with that same clientCertificate, and they are now an authenticated user if hash valid.
I will use the code below to ensure authenticity of certificate. The browser should deal with private keys and ensure the certificate was NOT faked.
Based on Subject+certificate combination, C# assigns them their role-access.
The following code can be used for authenticity of certificate right?
X509Certificate x509Cert = new X509Certificate(Request.ClientCertificate.Certificate);
SHA1 sha = new SHA1CryptoServiceProvider();
byte[] hashvalue = sha.ComputeHash(Request.ClientCertificate.Certificate);
byte[] x509Hash = x509Cert.GetCertHash();
// compare x509Hash WITH hashvalue to ensure they are a match.
// If not, possibly faked certificate, not a real smartcard???
Is this how SmartCard authentication process should work???
If you just need to authenticate users with client certificates you should do this in IIS. You do not need to add any code at all to your application:
Specify Whether to Use Client Certificates (IIS 7)
Unless you need to link client certificates with database accounts or perform an additional validation step. But still for client certificate authentication I would stick with IIS settings.
Update:
In case you need to manipulate the client certificate you can do:
X509Certificate2 x509Cert2 = new X509Certificate2(Page.Request.ClientCertificate.Certificate);
And then access its properties such as:
x509Cert2.Subject
However, leave the validation piece up to IIS. If the client presents a bad certificate your asp.net code will not even execute since IIS will reject it
See this thread, sir. You do not need to verify authenticity explicitly in your code. IIS will do it for you.
Does IIS do the SSL certificate check or do I have to verify it?
IIS even tries to check revocation lists (however, this is often disabled if the CRL is large). An OCSP responder should be used to validate in cases where the CRL is very large or latency in checking it is high http://www.axway.com/products-solutions/email-identity-security/identity-security/va-suite.
Client-certificate authentication is done during the SSL/TLS handshake.
It is usually done using a Public Key Infrastructure, whereby the server has a (fixed) list of trusted CA certificates which it uses to verify the client certificate (in the same way as clients to it for the server). Once the certificate is presented to your application after this stage, you will know that:
the client has the private key for that certificate (guaranteed by the Certificate Verify message in the TLS handhsake (the SSL/TLS stack will verify this for you, no need to implement anything);
the client has the identity described in the certificate, because you will have verified it against your trusted CA.
The verification against a trusted CA requires the user to be registered with that CA in advance. You can't just authenticate any certificate if it hasn't been issued by a CA you trust. (Mapping the certificate's subject to a local user ID is another matter: you could do this upon first connection if needed: have your own database or directory service to map the Subject DN to another kind of user ID in your application, for example.)
User registers an account and C# records user's clientCertificate
(public). The user can then log in the next time with that same
clientCertificate, and they are now an authenticated user if hash
valid.
It sounds like you want to allow any certificate to be presented and use it for the initial registration, without necessarily resorting to a commonly trusted CA.
This is possible in principle, and I've done this to explore alternatives to PKI in Java.
To do this, you need to let any certificate through as far as the SSL/TLS handshake is concerned, and verify the certificate itself later. (You do need to use some form of verification.) You are still guaranteed with this that the client has the private key for the public key certificate it has presented.
Doing this requires two steps:
You need to be able to advertise the fact that you're going to accept any certificate, by sending an empty list of certification authorities in the Certificate Request TLS message (explicitly allowed by TLS 1.1).
Configure the SSL/TLS stack to trust any certificate (once again, when you do this, do not forget to implement your own verification system within your application, otherwise anything will really get through).
In .Net, while it should be possible to address the second point using a remote certificate validation callback, I have never found a way to alter the first point (this was also asked in this question).
In Java, the JSSE's X509TrustManager allows you to address both points.
I posted days ago about access control to web service (Access control to web service). In short, I have an ASP.NET web service deployed on //service/webservice and I want my ASP.NET web application (app1) on the //web1 to access the web service with certificate authentication. I keep getting System.Net.WebException: The request failed with HTTP status 403: Forbidden exception. The following is my setup:
On certificate export;
I exported a server certificate issued to //service from LocalMachine store and saved it as service.cer.
I also exported a client certificate issued to //web1 from LocalMachine store and saved it as web1.cer
Setup on //service/webservice:
On Directory Security, unchecked Anonymous Access and all Authentication Access (Integrated Windows Access, Digest Authentication and Basic Authentication).
On Secure communications, checked Required secure channel(SSL), Require 128-bit encyption, Require client certificate, and Enable client certificate mapping. I then mapped web1.cer to an AD account MyDomain/user which has access right to //service/webservice
For //service/webservice/WebService.asmx, set <authentication mode="Windows" /> on web.config
Setup on //web1/app1
Set <authentication mode="Windows" /> and <identity impersonate="true" /> on web.config
In VS2008, I added the web reference to //service/webservice/WebService.asmx and named it WService
In //web1/app1/default.aspx.cs, I had this:
using System.Security.Cryptography.X509Certificates;
using System.Net;
WService.WebService ws = new WService.WebService();
ServicePointManager.ServerCertificateValidationCallback = delegate(Object sender1, X509Certificate cert, X509Chain chain, System.Net.Security.SslPolicyErrors errors) { return true; };
//I was a bit confused here on which certificate I should use so I have tried both service.cer and web1.cer but still got the same error
X509Certificate x509 = X509Certificate.CreateFromCertFile(#"C:\Certificates\service.cer");
ws.ClientCertificates.Add(x509);
ws.DoSomething();
I ran WinHttpCertCfg.exe to grant access to both certificates in LocalMachine for ASPNET account
I went to https://service/webservice/WebService.asmx and was prompted to provide a client certificate and after that I was through. But if I went to https://web1/app1/default.aspx (which would call the web service) and I would get the HTTP status 403 exception.
What did I miss? I would assume the problem is because //web1/app1/default.aspx.cs failed to transmit the certificate across. If that's the problem, how do I do that? I built both the asmx and aspx on VS 2008 and ASP.NET 3.5.
Make sure your client certificate was requested as a 'Computer' template certificate for 'Client Authentication' otherwise it will not work.
Sounds like the SSL certificate is failing to authenticate for the web service client. A good check is if you go to the service from the client’s machine and get an alert in the browser about an SSL certificate your service will not authenticate with the certificate (certificate is not trusted). It’s not that the certificate doesn’t work, it’s just not trusted.
If the service is across machines you might have to setup a certificate authority (this might help http://www.petri.co.il/install_windows_server_2003_ca.htm) and add it as a trusted publisher on the client machine. This might also help http://support.microsoft.com/kb/901183.
Another option is to simple not validate the SSL, see:
http://geekswithblogs.net/jwhitehorn/archive/2006/09/20/91657.aspx
When I had this problem it turns out the client certificate/key pair I was using was signed by an intermediate CA which was in the current user store instead of the local machine store. It all looked good if you examined the cert while logged in but the IIS worker process could not see the intermediate CA. Thus, the web service call was not supplying the certificate with the request. You can verify this by checking the server web log for a 403 7 5 response.
Make sure the users that are impersonating have access to the certificate store being used.