Search Unity

SSL Certificate Validation

Discussion in 'Scripting' started by jogo, Jan 14, 2014.

  1. jogo

    jogo

    Joined:
    May 31, 2013
    Posts:
    17
    Hi there,

    how is it possible to validate a SSL Certificate in Unity? I've come so far that the check works in the Unity game window, but not on real iOS or Android devices...


    Code (csharp):
    1.        
    2. ServicePointManager.CertificatePolicy = new SSLTest();
    3. try {
    4.         WebRequest wr = WebRequest.Create(url);
    5.         Stream stream = wr.GetResponse ().GetResponseStream ();
    6.            
    7.         Console.WriteLine (new StreamReader (stream).ReadToEnd ());
    8. }
    9. catch (WebException we) {
    10.     if (we != null  we.InnerException != null){
    11.         if (we.InnerException.Message == "The authentication or decryption has failed.")
    12.         {
    13.             // ssl certificate validation failed
    14.         }
    15.     }
    16. }
    17.  
    SSLTest:

    Code (csharp):
    1.  
    2. public class SSLTest : ICertificatePolicy {
    3.  
    4.     public bool CheckValidationResult (ServicePoint sp, X509Certificate certificate, WebRequest request, int error)
    5.     {
    6.         if (error == 0)
    7.         {
    8.             return true;
    9.         }
    10.         return false;
    11.     }
    12.  
    13. }
     
  2. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    It's possible that your exception message check isn't passing... you shouldn't check against the text of the message. You're catching your WebException, so what you want to do is check the Status property of the WebException:

    Code (csharp):
    1.  
    2. //...
    3. catch (WebException ex)
    4. {
    5.           if(ex.Status == WebExceptionStatus.TrustFailure)
    6.          {
    7.                 ///The SSL validation has failed.
    8.          }
    9.  
    10. }
    11.  
     
  3. jogo

    jogo

    Joined:
    May 31, 2013
    Posts:
    17
    Thank you for your answer, Dustin ... I tried that check first, but the thrown exception had the status SendFailure, not TrustFailure. So I had to look at the Message in InnerException.
    I know this is not pretty, but it was the only way that it worked in the game window for a start.
     
  4. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    Ok... what's the base url you're trying to hit so I can look at the cert? Is it a self-signed cert?
     
  5. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
  6. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    And sorry to spam the post... one more thing to try. :) Add this in your code before you make the web call:

    Code (csharp):
    1.  
    2. var aesCrypto  = new System.Security.Cryptography.AesCryptoServiceProvider();
    3.  
    You won't actually be using it but from what I read it's a possibly related mono bug and creating an instance of the provider will prevent the linker from tossing it during JIT / AOT.
     
  7. jogo

    jogo

    Joined:
    May 31, 2013
    Posts:
    17
    Hey Dustin,

    you got a mail with the base url. It seems I am a little bit closer ... I hope.


    With your help and some research about ServerCertificateValidationCallback I changed my code to:


    Code (csharp):
    1. public static bool ValidateServerCertificate(
    2.    object sender,
    3.    System.Security.Cryptography.X509Certificates.X509Certificate certificate,
    4.    X509Chain chain,
    5.    System.Net.Security.SslPolicyErrors sslPolicyErrors)
    6. {
    7.    if (sslPolicyErrors == System.Net.Security.SslPolicyErrors.None)
    8.         return true;
    9.  
    10.     Debug.Log("Certificate error: " + sslPolicyErrors);
    11.     return false;
    12. }
    13.    
    14.    
    15.        
    16. // in my main function:
    17. System.Net.Security.RemoteCertificateValidationCallback orgCallback = ServicePointManager.ServerCertificateValidationCallback;
    18. try
    19. {
    20.     var aesCrypto  = new System.Security.Cryptography.AesCryptoServiceProvider();
    21.  
    22.     ServicePointManager.ServerCertificateValidationCallback = new System.Net.Security.RemoteCertificateValidationCallback(ValidateServerCertificate);
    23.     ServicePointManager.Expect100Continue = true;
    24.        
    25.     WebRequest wr = WebRequest.Create(url);
    26.     Stream stream = wr.GetResponse ().GetResponseStream ();
    27.     Debug.Log (new StreamReader (stream).ReadToEnd ());
    28. }
    29. catch (WebException we)
    30. {
    31.     Debug.Log ("status: "+we.Status);
    32.     if (we != null  we.InnerException != null)
    33.     {
    34.         if (we.InnerException.Message == "The authentication or decryption has failed.")
    35.         {
    36.             sslCheckState = SSLCheckState.NOT_OK;
    37.         }
    38.     }
    39. }
    40. finally
    41. {
    42.     ServicePointManager.ServerCertificateValidationCallback = orgCallback;
    43. }
    44. }
    Calling the base url I've sent you, PolicyErrors contains "RemoteCertificateChainErrors" ... (output at line 10)
    If I call for example a test site with an expired SSL certificate like "https://testssl-expire.disig.sk/", PolicyErrors contains "RemoteCertificateNameMismath, RemoteCertificateChainErrors". So at least there is some difference...
     
  8. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    Ok... What you want to look at in your hander is:

    chain.ChainStatus

    This will return an array... you want to look at the "Status" and "Status Information" properties. There are two errors being returned:

    RevocationStatusUnknown

    and

    UntrustedRoot

    There may be something wrong with the cert... or it's not able to get the revocation status from the certificate authority for some reason.
     
  9. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    Ok I think I may have a solution for you. Remove the certificate validation handler for now and just go back to your webrequest in your Try/Catch block. I did some additional testing. I first created a site with a self-signed cert to verify and I got the same error. Then I changed the validation method to just return true and it worked perfectly, so it does seem to be a certificate validation error but I don't think the certificate is the problem because I replaced your URL with "https://www.google.com" and "https://www.microsoft.com" and I got the exact same error.

    I think the error is misleading and is actually caused by a policy restriction. First, change your code to this so you can see the actual exceptions being thrown:

    Code (csharp):
    1.  
    2.         try
    3.         {
    4.             WebRequest wr = WebRequest.Create("your-url-here");
    5.            
    6.             Stream stream = wr.GetResponse().GetResponseStream();
    7.             Debug.Log("Success");
    8.             Debug.Log(new StreamReader(stream).ReadToEnd());
    9.         }
    10.         catch(WebException we)
    11.         {
    12.             var ex = we as Exception;
    13.  
    14.             while (ex != null)
    15.             {
    16.                 Debug.Log(ex.ToString());
    17.                 ex = ex.InnerException;
    18.             }
    19.         }
    20.  
    You'll see three exceptions going back to the Invalid Server Certificate exception. I think this is because there is no crossdomain.xml file on your host. Create a crossdomain.xml file with the following. Easiest is to create it in Notepad++ and made sure it is UTF-8 encoded by clicking the Encoding menu and selecting "Encoding in UTF-8". It grants access to port 443 (the SSL Port).

    Code (csharp):
    1.  
    2. <?xml version="1.0" ?>
    3. <cross-domain-policy>
    4.     <allow-access-from domain="*" to-ports="443" />
    5. </cross-domain-policy>
    6.  
     
  10. jogo

    jogo

    Joined:
    May 31, 2013
    Posts:
    17
    Problem solved! After your suggestion with Crossdomain.xml I talked to the person responsible for the server where that file should have been copied. He did not want to go that way (security concerns) and we found a way that finally everything works fine. Now I will ship the certificates in my app, and at program start a X509Certificate2Collection takes the certificates in a static variable.
    When the ssl callback method is called, a new chain is build by that collection and checked.

    in ValidateServerCertificate:
    Code (csharp):
    1.  
    2.         X509Chain tempChain = new X509Chain();
    3.         tempChain.ChainPolicy.RevocationMode = chain.ChainPolicy.RevocationMode;   
    4.            
    5.         X509Certificate2Collection cert2Collection = tempChain.ChainPolicy.ExtraStore;
    6.         cert2Collection.AddRange(myStaticX509Collection);
    7.            
    8.         return = tempChain.Build(new X509Certificate2(certificate));
    9.  
     
  11. earthcrosser

    earthcrosser

    Joined:
    Oct 13, 2010
    Posts:
    122
    First why doesn't this work on my Android? Is it a Unity Pro thing?

    Second thanks for all your posts!, I was able to get something working for myself :) But it does not work on my Android Device, is this a Unity Pro function? I only have the free version thanks! Here's the code, what I had to do was that is a little different from the previous examples is include a client cert... Jogo I couldn't get Validate Cert to return true so I don't know what I'm doing wrong there so in my example it just always returns true.. so I'm still trying to figure that out but I am able to get my response back so I'm like 65% happy
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System;
    4. using System.IO;
    5. using System.Net;
    6. using System.Net.Security;
    7. using System.Security.Cryptography.X509Certificates;
    8. using System.Xml;
    9.  
    10. public class newhttptest : MonoBehaviour
    11. {
    12.     string s = "not set";
    13. // Use this for initialization
    14.         void Start ()
    15.         {
    16.                 X509Certificate2 adminClient = new X509Certificate2 ("Assets/Scripts/adminClient.p12", "xxxxxx");
    17.  
    18.  
    19.                 ServicePointManager.ServerCertificateValidationCallback = new System.Net.Security.RemoteCertificateValidationCallback (ValidateServerCertificate);
    20.                 try {
    21.  
    22.  
    23.                         HttpWebRequest request = (HttpWebRequest)WebRequest.Create ("https://xxxx/rest/user-message");
    24.                         request.AuthenticationLevel = AuthenticationLevel.MutualAuthRequested;
    25.  
    26.                         request.ClientCertificates.Add (adminClient);
    27.                         HttpWebResponse response = (HttpWebResponse)request.GetResponse ();
    28.  
    29.  
    30.                         //XmlDocument xmlDoc = new XmlDocument();
    31.                         //xmlDoc.Load(response.GetResponseStream());
    32.  
    33.                         Debug.Log(new StreamReader(response.GetResponseStream()).ReadToEnd());
    34.  
    35.  
    36.                         Debug.Log ("Success");
    37.  
    38.                         s = "yippy!";
    39.  
    40.                 } catch (WebException we) {
    41.                         var ex = we as Exception;
    42.                         s = "fail";
    43.                         while (ex != null) {
    44.                                 Debug.Log (ex.ToString ());
    45.                                 ex = ex.InnerException;
    46.                         }
    47.                 }
    48.  
    49.  
    50.  
    51.  
    52.         }
    53.  
    54.  
    55.         public static bool ValidateServerCertificate (
    56.         object sender,
    57.         System.Security.Cryptography.X509Certificates.X509Certificate certificate,
    58.         X509Chain chain,
    59.         System.Net.Security.SslPolicyErrors sslPolicyErrors)
    60.         {
    61.  
    62.                 return true;
    63.         }
    64.  
    65.     void OnGUI(){
    66.         GUI.Label (new Rect (0, 0, 100, 100), s);
    67.         if (GUI.Button (new Rect (20, 20, 100, 100), s)) {
    68.             Application.Quit();
    69.         }
    70.     }
    71.  
    72. // Update is called once per frame
    73.         void Update ()
    74.         {
    75.  
    76.         }
    77.  
    78.  
    79. }
    80.  
    81.  
     
  12. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    What happens when you try to run it on Android? Is there an error or does it just do nothing? And what version of Unity are you using?
     
  13. earthcrosser

    earthcrosser

    Joined:
    Oct 13, 2010
    Posts:
    122
    When I run it on Android nothing appears to happen, I set the button in the scene to get text "yippy!" or "fail" depending on the results of the try/catch statement but it keeps its default value "not set". I'm using Unity 4.5.0f6. The scene is empty except for the main camera and a cube with the script attached. I also made sure that the manifest file has
    <uses-permission android:name="android.permission.INTERNET" />
    is there another permission I need since I'm also using a file to create my x.509 certs?
     
  14. earthcrosser

    earthcrosser

    Joined:
    Oct 13, 2010
    Posts:
    122
    Ok I added a new permission to my android manifest file like so
    and I also added some comments around this line
    Code (CSharp):
    1.  
    2. s = "making cert";
    3. X509Certificate2 adminClient = new X509Certificate2 ("Assets/Scripts/adminClient.p12", "xxxxx");
    4. s = "cert created";
    5.  
    And what I found was is that the Android gets to the "making cert" line and quits so it's a good bet that this is where the error is thrown. So at least now I know where the problem is; so now I just need to figure out a fix. Let me know if you have an idea :)
     
  15. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    It fails when trying to create the new X509Certificate2 on line 3? And are you sure it's not a path problem accessing that file?
     
  16. earthcrosser

    earthcrosser

    Joined:
    Oct 13, 2010
    Posts:
    122
    Hi Dustin!
    So after I tried everything ... putting it the resources trying Application.persistentDataPath and storing the .p12 with a txt and bytes extension and then trying Resources.Load... nothing worked! Then finally I just used a www call to down load the .p12 from a server and then store it in Application.persistentDataPath and guess what? It worked!!!! I'll post all the details later today :) for now I'm taking a break! Thanks for your help with this :)
     
  17. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    No problem. I was guessing that was probably your issue is that it was something to do with accessing the file data.
     
  18. earthcrosser

    earthcrosser

    Joined:
    Oct 13, 2010
    Posts:
    122
    As promised here's what my final solution looks like
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.IO;
    4. using System.Text;
    5. using System.Net;
    6. using System.Net.Security;
    7. using System.Security.Cryptography.X509Certificates;
    8. using System.Xml;
    9.  
    10. public class RestCalls : MonoBehaviour
    11. {
    12.        
    13.         X509Certificate2 adminClient;
    14.         string clientCertPath="";
    15.         static string baseUrl = "https://"someurl"/rest/";
    16.         void Start ()
    17.         {
    18.                 clientCertPath = Application.persistentDataPath + "/adminClient.p12";
    19.                 StartCoroutine (GetCert ());  
    20.         }
    21.  
    22.         private IEnumerator GetCert ()
    23.         {
    24.                 if (File.Exists (clientCertPath)) {
    25.                         print ("I have the file");
    26.                         MakeRestCall();
    27.                 } else {
    28.                         WWW download = new WWW ("http://"pathtocert"/adminClient.p12");
    29.                         yield return download;
    30.                         if (download.error != null) {
    31.                                 print ("Error downloading: " + download.error);
    32.                         } else {
    33.                                 File.WriteAllBytes (clientCertPath, download.bytes);
    34.                                
    35.                         }
    36.                 }
    37.         }
    38.  
    39.         public XmlDocument MakeRestCall()
    40.         {
    41.             ServicePointManager.ServerCertificateValidationCallback = new System.Net.Security.RemoteCertificateValidationCallback (ValidateServerCertificate);
    42.             adminClient = new X509Certificate2 (clientCertPath, "xxxxx");
    43.             HttpWebRequest request = (HttpWebRequest)WebRequest.Create (baseUrl + "user-message");
    44.             request.AuthenticationLevel = AuthenticationLevel.MutualAuthRequested;
    45.            
    46.        
    47.             request.ClientCertificates.Add (adminClient);
    48.             HttpWebResponse response = (HttpWebResponse)request.GetResponse ();
    49.            
    50.             XmlDocument xmlDoc = new XmlDocument ();
    51.             xmlDoc.Load (response.GetResponseStream ());
    52.  
    53.             return xmlDoc;
    54.            
    55.            
    56.         }
    57.  
    58.         public static bool ValidateServerCertificate (
    59.             object sender,
    60.             System.Security.Cryptography.X509Certificates.X509Certificate certificate,
    61.             X509Chain chain,
    62.             System.Net.Security.SslPolicyErrors sslPolicyErrors)
    63.         {
    64.            //TODO Make this more secure
    65.             return true;
    66.         }
    67.  
    68.        
    69. }
    70.  
    71.  
     
    IgorAherne and akishimo like this.
  19. Selaphiel

    Selaphiel

    Joined:
    Jan 31, 2014
    Posts:
    23
    Hi,

    I using #18 same code for SSL validation, but i am getting below error on line no. 48 in #18 code, in editor. I am loading my .p12 certificate file from persistent storage. Please help

    Code (CSharp):
    1. IOException: BeginWrite failure
    2. System.Net.Sockets.NetworkStream.BeginWrite (System.Byte[] buffer, Int32 offset, Int32 size, System.AsyncCallback callback, System.Object state)
    3. Mono.Security.Protocol.Tls.RecordProtocol.BeginSendRecord (ContentType contentType, System.Byte[] recordData, System.AsyncCallback callback, System.Object state)
    4. Mono.Security.Protocol.Tls.RecordProtocol.SendRecord (ContentType contentType, System.Byte[] recordData)
    5. Mono.Security.Protocol.Tls.RecordProtocol.SendAlert (Mono.Security.Protocol.Tls.Alert alert)
    6. Mono.Security.Protocol.Tls.RecordProtocol.SendAlert (AlertDescription description)
    7. Mono.Security.Protocol.Tls.SslStreamBase.AsyncHandshakeCallback (IAsyncResult asyncResult)
    8. Rethrow as WebException: Error getting response stream (Write: BeginWrite failure): SendFailure
    9. System.Net.HttpWebRequest.EndGetResponse (IAsyncResult asyncResult)
    10. System.Net.HttpWebRequest.GetResponse ()
     
    Last edited: Oct 7, 2016
  20. gresolio

    gresolio

    Joined:
    Jan 5, 2014
    Posts:
    17
    How to validate SSL certificates when using HttpWebRequest
    Code (CSharp):
    1. ServicePointManager.ServerCertificateValidationCallback = MyRemoteCertificateValidationCallback;
    2.  
    3. public bool MyRemoteCertificateValidationCallback(System.Object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
    4. {
    5.     bool isOk = true;
    6.     // If there are errors in the certificate chain, look at each error to determine the cause.
    7.     if (sslPolicyErrors != SslPolicyErrors.None) {
    8.         for (int i = 0; i < chain.ChainStatus.Length; i++) {
    9.             if (chain.ChainStatus[i].Status != X509ChainStatusFlags.RevocationStatusUnknown) {
    10.                 chain.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain;
    11.                 chain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
    12.                 chain.ChainPolicy.UrlRetrievalTimeout = new TimeSpan(0, 1, 0);
    13.                 chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllFlags;
    14.                 bool chainIsValid = chain.Build((X509Certificate2)certificate);
    15.                 if (!chainIsValid) {
    16.                     isOk = false;
    17.                 }
    18.             }
    19.         }
    20.     }
    21.     return isOk;
    22. }
     
  21. a-t-hellboy

    a-t-hellboy

    Joined:
    Dec 6, 2013
    Posts:
    180
    Does Unity need to validate certificate with itself when I use for example Let's Encrypt or any site which sells certificate ? (not self-signed certificate)
     
  22. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,909
    Do you mean the TLS certificate?