// // TlsTest.cs: TLS/SSL Test Program // // Author: // Sebastien Pouliot // // (C) 2004 Novell (http://www.novell.com) // using System; using System.Collections; using System.Globalization; using System.IO; using System.Net; using System.Net.Sockets; using System.Reflection; using System.Security.Cryptography.X509Certificates; using System.Text; using Mono.Security.Protocol.Tls; public class TlsTest { public static void Usage (string message) { Console.WriteLine ("{0}tlstest - Copyright (c) 2004 Novell", Environment.NewLine); if (message != null) { Console.WriteLine ("{0}{1}{0}", Environment.NewLine, message); } Console.WriteLine ("Usage:"); Console.WriteLine ("tlstest [protocol] [class] [credentials] [--x:x509 [--x:x509]] [--time] [--show] url [...]"); Console.WriteLine ("{0}protocol (only applicable when using stream)", Environment.NewLine); Console.WriteLine ("\t--any \tNegotiate protocol [default]"); Console.WriteLine ("\t--ssl \tUse SSLv3"); Console.WriteLine ("\t--ssl2 \tUse SSLv2 - unsupported on Mono"); Console.WriteLine ("\t--ssl3 \tUse SSLv3"); Console.WriteLine ("\t--tls \tUse TLSv1"); Console.WriteLine ("\t--tls1 \tUse TLSv1"); Console.WriteLine ("{0}class", Environment.NewLine); Console.WriteLine ("\t--stream\tDirectly use the SslClientStream [default]"); Console.WriteLine ("\t--web \tUse the WebRequest/WebResponse classes"); Console.WriteLine ("{0}credentials", Environment.NewLine); Console.WriteLine ("\t--basic:username:password:domain\tBasic Authentication"); Console.WriteLine ("\t--digest:username:password:domain\tDigest Authentication"); Console.WriteLine ("{0}options", Environment.NewLine); Console.WriteLine ("\t--x:x509\tX.509 client certificate (multiple entries allowed"); Console.WriteLine ("\t--time \tShow the time required for each page load"); Console.WriteLine ("\t--show \tShow the web page content on screen"); Console.WriteLine ("{0}\turl [...]\tOne, or more, URL to download{0}", Environment.NewLine); } private static bool show; private static bool time; private static bool web; private static Mono.Security.Protocol.Tls.SecurityProtocolType protocol = Mono.Security.Protocol.Tls.SecurityProtocolType.Default; private static X509CertificateCollection certificates = new X509CertificateCollection (); private static NetworkCredential basicCred; private static NetworkCredential digestCred; public static void Main (string[] args) { if (args.Length == 0) { Usage ("Missing arguments"); return; } ArrayList urls = new ArrayList (); foreach (string arg in args) { switch (arg) { // protocol case "--any": protocol = Mono.Security.Protocol.Tls.SecurityProtocolType.Default; break; case "--ssl": case "--ssl3": protocol = Mono.Security.Protocol.Tls.SecurityProtocolType.Ssl3; break; case "--ssl2": protocol = Mono.Security.Protocol.Tls.SecurityProtocolType.Ssl2; // note: will only works with Fx 1.2 // but the tool doesn't link with it Usage ("Not supported"); return; case "--tls": case "--tls1": protocol = Mono.Security.Protocol.Tls.SecurityProtocolType.Tls; break; // class case "--stream": web = false; break; case "--web": web = true; break; // options case "--time": time = true; break; case "--show": show = true; break; case "--help": Usage (null); return; // credentials, certificates, urls or bad options default: if (arg.StartsWith ("--digest:")) { digestCred = GetCredentials (arg.Substring (9)); continue; } else if (arg.StartsWith ("--basic:")) { basicCred = GetCredentials (arg.Substring (8)); continue; } else if (arg.StartsWith ("--x:")) { string filename = arg.Substring (4); X509Certificate x509 = X509Certificate.CreateFromCertFile (filename); certificates.Add (x509); continue; } else if (arg.StartsWith ("--")) { Usage ("Invalid option " + arg); return; } urls.Add (arg); break; } } if ((web) && (protocol != Mono.Security.Protocol.Tls.SecurityProtocolType.Default)) { Usage ("You can't set the protocol when using the WebRequest/WebResponse class"); return; } if (urls.Count == 0) { Usage ("no URL were specified"); return; } foreach (string url in urls) { Console.WriteLine ("{0}{1}", Environment.NewLine, url); string content = null; DateTime start = DateTime.Now; try { if (web) { content = GetWebPage (url); } else { content = GetStreamPage (url); } } catch (Exception e) { // HResult is protected - but very useful in debugging PropertyInfo pi = e.GetType ().GetProperty ("HResult", BindingFlags.NonPublic | BindingFlags.GetProperty | BindingFlags.Instance); Console.WriteLine ("FAILED: #{0}", (int)pi.GetValue (e, null)); Console.WriteLine (e.ToString ()); } TimeSpan ts = (DateTime.Now - start); if ((show) && (content != null)) { Console.WriteLine ("{0}{1}{0}", Environment.NewLine, content); } if (time) { Console.WriteLine ("Time: " + ts.ToString ()); } } } public static string GetWebPage (string url) { ServicePointManager.CertificatePolicy = new TestCertificatePolicy (); Uri uri = new Uri (url); HttpWebRequest req = (HttpWebRequest) WebRequest.Create (uri); if ((digestCred != null) || (basicCred != null)) { CredentialCache cache = new CredentialCache (); if (digestCred != null) cache.Add (uri, "Digest", digestCred); if (basicCred != null) cache.Add (uri, "Basic", basicCred); req.Credentials = cache; } if (certificates.Count > 0) req.ClientCertificates.AddRange (certificates); WebResponse resp = req.GetResponse (); Stream stream = resp.GetResponseStream (); StreamReader sr = new StreamReader (stream, Encoding.UTF8); return sr.ReadToEnd (); } public static string GetStreamPage (string url) { Uri uri = new Uri (url); if (uri.Scheme != Uri.UriSchemeHttps) throw new NotSupportedException ("Stream only works with HTTPS protocol"); IPHostEntry host = Dns.Resolve (uri.Host); IPAddress ip = host.AddressList [0]; Socket socket = new Socket (ip.AddressFamily, SocketType.Stream, ProtocolType.Tcp); socket.Connect (new IPEndPoint (ip, uri.Port)); NetworkStream ns = new NetworkStream (socket, false); SslClientStream ssl = new SslClientStream (ns, uri.Host, false, protocol, certificates); ssl.ServerCertValidationDelegate += new CertificateValidationCallback (CertificateValidation); StreamWriter sw = new StreamWriter (ssl); sw.WriteLine ("GET {0} HTTP/1.0{1}", uri.AbsolutePath, Environment.NewLine); sw.Flush (); StreamReader sr = new StreamReader (ssl, Encoding.UTF8); return sr.ReadToEnd (); } private static NetworkCredential GetCredentials (string credentials) { string[] creds = credentials.Split (':'); NetworkCredential nc = new NetworkCredential (); nc.UserName = ((creds.Length > 0) ? creds [0] : String.Empty); nc.Password = ((creds.Length > 1) ? creds [1] : String.Empty); nc.Domain = ((creds.Length > 2) ? creds [2] : String.Empty); return nc; } private static void ShowCertificateError (int error) { string message = null; switch (error) { case -2146762490: message = "CERT_E_PURPOSE 0x800B0106"; break; case -2146762481: message = "CERT_E_CN_NO_MATCH 0x800B010F"; break; case -2146869223: message = "TRUST_E_BASIC_CONSTRAINTS 0x80096019"; break; case -2146869232: message = "TRUST_E_BAD_DIGEST 0x80096010"; break; case -2146762494: message = "CERT_E_VALIDITYPERIODNESTING 0x800B0102"; break; case -2146762495: message = "CERT_E_EXPIRED 0x800B0101"; break; case -2146762486: message = "CERT_E_CHAINING 0x800B010A"; break; case -2146762487: message = "CERT_E_UNTRUSTEDROOT 0x800B0109"; break; default: message = "unknown (try WinError.h)"; break; } Console.WriteLine ("Error #{0}: {1}", error, message); } private static bool CertificateValidation (X509Certificate certificate, int[] certificateErrors) { if (certificateErrors.Length > 0) { Console.WriteLine (certificate.ToString (true)); // X509Certificate.ToString(true) doesn't show dates :-( Console.WriteLine ("\tValid From: {0}", certificate.GetEffectiveDateString ()); Console.WriteLine ("\tValid Until: {0}{1}", certificate.GetExpirationDateString (), Environment.NewLine); // multiple errors are possible using SslClientStream foreach (int error in certificateErrors) { ShowCertificateError (error); } } // whatever the reason we do not stop the SSL connection return true; } public class TestCertificatePolicy : ICertificatePolicy { public bool CheckValidationResult (ServicePoint sp, X509Certificate certificate, WebRequest request, int error) { if (error != 0) { Console.WriteLine (certificate.ToString (true)); // X509Certificate.ToString(true) doesn't show dates :-( Console.WriteLine ("\tValid From: {0}", certificate.GetEffectiveDateString ()); Console.WriteLine ("\tValid Until: {0}{1}", certificate.GetExpirationDateString (), Environment.NewLine); ShowCertificateError (error); } // whatever the reason we do not stop the SSL connection return true; } } }