/* * Run the tests and return the report. */ internal Report Run() { string sni = explicitSNI; if (sni == null) { sni = serverName; } else if (sni == "-") { sni = null; } /* * Accumulate time offsets between client and server. */ timeOffsets = new List <long>(); /* * To keep track of DHE/ECDHE parameters. */ minDHSize = 0; minECSize = 0; minECSizeExt = 0; namedCurves = new SortedDictionary <int, SSLCurve>(); curveExplicitPrime = 0; curveExplicitChar2 = 0; unknownSKE = false; doesRenego = false; doesEtM = false; /* * Overall process: * * 1. First, try SSL 2.0. This is a single connection. * After this test, everything else uses SSL 3.0+. * * 2. Try to confirm that we are talking to an actual * SSL/TLS server and obtain its tolerance to variants: * maximum client version, presence of extensions, * large ClientHello messages. * * 3. For each supported protocol version, find * accepted cipher suites, then work out server's * behaviour for suite selection (client order, server * order, other). * * 4. Print report for cipher suites. * * 5. Print other information (compression support, * certificate information, DHE/ECDHE details...). */ rp = new Report(); rp.ConnName = serverName; rp.ConnPort = serverPort; rp.SNI = sni; /* * SSL 2.0 attempt. */ if (minVersion <= M.SSLv20) { if (verbose) { Console.WriteLine("[trying version=SSLv2]"); } SSL2 v2 = DoConnectV2(); if (v2 != null) { if (verbose) { Console.WriteLine("[SSLv2 supported," + " {0} cipher suite(s)]", v2.CipherSuites.Length); } } } /* * Make the list of cipher suites we are interested in. */ csl = new List <int>(); if (allSuites) { for (int i = 1; i <= 0xFFFF; i++) { if (i == M.TLS_EMPTY_RENEGOTIATION_INFO_SCSV || i == M.TLS_FALLBACK_SCSV) { continue; } csl.Add(i); } } else { foreach (int s in CipherSuite.ALL.Keys) { if (s != 0) { csl.Add(s); } } } /* * Create a test builder and populate it with the * configured information. */ tb = new SSLTestBuilder(); tb.ServerName = sni; tb.MaxVersion = maxVersion; withExts = true; gotSSLAnswer = false; serverCompress = false; sslAlert = -1; maxRecordLen = 8192; if (addECExt) { List <int> rx = new List <int>(); foreach (int x in SSLCurve.ALL.Keys) { rx.Add(x); } tb.SupportedCurves = rx.ToArray(); } /* * Each try entails using a protocol version, a * maximum record length, and optional extensions. * We then try all chunks of cipher suites (in our * list of cipher suites to try) until we get a * successfull handshake. * * On error, we try reducing maximum record length. * If that still fails, we lower the maximum version. * If even SSL 3.0 fails with a small record, then * we try again the whole process without extensions. */ for (;;) { maxRecordLen = 8192; if (TryConnect() || gotReadTimeout) { break; } maxRecordLen = 1024; if (TryConnect() || gotReadTimeout) { break; } maxRecordLen = 256; if (TryConnect() || gotReadTimeout) { break; } int v = tb.MaxVersion; if (v > M.SSLv30) { tb.MaxVersion = v - 1; continue; } if (withExts) { withExts = false; tb.DisableExtensions(); tb.MaxVersion = maxVersion; continue; } /* * No success. */ if (gotSSLAnswer && sslAlert >= 0) { throw new SSLAlertException(sslAlert); } else { string msg = "Could not initiate a handshake" + " (not SSL/TLS?)"; if (gotReadTimeout) { msg += " [read timeout]"; } throw new Exception(msg); } } if (maxRecordLen < 8192) { rp.NeedsShortHello = true; } if (!withExts) { rp.NoExtensions = true; } maxVersion = tb.MaxVersion; int startVersion = minVersion; if (startVersion < M.SSLv30) { startVersion = M.SSLv30; } /* * Now extract supported cipher suites for each protocol * version. We also try to get the highest version for * which EC-based cipher suites are supported, and * extract all supported EC-based cipher suites for * that version. * * For each such protocol version, we also try connecting * with a ClientHello in V2 format; we do so while ensuring * that the total hello length is no more than 127 bytes, * for maximum interoperability. Note that the V2 format * has no room for any extension. */ int maxECVersion = -1; int[] suppEC = null; for (int v = startVersion; v <= maxVersion; v++) { tb.MaxVersion = v; SupportedCipherSuites scs = GetSupportedCipherSuites(); if (scs == null) { continue; } rp.SetCipherSuites(v, scs); int[] ecs = scs.GetKnownECSuites(); if (ecs.Length > 0) { maxECVersion = v; suppEC = ecs; } if (scs.KXReuseDH) { rp.KXReuseDH = true; } if (scs.KXReuseECDH) { rp.KXReuseECDH = true; } /* * Check V2 format for ClientHello. */ int savedRV = tb.RecordVersion; tb.RecordVersion = M.SSLv20; if (DoConnect() != null) { rp.SupportsV2Hello = true; } tb.RecordVersion = savedRV; } /* * At that point, if the server used an EC-based cipher * suite, and we did not present a Supported Elliptic * Curves extension, then the server selected the * curve(s) all by itself. If we always presented that * extension, then we want to try talking to the server * without it, to see if it accepts doing EC at all * without the extension, and, if yes, what curve it may * use in that case. */ int[] spontaneousEC; SSLCurve[] spontaneousNamedCurves; if (addECExt && withExts && maxECVersion >= 0) { if (verbose) { Console.WriteLine("[spontaneous EC support," + " version={0}, {1} suite(s)]", M.VersionString(maxECVersion), suppEC.Length); } IDictionary <int, SSLCurve> oldNamedCurves = namedCurves; namedCurves = new SortedDictionary <int, SSLCurve>(); tb.MaxVersion = maxECVersion; tb.SupportedCurves = null; spontaneousEC = GetSupportedCipherSuites(suppEC, null); spontaneousNamedCurves = M.ToValueArray(namedCurves); foreach (int s in namedCurves.Keys) { oldNamedCurves[s] = namedCurves[s]; } namedCurves = oldNamedCurves; if (verbose) { Console.WriteLine(); } } else { spontaneousEC = suppEC; spontaneousNamedCurves = M.ToValueArray(namedCurves); } /* * We now try to enumerate all supported EC curves. */ if (withExts && maxECVersion >= 0) { tb.MaxVersion = maxECVersion; tb.CipherSuites = suppEC; if (verbose) { Console.WriteLine("[elliptic curve enumeration," + " version={0}, {1} suite(s)]", M.VersionString(maxECVersion), suppEC.Length); } /* * Try named curves. */ IDictionary <int, int> rec = new SortedDictionary <int, int>(); foreach (int id in SSLCurve.ALL.Keys) { AddToSet(rec, id); } while (rec.Count > 0) { tb.SupportedCurves = SetToArray(rec); SSLTestResult tr = DoConnect(); if (tr == null) { break; } SSLCurve sc = tr.Curve; if (sc == null) { break; } if (!rec.ContainsKey(sc.Id)) { break; } rec.Remove(sc.Id); } /* * Try explicit curves, prime and char2. */ tb.SupportedCurves = new int[] { SSLCurve.EXPLICIT_PRIME }; DoConnect(); tb.SupportedCurves = new int[] { SSLCurve.EXPLICIT_CHAR2 }; DoConnect(); if (verbose) { Console.WriteLine(); } } rp.DeflateCompress = serverCompress; rp.ServerTimeOffset = GetServerTimeOffset(); rp.SupportsSecureRenegotiation = doesRenego; rp.SupportsEncryptThenMAC = doesEtM; rp.MinDHSize = minDHSize; rp.MinECSize = minECSize; rp.MinECSizeExt = minECSizeExt; rp.NamedCurves = M.ToValueArray(namedCurves); rp.SpontaneousEC = spontaneousEC; rp.SpontaneousNamedCurves = spontaneousNamedCurves; rp.CurveExplicitPrime = curveExplicitPrime; rp.CurveExplicitChar2 = curveExplicitChar2; rp.UnknownSKE = unknownSKE; return(rp); }