325 lines
9.3 KiB
C#
325 lines
9.3 KiB
C#
//
|
|
// TlsTest.cs: TLS/SSL Test Program
|
|
//
|
|
// Author:
|
|
// Sebastien Pouliot <sebastien@xamarin.com>
|
|
//
|
|
// (C) 2004 Novell (http://www.novell.com)
|
|
// Copyright 2014 Xamarin Inc. (http://www.xamarin.com)
|
|
//
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Linq;
|
|
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("\t--tls11 \tUse TLSv1.1");
|
|
Console.WriteLine("\t--tls12 \tUse TLSv1.2");
|
|
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;
|
|
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;
|
|
case "--tls11":
|
|
protocol = Mono.Security.Protocol.Tls.SecurityProtocolType.Tls11;
|
|
break;
|
|
case "--tls12":
|
|
protocol = Mono.Security.Protocol.Tls.SecurityProtocolType.Tls12;
|
|
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 (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 now public (was protected before 4.5)
|
|
Console.WriteLine("FAILED: #{0}", e.HResult);
|
|
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();
|
|
if (protocol != null)
|
|
ServicePointManager.SecurityProtocol = (System.Net.SecurityProtocolType)(int)protocol.Value;
|
|
|
|
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 ?? Mono.Security.Protocol.Tls.SecurityProtocolType.Default, 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;
|
|
}
|
|
}
|
|
}
|