I'm trying to update my application's TcpClient to use TLS with SslStream instead of the normal Stream, the code i'm using for this seems to work outside of Unity, but fails when integrated in my Unity 2019.1.8 (tested on 2018 and 2017 as well) project.
To establish a connection and open a new SslStream I use the following code:
public static void InitClient(string hostName, int port, string certificateName)
{
client = new TcpClient(hostName, port);
if (client.Client.Connected)
{
Debug.LogFormat("Client connected succesfully");
}
else
{
Debug.LogErrorFormat("Client couldn't connect");
return;
}
stream = new SslStream(client.GetStream(), false, new RemoteCertificateValidationCallback(ValidateServerCertificate), null);
try
{
stream.AuthenticateAsClient(certificateName);
}
catch (AuthenticationException e)
{
Debug.LogErrorFormat("Error authenticating: {0}", e);
if (e.InnerException != null)
{
Debug.LogErrorFormat("Inner exception: {0}", e);
}
Debug.LogErrorFormat("Authentication failed - closing connection");
stream.Close();
client.Close();
}
}
And for validating the certificate
public static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors == SslPolicyErrors.None)
return true;
Debug.LogErrorFormat("Certificate error: {0}", sslPolicyErrors);
return false;
}
In Unity 2019.1.8 the client connects and will attempt to validate the remote certificate, which fails with the error TlsException: Handshake failed - error code: UNITYTLS_INTERNAL_ERROR, verify result: UNITYTLS_X509VERIFY_FLAG_NOT_TRUSTED.
Making ValidateServerCertificate always return true lets my client connect without issue.
I tried replicating the issue in a standalone C# Console Application targeting .net framework 4.7.1 using the exact same code. Launching the client in this application will return true from ValidateServerCertificate from the sslPolicyErrors == SslPolicyErrors.None check.
I know that the certificate is a valid cert, issued by a trusted CA (as verified by the fact that the cert is accepted from a console app, and it having a padlock in browsers).
Why does the validation fail in Unity, but nowhere else?
Why Unity can't validate option 1:
Although the certificate is valid and correct (e.g. it works when using it in a web browser), it did not include the intermediate certificates chain to the root CA. This has as a result that no chain of trust can be formed (Unity doesn't cache/retrieve intermediates), resulting in the UNITYTLS_X509VERIFY_FLAG_NOT_TRUSTED flag being set.
To fix this I needed to append the certificate chain to my leaf certificate so that Unity can verify the entire chain up to the root CA. To find the certificate chain for your certificate you can use a "TLS certificate chain composer".
Depending on what software you use you may either need to include the chain in your certificate, or keep it in a seperate file. From whatsmychaincert (i'm in no way affiliated to this site, merely used it):
Note: some software requires you to put your site's certificate (e.g. example.com.crt) and your chain certificates (e.g. example.com.chain.crt) in separate files, while other software requires you to put your chain certificates after your site's certificate in the same file.
Why Unity can't validate option 2:
When your server's SSL certificate has expired Unity will throw the exact same UNITYTLS_X509VERIFY_FLAG_NOT_TRUSTED error without additional information that the certificate has been expired, so make sure the "Valid to" date is in the future (this would also cause the certificate to be denied when used in a web browser).
Why a browser/Console app can validate:
Software can have different implementation on how it deals with incomplete chains. It can either throw an error, stating that the chain is broken and can thus not be trusted (as is the case with Unity), or cache and save the intermediates for later use/retrieve it from previous sessions (as browsers and Microsoft's .net(core) do).
As explained in this answer (emphasis mine)
In general, SSL/TLS clients will try to validate the server certificate chain as received from the server. If that chain does not please the client, then the client's behaviour depends on the implementation: some clients simply give up; others (especially Windows/Internet Explorer) will try to build another chain using locally known intermediate CA and also downloading certificates from URL found in other certificates (the "authority information access" extension).
Not trusting the certificate when no chain is provided is a deliberate decision made by Unity due to cross-platform compatability, as stated in an [answer to issue 1115214][3]:
We may be able to solve this issue by doing a verification via the system specific TLS api instead of using OpenSSL/MbedTLS to validate against root certificates as we do today, however this solution would then not work cross-platform. So we don't want to implement it today, as it would hide the misconfigured server from the user on some but not all platforms.
I figured out this was a solution to my particular situation while asking this question, so decided to self-answer it for future references. However UNITYTLS_X509VERIFY_FLAG_NOT_TRUSTED can have all kind of causes, this just being one of them.
For anyone stumbling across this who's using UnityWebRequest and Let's Encrypt:
There is a known issue on Unity's Issue Tracker where you will find it's been fixed in only certain versions:
Fixed in 2020.1.0a11
Fixed in 2019.3.3f1
Fixed in 2018.4.18f1
Changing Unity versions for an existing project is not a trivial task so we opted to buy a legit SSL cert which, although not cheap, was the simplest solution.
My solution was:
> Public class ForceAcceptAll : CertificateHandler {
> protected override bool ValidateCertificate(byte[] certificateData)
> {
> return true;
> } }
//-------
var cert = new ForceAcceptAll();
// www is a UnityWebRequest
www.certificateHandler = cert;
Nota: if used incorrectly (and found by the Google, your build might get rejected).
So use it with caution.
more information here.
Related
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.
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.
There's a problem. I have a site, that expanded locally on IIS, that needs to be connected to the remote WCF service. I've added all required certificates to the TrustedRoot store of LocalComputer. I've granted all permissions to my domain account for using certificates. But there's still a problem:
"The remote certificate is invalid according to the validation
procedure"
when I'm trying to run method of WCF-service (but it's connected to this WCF service fine).
I have some thinks about that:
1) Maybe I need to select SSL on IIS setting of this site?
2) Maybe I need to put certificates not only to LocalMachine, but to the CurrentUser too?
Advice me some tips for how to establish SSL connection and pass this exception. Thank you :)
This is because, the certificate you use on your local IIS, is a virtual one. Probably, you will not get it in real time. There are several hacks available to make it work locally.
NOTE: Never ever do this on production...
Add following code segment(an event handler), before calling any method of service.
ServicePointManager.ServerCertificateValidationCallback +=
EasyCertCheck;
bool EasyCertCheck(object sender, X509Certificate cert,
X509Chain chain, System.Net.Security.SslPolicyErrors error)
{
return true;
}
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.
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.