Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Resolved [SOLVED] Self Signed Certificate: WebGL TlsException

Discussion in 'Scripting' started by Mealycons, Oct 6, 2023.

  1. Mealycons

    Mealycons

    Joined:
    Dec 12, 2021
    Posts:
    5
    Hi,
    i use clientwebsocket in plain c# with ssl. When i integrate my sultion in unity, i get the error: UNITYTLS_X509VERIFY_FLAG_NOT_TRUSTED. How can i fix it ?

    In my opinion, i already added the flags for the socket..
    -Without unity, it works-

    If has another solution that will available without unity core, let me know it :)

    Code (CSharp):
    1. public class SSLClientSocket
    2. {
    3.     private ClientWebSocket socket;
    4.     private string cert;
    5.     private X509Certificate certificateFile;
    6.     private string domain;
    7.     private string port;
    8.     private string protocoll = "wss://";
    9.     public SSLClientSocket(string _domain, string _port)
    10.     {
    11.         socket = new ClientWebSocket();
    12.         cert = "./Assets/cert/cert.pem";
    13.         certificateFile = new X509Certificate(cert);
    14.         domain = _domain;
    15.         port = _port;
    16.        
    17.         //SSL Options
    18.         socket.Options.ClientCertificates.Add(certificateFile);
    19.         socket.Options.RemoteCertificateValidationCallback = Validat;
    20.     }
    21.  
    22.  
    23.     public async Task<bool> OpenConnection()
    24.     {
    25.         try
    26.         {
    27.  
    28.             await socket.ConnectAsync(new Uri($"{protocoll}{domain}:{port}"), CancellationToken.None); // TlsException: Handshake failed - error code: UNITYTLS_INTERNAL_ERROR, verify result: UNITYTLS_X509VERIFY_FLAG_NOT_TRUSTED
    29.  
    30.             return true;
    31.         }
    32.         catch
    33.         {
    34.             return false;
    35.         }
    36.    
    37. /*.......stuff......*/
    38.  
    39.     //Validation override for self signed cvertificates
    40.     private bool Validat(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
    41.     {
    42.         bool isOk = true;
    43.         if (sslPolicyErrors != SslPolicyErrors.None)
    44.         {
    45.             for (int i = 0; i < chain.ChainStatus.Length; i++)
    46.             {
    47.                 if (chain.ChainStatus[i].Status == X509ChainStatusFlags.RevocationStatusUnknown)
    48.                 {
    49.                     continue;
    50.                 }
    51.                 chain.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain;
    52.                 chain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
    53.                 chain.ChainPolicy.UrlRetrievalTimeout = new TimeSpan(0, 1, 0);
    54.                 chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllFlags;
    55.                 bool chainIsValid = chain.Build((X509Certificate2)certificate);
    56.                 if (!chainIsValid)
    57.                 {
    58.                     isOk = false;
    59.                     break;
    60.                 }
    61.             }
    62.         }
    63.         return isOk;
    64.     }
    65. }
     
  2. Nad_B

    Nad_B

    Joined:
    Aug 1, 2021
    Posts:
    326
    Why not just return true in "Validat"(e) ?


    Code (CSharp):
    1. private bool Validat(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
    2. {
    3.     return true;
    4. }
    5.    
     
  3. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,524
    While this of course "could work", it should never be used. You simply accept any server certificate. So you're open for man-in-the-middle attacks.

    Using a client certificate is just a certificate to identify the client to the server. However the server would still need his own ssl certificate. Self-signed certificates are never considered "save" unless you actually install them on the machine or you ship the public key of the server certificate with the client and do the verification yourself in the callback. I haven't reallly done that since my raspberry pi webserver uses a lets encrypt certificate which is a valid certificate that can actually be verified against the usual chain of trust since lets encrypt is a trusted CA.

    Note:
    This:
    "./Assets/cert/cert.pem"
    would not work in a build game at all since there's no Assets folder in a build game. Pure binary assets have to be shipped with the game either as TextAsset or in the streamingassets folder.

    It seems mono actually had a lot of issues with not having any root certificates at hand. Of course nowadays things get mixed with different frameworks and things work quite different on various platforms. This SO answer may help?

    There's an actual issue on the mono project specifically with running a websocket server with a self signed certificate and the validation of that connection. I haven't messed with any of that lately, so I can't really provide much help here. I have implemented my own websocket server from scratch, but only
    ws:
    , not
    wss:
    :)
     
    Nad_B likes this.
  4. Nad_B

    Nad_B

    Joined:
    Aug 1, 2021
    Posts:
    326
    It makes sense to never bypass TLS validation, but in this case it does not matter since I presume the full certificate (including the private key) is shipped with the game itself in the PEM file as I don't think it'll be wise to ask users to install a self signed certificate before playing your game... so security is already inexistant.

    If the certificate is installed as a trusted one on the machine, there will be no need to override validation at all, it'll just works.

    Anyway in the validation part, I think we need to do something like:
    Code (CSharp):
    1. // don't know if X509Certificate overrides equality, if not, we need to compare the two manually
    2. return certificate == certificateFile;
    Edit:
    X509Certificate has a custom Equals method that takes another X509Certificate and does the necessary work:
    Code (CSharp):
    1. return certificate.Equals(certificateFile);
    Edit 2:
    Apparently Equals() is not the recommended way for security checks. Instead it's advised to compare the Thumbprints instead:
    Code (CSharp):
    1. return certificate.Thumbprint == certificateFile.Thumbprint;
     
    Last edited: Oct 6, 2023
    Bunny83 likes this.
  5. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,524
    Well, not really. You may ship a client certificate with your application, but that's only used to identify your application to the server. The server certificate can be a self signed certificate but you would NEVER ship the private key with your application. A pre-shared key is actually more secure than the "normal" chain of trust since both, the client and server have a pre-shared key they can rely on. With the chain of trust and certificates that are signed by a CA, you essentially have to trust the verification and validating process of the CA. Though as you might know, you could install a compromised root certificate on a machine that could verify and compromise EVERY certificate. Some business firewalls offer such a certificate in order to sniff and scan all the traffic. However certain applications (mostly bank software which often ship certificates with the software) would refuse to work as they can detect this mitm attack since they know the certificate.

    Yes, you should "simply verify" the server certificate against the public key that you ship with your application. That's the most secure solution. More secure than an "actual" signed SSL certificate. That's why high security software usually ships with explicit certificates. Though the verification usually involves checking a signature that the server has signed with his private key with the public key of the server. That way the client can be sure the server actually has the private key. This is usually done when the actual handshake is done to establish the actual key exchange. So yes, verifying that the server certificate actually is the one you (the client) know about is usually the way to go. Though I also don't know how to properly "compare" the certificate. The Equals method doesn't really look very promising as it "only" compare the serial number and the issuer name. Both could be used in a forged certificate so a comparison would turn out true. The only safe way is to compare the public key as that's where the strength of an RSA key lies. You can't create a new certificate with the same public key without knowing the private key (or at least it's not feasible). Comparing the rawdata, the thumbprint or the public key byte array probably makes more sense. There's the "GetCertHashString" method that some examples seem to use. They simply hardcode the hex string of the certificate in the application.
     
    Nad_B likes this.
  6. Nad_B

    Nad_B

    Joined:
    Aug 1, 2021
    Posts:
    326
    Thanks @Bunny83, great answer.

    I think the term for accepting only a certain certificate that matches a public key in the client is called "Certificate pinning" and is now using widely in apps to (try) stop packets sniffing tools (like Wireshark or Fiddler) which decrypt https requests by using a man-in-the-middle attack approach (like you said about "installing a compromised root certificate on a machine that could verify and compromise EVERY certificate")
     
    Bunny83 likes this.
  7. Mealycons

    Mealycons

    Joined:
    Dec 12, 2021
    Posts:
    5
    Thanks @Nad_B and @Bunny83 for your input.
    Let's Encrypt is also "self" signed cert and is via default not trusted by unity..

    I tryed some stuff. This helped me a bit "This SO answer".
    At the moment I can establish an SSL connection from the Unity Editor Debug Play, but not when I build and upload it.

    Additional Informaiton:
    I use webgl export and i write the cert file as a string in the code and convert it into bytes.

    Code (CSharp):
    1.  
    2.             byte[] bytes = Encoding.ASCII.GetBytes("-----BEGIN CERTIFICATE-----\r\n....");
    3.             certificateFile = new X509Certificate(bytes);
    4.  
    5.             domain = _domain;
    6.             port = _port;
    7.  
    8.             //SSL Options
    9.             socket.Options.ClientCertificates.Add(certificateFile);
    10.             socket.Options.RemoteCertificateValidationCallback = Validat;
    11.  
    12.             ServicePointManager.CertificatePolicy = new NoCheckCertificatePolicy();
    My Webserver is a Apache2 with the htaccess file from the doc and default settings.

    When i upload the build to my webserver, i have a endless loop that my socket is still in the state "connecting". Have any body an idea what is happening ?
     
    Last edited: Oct 11, 2023
  8. Mealycons

    Mealycons

    Joined:
    Dec 12, 2021
    Posts:
    5
    btw, i cant write (dot)htaccess ;)
     
  9. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,524
    Not true. Lets Encrypt certificates are signed by an actual CA. Some devices may not accept that CA because they may not have that particular root certificate it's signed with, but most do.

    You create a WebGL export? Then you can completely forget about using self-signed certificates because in WebGL ALL https communcation goes through the browser and you can not bypass the security sandbox of the browser. You can not use the .NET WebRequest class since you can not use sockets at all. The only things you can use in a WebGL build are http(s) connections through the browser (UnityWebRequest) or WebSockets (also through the browser).

    Let's Encrypt certificates work well. We even use them for our backend servers at work and I use one for my own local server. I have a dyndns domain name and actually host my own apache server on my raspberry pi which is reachable from the internet. I created a proper Let's Encrypt certificate for my domain name. SSL with signed certificates require either a domain name or a fix IP address which have to be the actual target of the certificate. However most CAs do not issue certificates for public IPs since in most cases you can't really prove that it's actually fix for the duration of the certificate. So you usually need a domain name anyways. IPs can change owner quite frequently, domain names usually don't

    You can, on your machine only, accept that self signed certificate inside your browser for your domain and it should work (of course in your browser only). Though as I said, you can not use any class that is based on System.Net.Sockets in a WebGL build. Any other platform would work
     
    Mealycons likes this.
  10. Mealycons

    Mealycons

    Joined:
    Dec 12, 2021
    Posts:
    5
    Ahh i see. In my case, Rest-API calls is not an option about the amount of potentials calls and their overhead.

    Ya looks good, i use a cloud instance with "clean" apache webserver and tagget my domain to this public (static) ip.
    Over http (without ssl) the websockets was sucessfully, but i dont like the warning " without ssl connection" and there began my trouble.

    But when i understand you correctly, why i got the tls exemption with let's encrypt certificate ? My browser give me nothin about these ?
     
  11. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,524
    When you do what exactly? What class / method do you use in Unity? Do you try to reach something on the same domain where you host your WebGL content or is it a different domain / endpoint? Keep in mind accessing a different port on the same domain counts as a separate endpoint and requires your server to implement CORS headers.

    Here's a reddit question which also reports issues with a Let's Encrypt certificate, but the actual issue was that he wasn't loading everything from the same origin. Since you create a webGL build, make sure you check the browser console, network and console tab.

    Depending on the browser you're using, when debugging it's helpful when the console is not clearing after a page reload. This way you can properly detect and analyse redirects and other issues. However you have to clear the logs yourself or it keeps piling on. In Firefox it's "devtools.netmonitor.persistlog". The latest FF and chrome versions should have this setting directly as a checkbox in the developer tools and is usually called "preserve log".
     
  12. Mealycons

    Mealycons

    Joined:
    Dec 12, 2021
    Posts:
    5
    Ya, im distanced to cors i don't know why ^^

    Founded a quick sultion, i will refactore in the future, maybe others have the same issue.

    I used Python in the Backen. I have 2 instances (1 for the socket server) (1 for the webserver).
    I used the fullchain.pem in addition with, this gave the problem with the ssl for the lets encrypt certificates.
    Now, i use the cert.pem and privkey.pem for the backend.

    I think my problem was, that i use one instance for the webserver 443 and socket <other port>. I seperated it. With the information from the thread, i use the jslib variant.[https://docs.unity3d.com/2021.3/Documentation/Manual/webgl-interactingwithbrowserscripting.html]

    SSL Websocket with Lets Encrypt Certificated Websocker Server
    Code (JavaScript):
    1. mergeInto(LibraryManager.library, {
    2.  
    3.   TestSSLSocket: function() {
    4.         var socket = new WebSocket("wss://<domain.de>:<wss port>");
    5.         socket.addEventListener('open', function(event) {
    6.             socket.send("{JSON}");
    7.             window.alert("SSL Websocket sucesfully created ! ");
    8.         });
    9.         socket.addEventListener('close', function(event) {
    10.             console.log("conn close to wss server !");
    11.         });
    12.   },
    13.  
    14. });