Added command-line client (for debug only).
[BoarSSL] / Twrch / Twrch.cs
1 /*
2 * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
3 *
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:
11 *
12 * The above copyright notice and this permission notice shall be
13 * included in all copies or substantial portions of the Software.
14 *
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
22 * SOFTWARE.
23 */
24
25 using System;
26 using System.Collections.Generic;
27 using System.Diagnostics;
28 using System.IO;
29 using System.Text;
30
31 using Asn1;
32 using Crypto;
33 using IO;
34 using SSLTLS;
35 using XKeys;
36
37 /*
38 * This is the main Twrch class implementation: it provides the entry
39 * point for the command-line application.
40 */
41
42 public class Twrch {
43
44 public static void Main(string[] args)
45 {
46 try {
47 new Twrch().Run(args);
48 } catch (Exception e) {
49 Console.WriteLine(e.ToString());
50 Environment.Exit(1);
51 }
52 }
53
54 bool trace;
55 object conf;
56 string commandFile;
57 string commandArgs;
58 bool commandVerbose;
59 string chainRSAFile;
60 byte[][] chainRSA;
61 string skeyRSAFile;
62 IPrivateKey skeyRSA;
63 string chainECFile;
64 byte[][] chainEC;
65 string skeyECFile;
66 IPrivateKey skeyEC;
67 int[] versions;
68 int versionMin;
69 int versionMax;
70 int[] cipherSuites;
71 int[] hashAndSigns;
72 int[] curves;
73 bool noCloseNotify;
74 object[] tests;
75 IDictionary<string, object> testsByName;
76
77 int totalTests;
78 int totalSuccess;
79 int totalFailures;
80
81 void Run(string[] args)
82 {
83 List<string> r = new List<string>();
84 string confName = null;
85 int doEnum = 0;
86 foreach (string a in args) {
87 string b = a.ToLowerInvariant();
88 switch (b) {
89 case "-trace":
90 trace = true;
91 break;
92 case "-enum":
93 doEnum = 1;
94 break;
95 case "-noenum":
96 doEnum = -1;
97 break;
98 case "-cv":
99 commandVerbose = true;
100 break;
101 default:
102 if (confName == null) {
103 confName = a;
104 } else {
105 r.Add(a);
106 }
107 break;
108 }
109 }
110 if (confName == null) {
111 Usage();
112 }
113 string[] testNames = r.ToArray();
114 conf = ReadConfig(confName);
115 if (doEnum == 0) {
116 doEnum = (testNames.Length == 0) ? 1 : -1;
117 }
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");
131 }
132 versionMin = Int32.MaxValue;
133 versionMax = -1;
134 foreach (int v in versions) {
135 versionMin = Math.Min(v, versionMin);
136 versionMax = Math.Max(v, versionMax);
137 }
138 cipherSuites = GetCipherSuites();
139 if (cipherSuites.Length == 0) {
140 throw new Exception("Bad config: no cipher suites");
141 }
142 hashAndSigns = GetHashAndSigns();
143 if (hashAndSigns.Length == 0) {
144 throw new Exception("Bad config: no hash-and-signs");
145 }
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;
154 }
155
156 totalTests = 0;
157 totalSuccess = 0;
158 totalFailures = 0;
159 if (doEnum > 0) {
160 totalTests += ComputeTotalEnum();
161 }
162 if (testNames.Length == 0) {
163 foreach (object obj in tests) {
164 totalTests += GetNumTests(obj);
165 }
166 } else {
167 foreach (string name in testNames) {
168 bool client;
169 int version, suite, curve, hs;
170 if (StringToTEnum(name, out client, out version,
171 out suite, out curve, out hs))
172 {
173 totalTests ++;
174 continue;
175 }
176 if (name.EndsWith("_client")
177 || name.EndsWith("_server"))
178 {
179 totalTests ++;
180 } else {
181 totalTests += GetNumTests(
182 testsByName[name]);
183 }
184 }
185 }
186
187 if (doEnum > 0) {
188 RunEnum();
189 }
190 if (testNames.Length == 0) {
191 foreach (object obj in tests) {
192 RunTest(obj);
193 }
194 } else {
195 foreach (string name in testNames) {
196 bool client;
197 int version, suite, curve, hs;
198 if (StringToTEnum(name, out client, out version,
199 out suite, out curve, out hs))
200 {
201 RunEnum(client, version,
202 suite, curve, hs);
203 continue;
204 }
205 if (name.EndsWith("_client")) {
206 client = true;
207 } else if (name.EndsWith("_server")) {
208 client = false;
209 } else {
210 RunTest(testsByName[name]);
211 continue;
212 }
213 string s = name.Substring(0, name.Length - 7);
214 RunTest(client, testsByName[s]);
215 }
216 }
217
218 Console.WriteLine();
219 Console.WriteLine("\rtotal = {0}, failed = {1}",
220 totalTests, totalFailures);
221 }
222
223 static void Usage()
224 {
225 Console.WriteLine(
226 "usage: Twrch.exe [ options ] config [ test... ]");
227 Console.WriteLine(
228 "options:");
229 Console.WriteLine(
230 " -trace enable trace mode (hex dump of all exchanged bytes)");
231 Console.WriteLine(
232 " -cv pass the '-v' argument to the test command");
233 Console.WriteLine(
234 " -enum perform all version/suite/curve/hash&sign combination tests");
235 Console.WriteLine(
236 " -noenum do NOT perform the version/suite/curve/hash&sign tests");
237 Environment.Exit(1);
238 }
239
240 static object ReadConfig(string fname)
241 {
242 using (TextReader r = File.OpenText(fname)) {
243 return JSON.Parse(r);
244 }
245 }
246
247 int[] GetVersions()
248 {
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]);
253 }
254 return vv;
255 }
256
257 /* obsolete
258 internal static int GetVersionByName(string s)
259 {
260 s = s.Replace(" ", "").Replace(".", "").ToUpperInvariant();
261 switch (s) {
262 case "TLS10": return SSL.TLS10;
263 case "TLS11": return SSL.TLS11;
264 case "TLS12": return SSL.TLS12;
265 default:
266 throw new Exception(string.Format(
267 "Unknown version: '{0}'", s));
268 }
269 }
270 */
271
272 int[] GetCipherSuites()
273 {
274 return GetSuitesByName(
275 JSON.GetStringArray(conf, "cipherSuites"));
276 }
277
278 internal static int[] GetSuitesByName(string[] ss)
279 {
280 int[] r = new int[ss.Length];
281 for (int i = 0; i < ss.Length; i ++) {
282 r[i] = SSL.GetSuiteByName(ss[i]);
283 }
284 return r;
285 }
286
287 /* obsolete
288 internal static int GetSuiteByName(string s)
289 {
290 switch (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;
485 default:
486 throw new Exception(string.Format(
487 "Unknown cipher suite: '{0}'", s));
488 }
489 }
490 */
491
492 int[] GetHashAndSigns()
493 {
494 return GetHashAndSignsByName(
495 JSON.GetStringArray(conf, "hashAndSigns"));
496 }
497
498 internal static int[] GetHashAndSignsByName(string[] ss)
499 {
500 int[] r = new int[ss.Length];
501 for (int i = 0; i < ss.Length; i ++) {
502 r[i] = SSL.GetHashAndSignByName(ss[i]);
503 }
504 return r;
505 }
506
507 /* obsolete
508 internal static int GetHashAndSignByName(string s)
509 {
510 switch (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;
523 default:
524 throw new Exception(string.Format(
525 "Unknown hash-and-sign: '{0}'", s));
526 }
527 }
528 */
529
530 int[] GetCurves()
531 {
532 return GetCurvesByName(JSON.GetStringArray(conf, "curves"));
533 }
534
535 internal static int[] GetCurvesByName(string[] ss)
536 {
537 int[] r = new int[ss.Length];
538 for (int i = 0; i < ss.Length; i ++) {
539 r[i] = SSL.GetCurveByName(ss[i]);
540 }
541 return r;
542 }
543
544 /* obsolete
545 internal static int GetCurveByName(string s)
546 {
547 switch (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;
552 default:
553 throw new Exception(string.Format(
554 "Unknown curve: '{0}'", s));
555 }
556 }
557 */
558
559 /*
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.
564 */
565 void RunEnum()
566 {
567 RunEnum(true, true);
568 RunEnum(false, true);
569 }
570
571 int RunEnum(bool cmdClient, bool doit)
572 {
573 int count = 0;
574 foreach (int version in versions) {
575 foreach (int suite in cipherSuites) {
576 if (version < SSL.TLS12 && SSL.IsTLS12(suite)) {
577 continue;
578 }
579 if (!SSL.IsECDHE(suite)) {
580 if (doit) {
581 RunEnum(cmdClient,
582 version, suite, -1, -1);
583 }
584 count ++;
585 continue;
586 }
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) {
592 int sa = hs & 0xFF;
593 if (needRSA && sa != SSL.RSA) {
594 continue;
595 }
596 if (needECDSA && sa != SSL.ECDSA) {
597 continue;
598 }
599 foreach (int curve in curves) {
600 if (doit) {
601 RunEnum(cmdClient,
602 version,
603 suite,
604 curve, hs);
605 }
606 count ++;
607 }
608 }
609 }
610 }
611 return count;
612 }
613
614 int ComputeTotalEnum()
615 {
616 return RunEnum(true, false) + RunEnum(false, false);
617 }
618
619 static string TEnumToString(
620 int version, int suite, int curve, int hs)
621 {
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);
626 }
627 return sb.ToString();
628 }
629
630 static bool StringToTEnum(string s,
631 out bool cmdClient,
632 out int version, out int suite,
633 out int curve, out int hs)
634 {
635 cmdClient = false;
636 version = -1;
637 suite = -1;
638 curve = -1;
639 hs = -1;
640 s = s.Trim();
641 if (!s.StartsWith("enum_")) {
642 return false;
643 }
644 s = s.Substring(5);
645 if (s.EndsWith("_client")) {
646 cmdClient = true;
647 } else if (s.EndsWith("_server")) {
648 cmdClient = false;
649 } else {
650 return false;
651 }
652 s = s.Substring(0, s.Length - 7);
653 string[] ww = s.Split('_');
654 if (ww.Length != 2 && ww.Length != 4) {
655 return false;
656 }
657 version = ParseHex4(ww[0]);
658 suite = ParseHex4(ww[1]);
659 if (ww.Length == 2) {
660 return version >= 0 && suite >= 0;
661 } else {
662 curve = ParseDec(ww[2]);
663 hs = ParseDec(ww[3]);
664 return version >= 0 && suite >= 0
665 && curve >= 0 && hs >= 0;
666 }
667 }
668
669 static int HexVal(int cp)
670 {
671 if (cp >= '0' && cp <= '9') {
672 return cp - '0';
673 } else if (cp >= 'A' && cp <= 'F') {
674 return cp - ('A' - 10);
675 } else if (cp >= 'a' && cp <= 'f') {
676 return cp - ('a' - 10);
677 } else {
678 return -1;
679 }
680 }
681
682 static int ParseHex4(string s)
683 {
684 if (s.Length != 4) {
685 return -1;
686 }
687 return ParseHex(s);
688 }
689
690 static int ParseHex(string s)
691 {
692 int acc = 0;
693 for (int i = 0; i < 4; i ++) {
694 int v = HexVal(s[i]);
695 if (v < 0) {
696 return -1;
697 }
698 acc = (acc << 4) + v;
699 }
700 return acc;
701 }
702
703 static int ParseDec(string s)
704 {
705 int n = s.Length;
706 int acc = 0;
707 for (int i = 0; i < n; i ++) {
708 int v = HexVal(s[i]);
709 if (v < 0 || v >= 10) {
710 return -1;
711 }
712 acc = (acc * 10) + v;
713 }
714 return acc;
715 }
716
717 void RunEnum(bool cmdClient, int version, int suite, int curve, int hs)
718 {
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)
727 };
728 if (curve >= 0) {
729 d["curves"] = new string[] {
730 SSL.CurveName(curve)
731 };
732 d["hashAndSigns"] = new string[] {
733 SSL.HashAndSignName(hs)
734 };
735 } else {
736 d["curves"] = new string[0];
737 d["hashAndSigns"] = new string[0];
738 }
739 if (SSL.IsRSA(suite) || SSL.IsECDHE_RSA(suite)) {
740 d["serverCertType"] = "RSA";
741 }
742 if (SSL.IsECDH(suite) || SSL.IsECDHE_ECDSA(suite)) {
743 d["serverCertType"] = "EC";
744 }
745 RunTest(cmdClient, d);
746 }
747
748 /*
749 * Get certificate type for the provided test (client-side or
750 * server-side certificate, depending on 'client').
751 *
752 * If the test does not contain an explicit indication, then
753 * the certificate type will be "none" for a client, "RSA" for
754 * a server.
755 */
756 string GetCertType(object obj, bool client)
757 {
758 string name = client ? "clientCertType" : "serverCertType";
759 string ct;
760 if (JSON.TryGetString(obj, name, out ct)) {
761 return ct;
762 }
763 return client ? "none" : "RSA";
764 }
765
766 int GetNumTests(object obj)
767 {
768 int num = 0;
769 bool v;
770 if (!JSON.TryGetBool(obj, "serverOnly", out v) || !v) {
771 num ++;
772 }
773 if (!JSON.TryGetBool(obj, "clientOnly", out v) || !v) {
774 num ++;
775 }
776 return num;
777 }
778
779 void RunTest(object obj)
780 {
781 bool v;
782 if (!JSON.TryGetBool(obj, "serverOnly", out v) || !v) {
783 RunTest(true, obj);
784 }
785 if (!JSON.TryGetBool(obj, "clientOnly", out v) || !v) {
786 RunTest(false, obj);
787 }
788 }
789
790 void RunTest(bool cmdClient, object obj)
791 {
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);
797
798 /*
799 * Expected command exit code:
800 *
801 * 0 if the command is supposed to exit gracefully
802 * 1 if the command should detect and report an error
803 */
804 int expectedExitCode;
805 JSON.TryGetInt32(obj, "expectedExitCode", out expectedExitCode);
806
807 /*
808 * Expected failure: if defined, then we expect our
809 * library to throw an exception, and the message should
810 * contain that specific string.
811 */
812 string expectedFailure;
813 JSON.TryGetString(obj, "expectedFailure", out expectedFailure);
814
815 /*
816 * Assemble the sub-process command line:
817 *
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.
822 */
823 StringBuilder sb = new StringBuilder();
824 if (cmdClient) {
825 sb.Append("-client");
826 } else {
827 sb.Append("-server");
828 }
829 if (commandVerbose) {
830 sb.Append(" -v");
831 }
832 string certType = GetCertType(obj, cmdClient);
833 switch (certType) {
834 case "RSA":
835 sb.AppendFormat(" -cert \"{0}\" -key \"{1}\"",
836 chainRSAFile, skeyRSAFile);
837 break;
838 case "EC":
839 sb.AppendFormat(" -cert \"{0}\" -key \"{1}\"",
840 chainECFile, skeyECFile);
841 break;
842 case "none":
843 break;
844 default:
845 throw new Exception("Unknown certType: " + certType);
846 }
847 string extra;
848 if (JSON.TryGetString(obj, "extraArgs", out extra)) {
849 sb.Append(' ');
850 sb.Append(extra);
851 }
852
853 /*
854 * Run the sub-process.
855 */
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;
864
865 using (Process pp = new Process()) {
866 pp.StartInfo = si;
867 pp.Start();
868 Exception delayed = null;
869 try {
870 /*
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.
875 */
876 MergeStream ms = new MergeStream(
877 pp.StandardOutput.BaseStream,
878 pp.StandardInput.BaseStream);
879 if (trace) {
880 ms.Debug = Console.Out;
881 }
882 RunTestInner(cmdClient, obj, ms);
883 } catch (Exception ex) {
884 delayed = ex;
885 }
886
887 /*
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.
892 */
893 bool killed = false;
894 if (!pp.WaitForExit(2000)) {
895 try {
896 pp.Kill();
897 } catch {
898 // ignored
899 }
900 pp.WaitForExit();
901 killed = true;
902 }
903 int exc = pp.ExitCode;
904
905 /*
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.
909 */
910 List<string> msg = new List<string>();
911 if (killed) {
912 msg.Add("COMMAND KILLED");
913 }
914 if (exc != expectedExitCode) {
915 msg.Add("Wrong exit code: "
916 + exc + " (expected: "
917 + expectedExitCode + ")");
918 }
919 if (delayed == null) {
920 if (expectedFailure != null) {
921 msg.Add("An exception was expected");
922 }
923 } else {
924 if (expectedFailure == null) {
925 msg.Add(delayed.ToString());
926 } else {
927 string s = delayed.Message;
928 if (s == null) {
929 s = "";
930 }
931 if (s.IndexOf(expectedFailure) < 0) {
932 msg.Add(delayed.ToString());
933 }
934 }
935 }
936 if (msg.Count == 0) {
937 totalSuccess ++;
938 } else {
939 Console.WriteLine("{0}: FAIL:", name);
940 foreach (string s in msg) {
941 Console.WriteLine(s);
942 }
943 totalFailures ++;
944 }
945 }
946 }
947
948 void RunTestInner(bool cmdClient, object obj, Stream peer)
949 {
950 /*
951 * Create the SSL engine, and configure it as specified
952 * in the configuration object (with the default
953 * configuration as fallback).
954 */
955
956 SSLEngine eng;
957 byte[][] chain = null;
958 IPrivateKey skey = null;
959 string certType = GetCertType(obj, !cmdClient);
960 switch (certType) {
961 case "RSA":
962 chain = chainRSA;
963 skey = skeyRSA;
964 break;
965 case "EC":
966 chain = chainEC;
967 skey = skeyEC;
968 break;
969 case "none":
970 break;
971 default:
972 throw new Exception("Unknown certType: " + certType);
973 }
974 if (cmdClient) {
975 IServerPolicy spol = new SSLServerPolicyBasic(
976 chain, skey, KeyUsage.EncryptAndSign);
977 SSLServer ss = new SSLServer(peer, spol);
978 ss.SessionCache = new SSLSessionCacheLRU(20);
979 eng = ss;
980 } else {
981 SSLClient sc = new SSLClient(peer);
982 sc.ServerCertValidator =
983 SSLClient.InsecureCertValidator;
984 eng = sc;
985 }
986 eng.NormalizeIOError = true;
987 eng.AutoFlush = false;
988
989 /*
990 * Minimum version.
991 */
992 string svmin;
993 if (JSON.TryGetString(obj, "versionMin", out svmin)) {
994 eng.VersionMin = SSL.GetVersionByName(svmin);
995 } else {
996 eng.VersionMin = versionMin;
997 }
998
999 /*
1000 * Maximum version.
1001 */
1002 string svmax;
1003 if (JSON.TryGetString(obj, "versionMax", out svmax)) {
1004 eng.VersionMax = SSL.GetVersionByName(svmax);
1005 } else {
1006 eng.VersionMax = versionMax;
1007 }
1008
1009 /*
1010 * Supported cipher suites.
1011 */
1012 string[] sccs;
1013 if (JSON.TryGetStringArray(obj, "cipherSuites", out sccs)) {
1014 eng.SupportedCipherSuites = GetSuitesByName(sccs);
1015 } else {
1016 eng.SupportedCipherSuites = cipherSuites;
1017 }
1018
1019 /*
1020 * Supported hash-and-sign algorithms.
1021 */
1022 string[] shss;
1023 if (JSON.TryGetStringArray(obj, "hashAndSigns", out shss)) {
1024 eng.SupportedHashAndSign = GetHashAndSignsByName(shss);
1025 } else {
1026 eng.SupportedHashAndSign = hashAndSigns;
1027 }
1028
1029 /*
1030 * Supported elliptic curves.
1031 */
1032 string[] secc;
1033 if (JSON.TryGetStringArray(obj, "curves", out secc)) {
1034 eng.SupportedCurves = GetCurvesByName(secc);
1035 } else {
1036 eng.SupportedCurves = curves;
1037 }
1038
1039 /*
1040 * What to do when there is no close_notify.
1041 */
1042 bool ncn;
1043 if (JSON.TryGetBool(obj, "noCloseNotify", out ncn)) {
1044 eng.NoCloseNotify = ncn;
1045 } else {
1046 eng.NoCloseNotify = noCloseNotify;
1047 }
1048
1049 /*
1050 * Quirks.
1051 */
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);
1057 }
1058 eng.Quirks = q;
1059 }
1060
1061 bool askClose;
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);
1069
1070 bool reconnectSelf = false, reconnectPeer = false;
1071 string rcs;
1072 if (JSON.TryGetString(obj, "reconnect", out rcs)) {
1073 switch (rcs) {
1074 case "self": reconnectSelf = true; break;
1075 case "peer": reconnectPeer = true; break;
1076 default:
1077 throw new Exception("Unknown 'reconnect' type: "
1078 + rcs);
1079 }
1080 }
1081
1082 bool forgetSelf = false, forgetPeer = false;
1083 string fgs;
1084 if (JSON.TryGetString(obj, "forget", out fgs)) {
1085 switch (fgs) {
1086 case "self": forgetSelf = true; break;
1087 case "peer": forgetPeer = true; break;
1088 default:
1089 throw new Exception("Unknown 'forget' type: "
1090 + fgs);
1091 }
1092 }
1093
1094 if (askClose) {
1095 SendCommand(eng, 'C');
1096 if (eng.ReadByte() != -1) {
1097 throw new Exception("Peer did not close");
1098 }
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");
1105 }
1106 } else {
1107 if (renegotiateAccepted) {
1108 throw new Exception("Renegotiation"
1109 + " should have been accepted");
1110 }
1111 }
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);
1118 switch (s) {
1119 case "DENIED":
1120 if (askRenegotiateAccepted) {
1121 throw new Exception("Renegotiation"
1122 + " should have been accepted");
1123 }
1124 break;
1125 case "OK":
1126 if (!askRenegotiateAccepted) {
1127 throw new Exception("Renegotiation"
1128 + " should have been rejected");
1129 }
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})",
1135 rc, nrc));
1136 }
1137 break;
1138 default:
1139 throw new Exception(string.Format(
1140 "Unexpected answer string '{0}'", s));
1141 }
1142 SendMessageNormal(eng, 8);
1143 } else if (reconnectSelf || reconnectPeer) {
1144 SendMessageNormal(eng, 50);
1145 SendMessageNormal(eng, 100);
1146 if (forgetPeer) {
1147 SendCommand(eng, 'U');
1148 string s = ReadLine(eng);
1149 if (s != "DONE") {
1150 throw new Exception(string.Format(
1151 "Unexpected answer '{0}'", s));
1152 }
1153 }
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");
1160 }
1161 } else {
1162 SendCommand(eng, 'R');
1163 string s = ReadLine(eng);
1164 if (s != "OK") {
1165 throw new Exception(string.Format(
1166 "Unexpected answer '{0}'", s));
1167 }
1168 eng.Close();
1169 }
1170 SSLEngine eng2;
1171 if (cmdClient) {
1172 IServerPolicy spol = new SSLServerPolicyBasic(
1173 chain, skey, KeyUsage.EncryptAndSign);
1174 SSLServer ss = new SSLServer(peer, spol);
1175 if (forgetSelf) {
1176 ss.SessionCache =
1177 new SSLSessionCacheLRU(20);
1178 } else {
1179 ss.SessionCache =
1180 ((SSLServer)eng).SessionCache;
1181 }
1182 eng2 = ss;
1183 } else {
1184 SSLSessionParameters sp;
1185 if (forgetSelf) {
1186 sp = null;
1187 } else {
1188 sp = eng.SessionParameters;
1189 }
1190 SSLClient sc = new SSLClient(peer, sp);
1191 sc.ServerCertValidator =
1192 SSLClient.InsecureCertValidator;
1193 eng2 = sc;
1194 }
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;
1204 eng = eng2;
1205 SendMessageNormal(eng, 60);
1206 SendMessageNormal(eng, 90);
1207 if (forgetSelf || forgetPeer) {
1208 if (eng.IsResume) {
1209 throw new Exception(
1210 "Session was resumed");
1211 }
1212 } else {
1213 if (!eng.IsResume) {
1214 throw new Exception(
1215 "Session was not resumed");
1216 }
1217 }
1218 } else {
1219 for (int i = 0; i <= 38; i ++) {
1220 int len;
1221 if (i <= 20) {
1222 len = i;
1223 } else {
1224 len = 20 + (1 << (i - 20));
1225 }
1226 SendMessageNormal(eng, len);
1227 }
1228 }
1229
1230 eng.Close();
1231 }
1232
1233 /*
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.
1241 */
1242 void SendMessageNormal(SSLEngine eng, int len)
1243 {
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 ++) {
1248 buf[i] ^= 0x0A;
1249 }
1250 buf[len] = 0x0A;
1251 if (len == 1) {
1252 buf[0] = (byte)('a' + (buf[0] & 0x0F));
1253 }
1254 StringBuilder sb = new StringBuilder();
1255 foreach (byte b in sha1.Hash(buf, 0, len)) {
1256 sb.AppendFormat("{0:x2}", b);
1257 }
1258 sb.Append('\n');
1259 eng.Write(buf, 0, buf.Length);
1260 eng.Flush();
1261 for (int i = 0; i < sb.Length; i ++) {
1262 int x = eng.ReadByte();
1263 int y = sb[i];
1264 if (x != y) {
1265 throw new Exception(string.Format(
1266 "received {0} (exp: {1})", y, x));
1267 }
1268 }
1269 }
1270
1271 void SendCommand(SSLEngine eng, char cmd)
1272 {
1273 eng.WriteByte((byte)cmd);
1274 eng.WriteByte(0x0A);
1275 eng.Flush();
1276 }
1277
1278 string ReadLine(SSLEngine eng)
1279 {
1280 StringBuilder sb = new StringBuilder();
1281 for (;;) {
1282 int c = eng.ReadByte();
1283 if (c < 0) {
1284 throw new Exception("Unexpected EOF");
1285 }
1286 if (c == 0x0A) {
1287 return sb.ToString();
1288 }
1289 sb.Append((char)c);
1290 }
1291 }
1292
1293 static byte[][] DecodeChain(string fname)
1294 {
1295 byte[] buf = File.ReadAllBytes(fname);
1296 PEMObject[] fpo = AsnIO.DecodePEM(buf);
1297 if (fpo.Length == 0) {
1298 buf = AsnIO.FindBER(buf);
1299 if (buf == null) {
1300 throw new Exception(string.Format(
1301 "No certificate in file '{0}'", fname));
1302 }
1303 return new byte[][] { buf };
1304 }
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") {
1309 r.Add(po.data);
1310 }
1311 }
1312 if (r.Count == 0) {
1313 throw new Exception(string.Format(
1314 "No certificate in file '{0}'", fname));
1315 }
1316 return r.ToArray();
1317 }
1318
1319 static IPrivateKey DecodePrivateKey(string fname)
1320 {
1321 byte[] buf = File.ReadAllBytes(fname);
1322 PEMObject[] fpo = AsnIO.DecodePEM(buf);
1323 if (fpo.Length == 0) {
1324 buf = AsnIO.FindBER(buf);
1325 } else {
1326 buf = null;
1327 foreach (PEMObject po in fpo) {
1328 string tt = po.type.ToUpperInvariant();
1329 if (tt.IndexOf("PRIVATE KEY") >= 0) {
1330 if (buf != null) {
1331 throw new Exception(
1332 "Multiple keys in '"
1333 + fname + "'");
1334 }
1335 buf = po.data;
1336 }
1337 }
1338 }
1339 if (buf == null) {
1340 throw new Exception(string.Format(
1341 "No private key in file '{0}'", fname));
1342 }
1343 return KF.DecodePrivateKey(buf);
1344 }
1345 }