2 * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files (the
6 * "Software"), to deal in the Software without restriction, including
7 * without limitation the rights to use, copy, modify, merge, publish,
8 * distribute, sublicense, and/or sell copies of the Software, and to
9 * permit persons to whom the Software is furnished to do so, subject to
10 * the following conditions:
12 * The above copyright notice and this permission notice shall be
13 * included in all copies or substantial portions of the Software.
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
19 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
20 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26 using System.Collections.Generic;
27 using System.Diagnostics;
38 * This is the main Twrch class implementation: it provides the entry
39 * point for the command-line application.
44 public static void Main(string[] args)
47 new Twrch().Run(args);
48 } catch (Exception e) {
49 Console.WriteLine(e.ToString());
75 IDictionary<string, object> testsByName;
81 void Run(string[] args)
83 List<string> r = new List<string>();
84 string confName = null;
86 foreach (string a in args) {
87 string b = a.ToLowerInvariant();
99 commandVerbose = true;
102 if (confName == null) {
110 if (confName == null) {
113 string[] testNames = r.ToArray();
114 conf = ReadConfig(confName);
116 doEnum = (testNames.Length == 0) ? 1 : -1;
118 commandFile = JSON.GetString(conf, "commandFile");
119 commandArgs = JSON.GetString(conf, "commandArgs");
120 chainRSAFile = JSON.GetString(conf, "chainRSA");
121 chainECFile = JSON.GetString(conf, "chainEC");
122 skeyRSAFile = JSON.GetString(conf, "skeyRSA");
123 skeyECFile = JSON.GetString(conf, "skeyEC");
124 chainRSA = DecodeChain(chainRSAFile);
125 skeyRSA = DecodePrivateKey(skeyRSAFile);
126 chainEC = DecodeChain(chainECFile);
127 skeyEC = DecodePrivateKey(skeyECFile);
128 versions = GetVersions();
129 if (versions.Length == 0) {
130 throw new Exception("Bad config: no versions");
132 versionMin = Int32.MaxValue;
134 foreach (int v in versions) {
135 versionMin = Math.Min(v, versionMin);
136 versionMax = Math.Max(v, versionMax);
138 cipherSuites = GetCipherSuites();
139 if (cipherSuites.Length == 0) {
140 throw new Exception("Bad config: no cipher suites");
142 hashAndSigns = GetHashAndSigns();
143 if (hashAndSigns.Length == 0) {
144 throw new Exception("Bad config: no hash-and-signs");
146 curves = GetCurves();
147 noCloseNotify = JSON.GetBool(conf, "noCloseNotify");
148 tests = JSON.GetArray(conf, "tests");
149 testsByName = new SortedDictionary<string, object>(
150 StringComparer.Ordinal);
151 foreach (object obj in tests) {
152 string name = JSON.GetString(obj, "name");
153 testsByName[name] = obj;
160 totalTests += ComputeTotalEnum();
162 if (testNames.Length == 0) {
163 foreach (object obj in tests) {
164 totalTests += GetNumTests(obj);
167 foreach (string name in testNames) {
169 int version, suite, curve, hs;
170 if (StringToTEnum(name, out client, out version,
171 out suite, out curve, out hs))
176 if (name.EndsWith("_client")
177 || name.EndsWith("_server"))
181 totalTests += GetNumTests(
190 if (testNames.Length == 0) {
191 foreach (object obj in tests) {
195 foreach (string name in testNames) {
197 int version, suite, curve, hs;
198 if (StringToTEnum(name, out client, out version,
199 out suite, out curve, out hs))
201 RunEnum(client, version,
205 if (name.EndsWith("_client")) {
207 } else if (name.EndsWith("_server")) {
210 RunTest(testsByName[name]);
213 string s = name.Substring(0, name.Length - 7);
214 RunTest(client, testsByName[s]);
219 Console.WriteLine("\rtotal = {0}, failed = {1}",
220 totalTests, totalFailures);
226 "usage: Twrch.exe [ options ] config [ test... ]");
230 " -trace enable trace mode (hex dump of all exchanged bytes)");
232 " -cv pass the '-v' argument to the test command");
234 " -enum perform all version/suite/curve/hash&sign combination tests");
236 " -noenum do NOT perform the version/suite/curve/hash&sign tests");
240 static object ReadConfig(string fname)
242 using (TextReader r = File.OpenText(fname)) {
243 return JSON.Parse(r);
249 string[] r = JSON.GetStringArray(conf, "versions");
250 int[] vv = new int[r.Length];
251 for (int i = 0; i < r.Length; i ++) {
252 vv[i] = SSL.GetVersionByName(r[i]);
258 internal static int GetVersionByName(string s)
260 s = s.Replace(" ", "").Replace(".", "").ToUpperInvariant();
262 case "TLS10": return SSL.TLS10;
263 case "TLS11": return SSL.TLS11;
264 case "TLS12": return SSL.TLS12;
266 throw new Exception(string.Format(
267 "Unknown version: '{0}'", s));
272 int[] GetCipherSuites()
274 return GetSuitesByName(
275 JSON.GetStringArray(conf, "cipherSuites"));
278 internal static int[] GetSuitesByName(string[] ss)
280 int[] r = new int[ss.Length];
281 for (int i = 0; i < ss.Length; i ++) {
282 r[i] = SSL.GetSuiteByName(ss[i]);
288 internal static int GetSuiteByName(string s)
291 case "NULL_WITH_NULL_NULL":
292 return SSL.NULL_WITH_NULL_NULL;
293 case "RSA_WITH_NULL_MD5":
294 return SSL.RSA_WITH_NULL_MD5;
295 case "RSA_WITH_NULL_SHA":
296 return SSL.RSA_WITH_NULL_SHA;
297 case "RSA_WITH_NULL_SHA256":
298 return SSL.RSA_WITH_NULL_SHA256;
299 case "RSA_WITH_RC4_128_MD5":
300 return SSL.RSA_WITH_RC4_128_MD5;
301 case "RSA_WITH_RC4_128_SHA":
302 return SSL.RSA_WITH_RC4_128_SHA;
303 case "RSA_WITH_3DES_EDE_CBC_SHA":
304 return SSL.RSA_WITH_3DES_EDE_CBC_SHA;
305 case "RSA_WITH_AES_128_CBC_SHA":
306 return SSL.RSA_WITH_AES_128_CBC_SHA;
307 case "RSA_WITH_AES_256_CBC_SHA":
308 return SSL.RSA_WITH_AES_256_CBC_SHA;
309 case "RSA_WITH_AES_128_CBC_SHA256":
310 return SSL.RSA_WITH_AES_128_CBC_SHA256;
311 case "RSA_WITH_AES_256_CBC_SHA256":
312 return SSL.RSA_WITH_AES_256_CBC_SHA256;
313 case "DH_DSS_WITH_3DES_EDE_CBC_SHA":
314 return SSL.DH_DSS_WITH_3DES_EDE_CBC_SHA;
315 case "DH_RSA_WITH_3DES_EDE_CBC_SHA":
316 return SSL.DH_RSA_WITH_3DES_EDE_CBC_SHA;
317 case "DHE_DSS_WITH_3DES_EDE_CBC_SHA":
318 return SSL.DHE_DSS_WITH_3DES_EDE_CBC_SHA;
319 case "DHE_RSA_WITH_3DES_EDE_CBC_SHA":
320 return SSL.DHE_RSA_WITH_3DES_EDE_CBC_SHA;
321 case "DH_DSS_WITH_AES_128_CBC_SHA":
322 return SSL.DH_DSS_WITH_AES_128_CBC_SHA;
323 case "DH_RSA_WITH_AES_128_CBC_SHA":
324 return SSL.DH_RSA_WITH_AES_128_CBC_SHA;
325 case "DHE_DSS_WITH_AES_128_CBC_SHA":
326 return SSL.DHE_DSS_WITH_AES_128_CBC_SHA;
327 case "DHE_RSA_WITH_AES_128_CBC_SHA":
328 return SSL.DHE_RSA_WITH_AES_128_CBC_SHA;
329 case "DH_DSS_WITH_AES_256_CBC_SHA":
330 return SSL.DH_DSS_WITH_AES_256_CBC_SHA;
331 case "DH_RSA_WITH_AES_256_CBC_SHA":
332 return SSL.DH_RSA_WITH_AES_256_CBC_SHA;
333 case "DHE_DSS_WITH_AES_256_CBC_SHA":
334 return SSL.DHE_DSS_WITH_AES_256_CBC_SHA;
335 case "DHE_RSA_WITH_AES_256_CBC_SHA":
336 return SSL.DHE_RSA_WITH_AES_256_CBC_SHA;
337 case "DH_DSS_WITH_AES_128_CBC_SHA256":
338 return SSL.DH_DSS_WITH_AES_128_CBC_SHA256;
339 case "DH_RSA_WITH_AES_128_CBC_SHA256":
340 return SSL.DH_RSA_WITH_AES_128_CBC_SHA256;
341 case "DHE_DSS_WITH_AES_128_CBC_SHA256":
342 return SSL.DHE_DSS_WITH_AES_128_CBC_SHA256;
343 case "DHE_RSA_WITH_AES_128_CBC_SHA256":
344 return SSL.DHE_RSA_WITH_AES_128_CBC_SHA256;
345 case "DH_DSS_WITH_AES_256_CBC_SHA256":
346 return SSL.DH_DSS_WITH_AES_256_CBC_SHA256;
347 case "DH_RSA_WITH_AES_256_CBC_SHA256":
348 return SSL.DH_RSA_WITH_AES_256_CBC_SHA256;
349 case "DHE_DSS_WITH_AES_256_CBC_SHA256":
350 return SSL.DHE_DSS_WITH_AES_256_CBC_SHA256;
351 case "DHE_RSA_WITH_AES_256_CBC_SHA256":
352 return SSL.DHE_RSA_WITH_AES_256_CBC_SHA256;
353 case "DH_anon_WITH_RC4_128_MD5":
354 return SSL.DH_anon_WITH_RC4_128_MD5;
355 case "DH_anon_WITH_3DES_EDE_CBC_SHA":
356 return SSL.DH_anon_WITH_3DES_EDE_CBC_SHA;
357 case "DH_anon_WITH_AES_128_CBC_SHA":
358 return SSL.DH_anon_WITH_AES_128_CBC_SHA;
359 case "DH_anon_WITH_AES_256_CBC_SHA":
360 return SSL.DH_anon_WITH_AES_256_CBC_SHA;
361 case "DH_anon_WITH_AES_128_CBC_SHA256":
362 return SSL.DH_anon_WITH_AES_128_CBC_SHA256;
363 case "DH_anon_WITH_AES_256_CBC_SHA256":
364 return SSL.DH_anon_WITH_AES_256_CBC_SHA256;
365 case "ECDH_ECDSA_WITH_NULL_SHA":
366 return SSL.ECDH_ECDSA_WITH_NULL_SHA;
367 case "ECDH_ECDSA_WITH_RC4_128_SHA":
368 return SSL.ECDH_ECDSA_WITH_RC4_128_SHA;
369 case "ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA":
370 return SSL.ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA;
371 case "ECDH_ECDSA_WITH_AES_128_CBC_SHA":
372 return SSL.ECDH_ECDSA_WITH_AES_128_CBC_SHA;
373 case "ECDH_ECDSA_WITH_AES_256_CBC_SHA":
374 return SSL.ECDH_ECDSA_WITH_AES_256_CBC_SHA;
375 case "ECDHE_ECDSA_WITH_NULL_SHA":
376 return SSL.ECDHE_ECDSA_WITH_NULL_SHA;
377 case "ECDHE_ECDSA_WITH_RC4_128_SHA":
378 return SSL.ECDHE_ECDSA_WITH_RC4_128_SHA;
379 case "ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA":
380 return SSL.ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA;
381 case "ECDHE_ECDSA_WITH_AES_128_CBC_SHA":
382 return SSL.ECDHE_ECDSA_WITH_AES_128_CBC_SHA;
383 case "ECDHE_ECDSA_WITH_AES_256_CBC_SHA":
384 return SSL.ECDHE_ECDSA_WITH_AES_256_CBC_SHA;
385 case "ECDH_RSA_WITH_NULL_SHA":
386 return SSL.ECDH_RSA_WITH_NULL_SHA;
387 case "ECDH_RSA_WITH_RC4_128_SHA":
388 return SSL.ECDH_RSA_WITH_RC4_128_SHA;
389 case "ECDH_RSA_WITH_3DES_EDE_CBC_SHA":
390 return SSL.ECDH_RSA_WITH_3DES_EDE_CBC_SHA;
391 case "ECDH_RSA_WITH_AES_128_CBC_SHA":
392 return SSL.ECDH_RSA_WITH_AES_128_CBC_SHA;
393 case "ECDH_RSA_WITH_AES_256_CBC_SHA":
394 return SSL.ECDH_RSA_WITH_AES_256_CBC_SHA;
395 case "ECDHE_RSA_WITH_NULL_SHA":
396 return SSL.ECDHE_RSA_WITH_NULL_SHA;
397 case "ECDHE_RSA_WITH_RC4_128_SHA":
398 return SSL.ECDHE_RSA_WITH_RC4_128_SHA;
399 case "ECDHE_RSA_WITH_3DES_EDE_CBC_SHA":
400 return SSL.ECDHE_RSA_WITH_3DES_EDE_CBC_SHA;
401 case "ECDHE_RSA_WITH_AES_128_CBC_SHA":
402 return SSL.ECDHE_RSA_WITH_AES_128_CBC_SHA;
403 case "ECDHE_RSA_WITH_AES_256_CBC_SHA":
404 return SSL.ECDHE_RSA_WITH_AES_256_CBC_SHA;
405 case "ECDH_anon_WITH_NULL_SHA":
406 return SSL.ECDH_anon_WITH_NULL_SHA;
407 case "ECDH_anon_WITH_RC4_128_SHA":
408 return SSL.ECDH_anon_WITH_RC4_128_SHA;
409 case "ECDH_anon_WITH_3DES_EDE_CBC_SHA":
410 return SSL.ECDH_anon_WITH_3DES_EDE_CBC_SHA;
411 case "ECDH_anon_WITH_AES_128_CBC_SHA":
412 return SSL.ECDH_anon_WITH_AES_128_CBC_SHA;
413 case "ECDH_anon_WITH_AES_256_CBC_SHA":
414 return SSL.ECDH_anon_WITH_AES_256_CBC_SHA;
415 case "RSA_WITH_AES_128_GCM_SHA256":
416 return SSL.RSA_WITH_AES_128_GCM_SHA256;
417 case "RSA_WITH_AES_256_GCM_SHA384":
418 return SSL.RSA_WITH_AES_256_GCM_SHA384;
419 case "DHE_RSA_WITH_AES_128_GCM_SHA256":
420 return SSL.DHE_RSA_WITH_AES_128_GCM_SHA256;
421 case "DHE_RSA_WITH_AES_256_GCM_SHA384":
422 return SSL.DHE_RSA_WITH_AES_256_GCM_SHA384;
423 case "DH_RSA_WITH_AES_128_GCM_SHA256":
424 return SSL.DH_RSA_WITH_AES_128_GCM_SHA256;
425 case "DH_RSA_WITH_AES_256_GCM_SHA384":
426 return SSL.DH_RSA_WITH_AES_256_GCM_SHA384;
427 case "DHE_DSS_WITH_AES_128_GCM_SHA256":
428 return SSL.DHE_DSS_WITH_AES_128_GCM_SHA256;
429 case "DHE_DSS_WITH_AES_256_GCM_SHA384":
430 return SSL.DHE_DSS_WITH_AES_256_GCM_SHA384;
431 case "DH_DSS_WITH_AES_128_GCM_SHA256":
432 return SSL.DH_DSS_WITH_AES_128_GCM_SHA256;
433 case "DH_DSS_WITH_AES_256_GCM_SHA384":
434 return SSL.DH_DSS_WITH_AES_256_GCM_SHA384;
435 case "DH_anon_WITH_AES_128_GCM_SHA256":
436 return SSL.DH_anon_WITH_AES_128_GCM_SHA256;
437 case "DH_anon_WITH_AES_256_GCM_SHA384":
438 return SSL.DH_anon_WITH_AES_256_GCM_SHA384;
439 case "ECDHE_ECDSA_WITH_AES_128_CBC_SHA256":
440 return SSL.ECDHE_ECDSA_WITH_AES_128_CBC_SHA256;
441 case "ECDHE_ECDSA_WITH_AES_256_CBC_SHA384":
442 return SSL.ECDHE_ECDSA_WITH_AES_256_CBC_SHA384;
443 case "ECDH_ECDSA_WITH_AES_128_CBC_SHA256":
444 return SSL.ECDH_ECDSA_WITH_AES_128_CBC_SHA256;
445 case "ECDH_ECDSA_WITH_AES_256_CBC_SHA384":
446 return SSL.ECDH_ECDSA_WITH_AES_256_CBC_SHA384;
447 case "ECDHE_RSA_WITH_AES_128_CBC_SHA256":
448 return SSL.ECDHE_RSA_WITH_AES_128_CBC_SHA256;
449 case "ECDHE_RSA_WITH_AES_256_CBC_SHA384":
450 return SSL.ECDHE_RSA_WITH_AES_256_CBC_SHA384;
451 case "ECDH_RSA_WITH_AES_128_CBC_SHA256":
452 return SSL.ECDH_RSA_WITH_AES_128_CBC_SHA256;
453 case "ECDH_RSA_WITH_AES_256_CBC_SHA384":
454 return SSL.ECDH_RSA_WITH_AES_256_CBC_SHA384;
455 case "ECDHE_ECDSA_WITH_AES_128_GCM_SHA256":
456 return SSL.ECDHE_ECDSA_WITH_AES_128_GCM_SHA256;
457 case "ECDHE_ECDSA_WITH_AES_256_GCM_SHA384":
458 return SSL.ECDHE_ECDSA_WITH_AES_256_GCM_SHA384;
459 case "ECDH_ECDSA_WITH_AES_128_GCM_SHA256":
460 return SSL.ECDH_ECDSA_WITH_AES_128_GCM_SHA256;
461 case "ECDH_ECDSA_WITH_AES_256_GCM_SHA384":
462 return SSL.ECDH_ECDSA_WITH_AES_256_GCM_SHA384;
463 case "ECDHE_RSA_WITH_AES_128_GCM_SHA256":
464 return SSL.ECDHE_RSA_WITH_AES_128_GCM_SHA256;
465 case "ECDHE_RSA_WITH_AES_256_GCM_SHA384":
466 return SSL.ECDHE_RSA_WITH_AES_256_GCM_SHA384;
467 case "ECDH_RSA_WITH_AES_128_GCM_SHA256":
468 return SSL.ECDH_RSA_WITH_AES_128_GCM_SHA256;
469 case "ECDH_RSA_WITH_AES_256_GCM_SHA384":
470 return SSL.ECDH_RSA_WITH_AES_256_GCM_SHA384;
471 case "ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256":
472 return SSL.ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256;
473 case "ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256":
474 return SSL.ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256;
475 case "DHE_RSA_WITH_CHACHA20_POLY1305_SHA256":
476 return SSL.DHE_RSA_WITH_CHACHA20_POLY1305_SHA256;
477 case "PSK_WITH_CHACHA20_POLY1305_SHA256":
478 return SSL.PSK_WITH_CHACHA20_POLY1305_SHA256;
479 case "ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256":
480 return SSL.ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256;
481 case "DHE_PSK_WITH_CHACHA20_POLY1305_SHA256":
482 return SSL.DHE_PSK_WITH_CHACHA20_POLY1305_SHA256;
483 case "RSA_PSK_WITH_CHACHA20_POLY1305_SHA256":
484 return SSL.RSA_PSK_WITH_CHACHA20_POLY1305_SHA256;
486 throw new Exception(string.Format(
487 "Unknown cipher suite: '{0}'", s));
492 int[] GetHashAndSigns()
494 return GetHashAndSignsByName(
495 JSON.GetStringArray(conf, "hashAndSigns"));
498 internal static int[] GetHashAndSignsByName(string[] ss)
500 int[] r = new int[ss.Length];
501 for (int i = 0; i < ss.Length; i ++) {
502 r[i] = SSL.GetHashAndSignByName(ss[i]);
508 internal static int GetHashAndSignByName(string s)
511 case "RSA_MD5": return SSL.RSA_MD5;
512 case "RSA_SHA1": return SSL.RSA_SHA1;
513 case "RSA_SHA224": return SSL.RSA_SHA224;
514 case "RSA_SHA256": return SSL.RSA_SHA256;
515 case "RSA_SHA384": return SSL.RSA_SHA384;
516 case "RSA_SHA512": return SSL.RSA_SHA512;
517 case "ECDSA_MD5": return SSL.ECDSA_MD5;
518 case "ECDSA_SHA1": return SSL.ECDSA_SHA1;
519 case "ECDSA_SHA224": return SSL.ECDSA_SHA224;
520 case "ECDSA_SHA256": return SSL.ECDSA_SHA256;
521 case "ECDSA_SHA384": return SSL.ECDSA_SHA384;
522 case "ECDSA_SHA512": return SSL.ECDSA_SHA512;
524 throw new Exception(string.Format(
525 "Unknown hash-and-sign: '{0}'", s));
532 return GetCurvesByName(JSON.GetStringArray(conf, "curves"));
535 internal static int[] GetCurvesByName(string[] ss)
537 int[] r = new int[ss.Length];
538 for (int i = 0; i < ss.Length; i ++) {
539 r[i] = SSL.GetCurveByName(ss[i]);
545 internal static int GetCurveByName(string s)
548 case "Curve25519": return SSL.Curve25519;
549 case "NIST_P256": return SSL.NIST_P256;
550 case "NIST_P384": return SSL.NIST_P384;
551 case "NIST_P521": return SSL.NIST_P521;
553 throw new Exception(string.Format(
554 "Unknown curve: '{0}'", s));
560 * RunEnum() builds and runs synthetic tests that exercise all
561 * combinations of protocol version, cipher suites, curves
562 * and hash-and-sign. Curves and hash-and-sign are enumerated
563 * only for ECDHE suites.
568 RunEnum(false, true);
571 int RunEnum(bool cmdClient, bool doit)
574 foreach (int version in versions) {
575 foreach (int suite in cipherSuites) {
576 if (version < SSL.TLS12 && SSL.IsTLS12(suite)) {
579 if (!SSL.IsECDHE(suite)) {
582 version, suite, -1, -1);
587 bool needRSA = (version >= SSL.TLS12)
588 && SSL.IsECDHE_RSA(suite);
589 bool needECDSA = (version >= SSL.TLS12)
590 && SSL.IsECDHE_ECDSA(suite);
591 foreach (int hs in hashAndSigns) {
593 if (needRSA && sa != SSL.RSA) {
596 if (needECDSA && sa != SSL.ECDSA) {
599 foreach (int curve in curves) {
614 int ComputeTotalEnum()
616 return RunEnum(true, false) + RunEnum(false, false);
619 static string TEnumToString(
620 int version, int suite, int curve, int hs)
622 StringBuilder sb = new StringBuilder();
623 sb.AppendFormat("enum_{0:X4}_{1:X4}", version, suite);
624 if (curve >= 0 && hs >= 0) {
625 sb.AppendFormat("_{0}_{1}", curve, hs);
627 return sb.ToString();
630 static bool StringToTEnum(string s,
632 out int version, out int suite,
633 out int curve, out int hs)
641 if (!s.StartsWith("enum_")) {
645 if (s.EndsWith("_client")) {
647 } else if (s.EndsWith("_server")) {
652 s = s.Substring(0, s.Length - 7);
653 string[] ww = s.Split('_');
654 if (ww.Length != 2 && ww.Length != 4) {
657 version = ParseHex4(ww[0]);
658 suite = ParseHex4(ww[1]);
659 if (ww.Length == 2) {
660 return version >= 0 && suite >= 0;
662 curve = ParseDec(ww[2]);
663 hs = ParseDec(ww[3]);
664 return version >= 0 && suite >= 0
665 && curve >= 0 && hs >= 0;
669 static int HexVal(int cp)
671 if (cp >= '0' && cp <= '9') {
673 } else if (cp >= 'A' && cp <= 'F') {
674 return cp - ('A' - 10);
675 } else if (cp >= 'a' && cp <= 'f') {
676 return cp - ('a' - 10);
682 static int ParseHex4(string s)
690 static int ParseHex(string s)
693 for (int i = 0; i < 4; i ++) {
694 int v = HexVal(s[i]);
698 acc = (acc << 4) + v;
703 static int ParseDec(string s)
707 for (int i = 0; i < n; i ++) {
708 int v = HexVal(s[i]);
709 if (v < 0 || v >= 10) {
712 acc = (acc * 10) + v;
717 void RunEnum(bool cmdClient, int version, int suite, int curve, int hs)
719 IDictionary<string, object> d =
720 new SortedDictionary<string, object>(
721 StringComparer.Ordinal);
722 d["name"] = TEnumToString(version, suite, curve, hs);
723 d["versionMin"] = SSL.VersionName(version);
724 d["versionMax"] = SSL.VersionName(version);
725 d["cipherSuites"] = new string[] {
726 SSL.CipherSuiteName(suite)
729 d["curves"] = new string[] {
732 d["hashAndSigns"] = new string[] {
733 SSL.HashAndSignName(hs)
736 d["curves"] = new string[0];
737 d["hashAndSigns"] = new string[0];
739 if (SSL.IsRSA(suite) || SSL.IsECDHE_RSA(suite)) {
740 d["serverCertType"] = "RSA";
742 if (SSL.IsECDH(suite) || SSL.IsECDHE_ECDSA(suite)) {
743 d["serverCertType"] = "EC";
745 RunTest(cmdClient, d);
749 * Get certificate type for the provided test (client-side or
750 * server-side certificate, depending on 'client').
752 * If the test does not contain an explicit indication, then
753 * the certificate type will be "none" for a client, "RSA" for
756 string GetCertType(object obj, bool client)
758 string name = client ? "clientCertType" : "serverCertType";
760 if (JSON.TryGetString(obj, name, out ct)) {
763 return client ? "none" : "RSA";
766 int GetNumTests(object obj)
770 if (!JSON.TryGetBool(obj, "serverOnly", out v) || !v) {
773 if (!JSON.TryGetBool(obj, "clientOnly", out v) || !v) {
779 void RunTest(object obj)
782 if (!JSON.TryGetBool(obj, "serverOnly", out v) || !v) {
785 if (!JSON.TryGetBool(obj, "clientOnly", out v) || !v) {
790 void RunTest(bool cmdClient, object obj)
792 string name = JSON.GetString(obj, "name")
793 + (cmdClient ? "_client" : "_server");
794 Console.Write("\r({0}/{1})",
795 totalSuccess + totalFailures + 1, totalTests);
796 // Console.Write("{0}:", name);
799 * Expected command exit code:
801 * 0 if the command is supposed to exit gracefully
802 * 1 if the command should detect and report an error
804 int expectedExitCode;
805 JSON.TryGetInt32(obj, "expectedExitCode", out expectedExitCode);
808 * Expected failure: if defined, then we expect our
809 * library to throw an exception, and the message should
810 * contain that specific string.
812 string expectedFailure;
813 JSON.TryGetString(obj, "expectedFailure", out expectedFailure);
816 * Assemble the sub-process command line:
818 * - Always one of "-client" or "-server"
819 * - For a server command, a certificate and key are
820 * always provided (defaults to RSA); for a client,
821 * only if explicitly asked for.
823 StringBuilder sb = new StringBuilder();
825 sb.Append("-client");
827 sb.Append("-server");
829 if (commandVerbose) {
832 string certType = GetCertType(obj, cmdClient);
835 sb.AppendFormat(" -cert \"{0}\" -key \"{1}\"",
836 chainRSAFile, skeyRSAFile);
839 sb.AppendFormat(" -cert \"{0}\" -key \"{1}\"",
840 chainECFile, skeyECFile);
845 throw new Exception("Unknown certType: " + certType);
848 if (JSON.TryGetString(obj, "extraArgs", out extra)) {
854 * Run the sub-process.
856 ProcessStartInfo si = new ProcessStartInfo();
857 si.FileName = commandFile;
858 si.Arguments = string.Format(commandArgs, sb.ToString());
859 si.UseShellExecute = false;
860 si.ErrorDialog = false;
861 si.CreateNoWindow = true;
862 si.RedirectStandardInput = true;
863 si.RedirectStandardOutput = true;
865 using (Process pp = new Process()) {
868 Exception delayed = null;
871 * TODO: add a time-out on the streams
872 * so that the test never stalls
873 * indefinitely if the two SSL engines
874 * lose synchronisation.
876 MergeStream ms = new MergeStream(
877 pp.StandardOutput.BaseStream,
878 pp.StandardInput.BaseStream);
880 ms.Debug = Console.Out;
882 RunTestInner(cmdClient, obj, ms);
883 } catch (Exception ex) {
888 * Once the test has run, we must make sure that
889 * the sub-processed is finished. It _should_ end
890 * properly by itself for all successful test cases,
891 * so if we have to kill it, then it's a bug.
894 if (!pp.WaitForExit(2000)) {
903 int exc = pp.ExitCode;
906 * If we had to kill the command, then that is
907 * always a bug. Otherwise, we compare what we
908 * got with the expected outcomes.
910 List<string> msg = new List<string>();
912 msg.Add("COMMAND KILLED");
914 if (exc != expectedExitCode) {
915 msg.Add("Wrong exit code: "
916 + exc + " (expected: "
917 + expectedExitCode + ")");
919 if (delayed == null) {
920 if (expectedFailure != null) {
921 msg.Add("An exception was expected");
924 if (expectedFailure == null) {
925 msg.Add(delayed.ToString());
927 string s = delayed.Message;
931 if (s.IndexOf(expectedFailure) < 0) {
932 msg.Add(delayed.ToString());
936 if (msg.Count == 0) {
939 Console.WriteLine("{0}: FAIL:", name);
940 foreach (string s in msg) {
941 Console.WriteLine(s);
948 void RunTestInner(bool cmdClient, object obj, Stream peer)
951 * Create the SSL engine, and configure it as specified
952 * in the configuration object (with the default
953 * configuration as fallback).
957 byte[][] chain = null;
958 IPrivateKey skey = null;
959 string certType = GetCertType(obj, !cmdClient);
972 throw new Exception("Unknown certType: " + certType);
975 IServerPolicy spol = new SSLServerPolicyBasic(
976 chain, skey, KeyUsage.EncryptAndSign);
977 SSLServer ss = new SSLServer(peer, spol);
978 ss.SessionCache = new SSLSessionCacheLRU(20);
981 SSLClient sc = new SSLClient(peer);
982 sc.ServerCertValidator =
983 SSLClient.InsecureCertValidator;
986 eng.NormalizeIOError = true;
987 eng.AutoFlush = false;
993 if (JSON.TryGetString(obj, "versionMin", out svmin)) {
994 eng.VersionMin = SSL.GetVersionByName(svmin);
996 eng.VersionMin = versionMin;
1003 if (JSON.TryGetString(obj, "versionMax", out svmax)) {
1004 eng.VersionMax = SSL.GetVersionByName(svmax);
1006 eng.VersionMax = versionMax;
1010 * Supported cipher suites.
1013 if (JSON.TryGetStringArray(obj, "cipherSuites", out sccs)) {
1014 eng.SupportedCipherSuites = GetSuitesByName(sccs);
1016 eng.SupportedCipherSuites = cipherSuites;
1020 * Supported hash-and-sign algorithms.
1023 if (JSON.TryGetStringArray(obj, "hashAndSigns", out shss)) {
1024 eng.SupportedHashAndSign = GetHashAndSignsByName(shss);
1026 eng.SupportedHashAndSign = hashAndSigns;
1030 * Supported elliptic curves.
1033 if (JSON.TryGetStringArray(obj, "curves", out secc)) {
1034 eng.SupportedCurves = GetCurvesByName(secc);
1036 eng.SupportedCurves = curves;
1040 * What to do when there is no close_notify.
1043 if (JSON.TryGetBool(obj, "noCloseNotify", out ncn)) {
1044 eng.NoCloseNotify = ncn;
1046 eng.NoCloseNotify = noCloseNotify;
1052 IDictionary<string, object> qm;
1053 if (JSON.TryGetObjectMap(obj, "quirks", out qm)) {
1054 SSLQuirks q = new SSLQuirks();
1055 foreach (string name in qm.Keys) {
1056 q[name] = JSON.GetString(qm, name);
1062 JSON.TryGetBool(obj, "askClose", out askClose);
1063 bool renegotiate, renegotiateAccepted;
1064 renegotiate = JSON.TryGetBool(obj, "renegotiate",
1065 out renegotiateAccepted);
1066 bool askRenegotiate, askRenegotiateAccepted;
1067 askRenegotiate = JSON.TryGetBool(obj, "askRenegotiate",
1068 out askRenegotiateAccepted);
1070 bool reconnectSelf = false, reconnectPeer = false;
1072 if (JSON.TryGetString(obj, "reconnect", out rcs)) {
1074 case "self": reconnectSelf = true; break;
1075 case "peer": reconnectPeer = true; break;
1077 throw new Exception("Unknown 'reconnect' type: "
1082 bool forgetSelf = false, forgetPeer = false;
1084 if (JSON.TryGetString(obj, "forget", out fgs)) {
1086 case "self": forgetSelf = true; break;
1087 case "peer": forgetPeer = true; break;
1089 throw new Exception("Unknown 'forget' type: "
1095 SendCommand(eng, 'C');
1096 if (eng.ReadByte() != -1) {
1097 throw new Exception("Peer did not close");
1099 } else if (renegotiate) {
1100 SendMessageNormal(eng, 10);
1101 if (eng.Renegotiate()) {
1102 if (!renegotiateAccepted) {
1103 throw new Exception("Renegotiation"
1104 + " should have been rejected");
1107 if (renegotiateAccepted) {
1108 throw new Exception("Renegotiation"
1109 + " should have been accepted");
1112 SendMessageNormal(eng, 9);
1113 } else if (askRenegotiate) {
1114 SendMessageNormal(eng, 10);
1115 long rc = eng.HandshakeCount;
1116 SendCommand(eng, 'G');
1117 string s = ReadLine(eng);
1120 if (askRenegotiateAccepted) {
1121 throw new Exception("Renegotiation"
1122 + " should have been accepted");
1126 if (!askRenegotiateAccepted) {
1127 throw new Exception("Renegotiation"
1128 + " should have been rejected");
1130 long nrc = eng.HandshakeCount;
1131 if (nrc != rc + 1) {
1132 throw new Exception(string.Format(
1133 "Wrong handshake count"
1134 + " (old={0}, new={1})",
1139 throw new Exception(string.Format(
1140 "Unexpected answer string '{0}'", s));
1142 SendMessageNormal(eng, 8);
1143 } else if (reconnectSelf || reconnectPeer) {
1144 SendMessageNormal(eng, 50);
1145 SendMessageNormal(eng, 100);
1147 SendCommand(eng, 'U');
1148 string s = ReadLine(eng);
1150 throw new Exception(string.Format(
1151 "Unexpected answer '{0}'", s));
1154 eng.CloseSub = false;
1155 if (reconnectPeer) {
1156 SendCommand(eng, 'T');
1157 if (eng.ReadByte() != -1) {
1158 throw new Exception(
1159 "Peer did not close");
1162 SendCommand(eng, 'R');
1163 string s = ReadLine(eng);
1165 throw new Exception(string.Format(
1166 "Unexpected answer '{0}'", s));
1172 IServerPolicy spol = new SSLServerPolicyBasic(
1173 chain, skey, KeyUsage.EncryptAndSign);
1174 SSLServer ss = new SSLServer(peer, spol);
1177 new SSLSessionCacheLRU(20);
1180 ((SSLServer)eng).SessionCache;
1184 SSLSessionParameters sp;
1188 sp = eng.SessionParameters;
1190 SSLClient sc = new SSLClient(peer, sp);
1191 sc.ServerCertValidator =
1192 SSLClient.InsecureCertValidator;
1195 eng2.NormalizeIOError = eng.NormalizeIOError;
1196 eng2.AutoFlush = eng.AutoFlush;
1197 eng2.VersionMin = eng.VersionMin;
1198 eng2.VersionMax = eng.VersionMax;
1199 eng2.SupportedCipherSuites = eng.SupportedCipherSuites;
1200 eng2.SupportedHashAndSign = eng.SupportedHashAndSign;
1201 eng2.SupportedCurves = eng.SupportedCurves;
1202 eng2.NoCloseNotify = eng.NoCloseNotify;
1203 eng2.Quirks = eng.Quirks;
1205 SendMessageNormal(eng, 60);
1206 SendMessageNormal(eng, 90);
1207 if (forgetSelf || forgetPeer) {
1209 throw new Exception(
1210 "Session was resumed");
1213 if (!eng.IsResume) {
1214 throw new Exception(
1215 "Session was not resumed");
1219 for (int i = 0; i <= 38; i ++) {
1224 len = 20 + (1 << (i - 20));
1226 SendMessageNormal(eng, len);
1234 * Send a "normal" message to the peer, of the specified
1235 * length: this is a sequence of 'len' random bytes, distinct
1236 * from 0x0A, followed one 0x0A byte. The peer is supposed to
1237 * respond with the SHA-1 hash of the message bytes (excluding
1238 * the final 0x0A), encoded in hexadecimal (lowercase) and
1239 * followed by a newline (0x0A). An exception is thrown if the
1240 * expected value is not obtained.
1242 void SendMessageNormal(SSLEngine eng, int len)
1244 SHA1 sha1 = new SHA1();
1245 byte[] buf = new byte[len + 1];
1246 RNG.GetBytesNonZero(buf, 0, len);
1247 for (int i = 0; i < len; i ++) {
1252 buf[0] = (byte)('a' + (buf[0] & 0x0F));
1254 StringBuilder sb = new StringBuilder();
1255 foreach (byte b in sha1.Hash(buf, 0, len)) {
1256 sb.AppendFormat("{0:x2}", b);
1259 eng.Write(buf, 0, buf.Length);
1261 for (int i = 0; i < sb.Length; i ++) {
1262 int x = eng.ReadByte();
1265 throw new Exception(string.Format(
1266 "received {0} (exp: {1})", y, x));
1271 void SendCommand(SSLEngine eng, char cmd)
1273 eng.WriteByte((byte)cmd);
1274 eng.WriteByte(0x0A);
1278 string ReadLine(SSLEngine eng)
1280 StringBuilder sb = new StringBuilder();
1282 int c = eng.ReadByte();
1284 throw new Exception("Unexpected EOF");
1287 return sb.ToString();
1293 static byte[][] DecodeChain(string fname)
1295 byte[] buf = File.ReadAllBytes(fname);
1296 PEMObject[] fpo = AsnIO.DecodePEM(buf);
1297 if (fpo.Length == 0) {
1298 buf = AsnIO.FindBER(buf);
1300 throw new Exception(string.Format(
1301 "No certificate in file '{0}'", fname));
1303 return new byte[][] { buf };
1305 List<byte[]> r = new List<byte[]>();
1306 foreach (PEMObject po in fpo) {
1307 string tt = po.type.ToUpperInvariant();
1308 if (tt == "CERTIFICATE" || tt == "X509 CERTIFICATE") {
1313 throw new Exception(string.Format(
1314 "No certificate in file '{0}'", fname));
1319 static IPrivateKey DecodePrivateKey(string fname)
1321 byte[] buf = File.ReadAllBytes(fname);
1322 PEMObject[] fpo = AsnIO.DecodePEM(buf);
1323 if (fpo.Length == 0) {
1324 buf = AsnIO.FindBER(buf);
1327 foreach (PEMObject po in fpo) {
1328 string tt = po.type.ToUpperInvariant();
1329 if (tt.IndexOf("PRIVATE KEY") >= 0) {
1331 throw new Exception(
1332 "Multiple keys in '"
1340 throw new Exception(string.Format(
1341 "No private key in file '{0}'", fname));
1343 return KF.DecodePrivateKey(buf);