1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "config.h"
7
8 #include <algorithm>
9 #include <cstdlib>
10 #include <iostream>
11 #include <memory>
12 #include "nspr.h"
13 #include "nss.h"
14 #include "prio.h"
15 #include "prnetdb.h"
16 #include "secerr.h"
17 #include "ssl.h"
18 #include "ssl3prot.h"
19 #include "sslerr.h"
20 #include "sslproto.h"
21 #include "nss_scoped_ptrs.h"
22
23 #include "nsskeys.h"
24
25 static const char* kVersionDisableFlags[] = {"no-ssl3", "no-tls1", "no-tls11",
26 "no-tls12", "no-tls13"};
27
28 bool exitCodeUnimplemented = false;
29
FormatError(PRErrorCode code)30 std::string FormatError(PRErrorCode code) {
31 return std::string(":") + PORT_ErrorToName(code) + ":" + ":" +
32 PORT_ErrorToString(code);
33 }
34
35 class TestAgent {
36 public:
TestAgent(const Config & cfg)37 TestAgent(const Config& cfg) : cfg_(cfg) {}
38
~TestAgent()39 ~TestAgent() {}
40
Create(const Config & cfg)41 static std::unique_ptr<TestAgent> Create(const Config& cfg) {
42 std::unique_ptr<TestAgent> agent(new TestAgent(cfg));
43
44 if (!agent->Init()) return nullptr;
45
46 return agent;
47 }
48
Init()49 bool Init() {
50 if (!ConnectTcp()) {
51 return false;
52 }
53
54 if (!SetupKeys()) {
55 std::cerr << "Couldn't set up keys/certs\n";
56 return false;
57 }
58
59 if (!SetupOptions()) {
60 std::cerr << "Couldn't configure socket\n";
61 return false;
62 }
63
64 SECStatus rv = SSL_ResetHandshake(ssl_fd_.get(), cfg_.get<bool>("server"));
65 if (rv != SECSuccess) return false;
66
67 return true;
68 }
69
ConnectTcp()70 bool ConnectTcp() {
71 // Try IPv6 first, then IPv4 in case of failure.
72 if (!OpenConnection("::1") && !OpenConnection("127.0.0.1")) {
73 return false;
74 }
75
76 ssl_fd_ = ScopedPRFileDesc(SSL_ImportFD(NULL, pr_fd_.get()));
77 if (!ssl_fd_) {
78 return false;
79 }
80 pr_fd_.release();
81
82 return true;
83 }
84
OpenConnection(const char * ip)85 bool OpenConnection(const char* ip) {
86 PRStatus prv;
87 PRNetAddr addr;
88
89 prv = PR_StringToNetAddr(ip, &addr);
90
91 if (prv != PR_SUCCESS) {
92 return false;
93 }
94
95 addr.inet.port = PR_htons(cfg_.get<int>("port"));
96
97 pr_fd_ = ScopedPRFileDesc(PR_OpenTCPSocket(addr.raw.family));
98 if (!pr_fd_) return false;
99
100 prv = PR_Connect(pr_fd_.get(), &addr, PR_INTERVAL_NO_TIMEOUT);
101 if (prv != PR_SUCCESS) {
102 return false;
103 }
104 return true;
105 }
106
SetupKeys()107 bool SetupKeys() {
108 SECStatus rv;
109
110 if (cfg_.get<std::string>("key-file") != "") {
111 key_ = ScopedSECKEYPrivateKey(
112 ReadPrivateKey(cfg_.get<std::string>("key-file")));
113 if (!key_) return false;
114 }
115 if (cfg_.get<std::string>("cert-file") != "") {
116 cert_ = ScopedCERTCertificate(
117 ReadCertificate(cfg_.get<std::string>("cert-file")));
118 if (!cert_) return false;
119 }
120
121 // Needed because certs are not entirely valid.
122 rv = SSL_AuthCertificateHook(ssl_fd_.get(), AuthCertificateHook, this);
123 if (rv != SECSuccess) return false;
124
125 if (cfg_.get<bool>("server")) {
126 // Server
127 rv = SSL_ConfigServerCert(ssl_fd_.get(), cert_.get(), key_.get(), nullptr,
128 0);
129 if (rv != SECSuccess) {
130 std::cerr << "Couldn't configure server cert\n";
131 return false;
132 }
133
134 } else if (key_ && cert_) {
135 // Client.
136 rv =
137 SSL_GetClientAuthDataHook(ssl_fd_.get(), GetClientAuthDataHook, this);
138 if (rv != SECSuccess) return false;
139 }
140
141 return true;
142 }
143
ConvertFromWireVersion(SSLProtocolVariant variant,int wire_version,uint16_t * lib_version)144 static bool ConvertFromWireVersion(SSLProtocolVariant variant,
145 int wire_version, uint16_t* lib_version) {
146 // These default values are used when {min,max}-version isn't given.
147 if (wire_version == 0 || wire_version == 0xffff) {
148 *lib_version = static_cast<uint16_t>(wire_version);
149 return true;
150 }
151
152 #ifdef TLS_1_3_DRAFT_VERSION
153 if (wire_version == (0x7f00 | TLS_1_3_DRAFT_VERSION)) {
154 // N.B. SSL_LIBRARY_VERSION_DTLS_1_3_WIRE == SSL_LIBRARY_VERSION_TLS_1_3
155 wire_version = SSL_LIBRARY_VERSION_TLS_1_3;
156 }
157 #endif
158
159 if (variant == ssl_variant_datagram) {
160 switch (wire_version) {
161 case SSL_LIBRARY_VERSION_DTLS_1_0_WIRE:
162 *lib_version = SSL_LIBRARY_VERSION_DTLS_1_0;
163 break;
164 case SSL_LIBRARY_VERSION_DTLS_1_2_WIRE:
165 *lib_version = SSL_LIBRARY_VERSION_DTLS_1_2;
166 break;
167 case SSL_LIBRARY_VERSION_DTLS_1_3_WIRE:
168 *lib_version = SSL_LIBRARY_VERSION_DTLS_1_3;
169 break;
170 default:
171 std::cerr << "Unrecognized DTLS version " << wire_version << ".\n";
172 return false;
173 }
174 } else {
175 if (wire_version < SSL_LIBRARY_VERSION_3_0 ||
176 wire_version > SSL_LIBRARY_VERSION_TLS_1_3) {
177 std::cerr << "Unrecognized TLS version " << wire_version << ".\n";
178 return false;
179 }
180 *lib_version = static_cast<uint16_t>(wire_version);
181 }
182 return true;
183 }
184
GetVersionRange(SSLVersionRange * range_out,SSLProtocolVariant variant)185 bool GetVersionRange(SSLVersionRange* range_out, SSLProtocolVariant variant) {
186 SSLVersionRange supported;
187 if (SSL_VersionRangeGetSupported(variant, &supported) != SECSuccess) {
188 return false;
189 }
190
191 uint16_t min_allowed;
192 uint16_t max_allowed;
193 if (!ConvertFromWireVersion(variant, cfg_.get<int>("min-version"),
194 &min_allowed)) {
195 return false;
196 }
197 if (!ConvertFromWireVersion(variant, cfg_.get<int>("max-version"),
198 &max_allowed)) {
199 return false;
200 }
201
202 min_allowed = std::max(min_allowed, supported.min);
203 max_allowed = std::min(max_allowed, supported.max);
204
205 bool found_min = false;
206 bool found_max = false;
207 // Ignore -no-ssl3, because SSLv3 is never supported.
208 for (size_t i = 1; i < PR_ARRAY_SIZE(kVersionDisableFlags); ++i) {
209 auto version =
210 static_cast<uint16_t>(SSL_LIBRARY_VERSION_TLS_1_0 + (i - 1));
211 if (variant == ssl_variant_datagram) {
212 // In DTLS mode, the -no-tlsN flags refer to DTLS versions,
213 // but NSS wants the corresponding TLS versions.
214 if (version == SSL_LIBRARY_VERSION_TLS_1_1) {
215 // DTLS 1.1 doesn't exist.
216 continue;
217 }
218 if (version == SSL_LIBRARY_VERSION_TLS_1_0) {
219 version = SSL_LIBRARY_VERSION_DTLS_1_0;
220 }
221 }
222
223 if (version < min_allowed) {
224 continue;
225 }
226 if (version > max_allowed) {
227 break;
228 }
229
230 const bool allowed = !cfg_.get<bool>(kVersionDisableFlags[i]);
231
232 if (!found_min && allowed) {
233 found_min = true;
234 range_out->min = version;
235 }
236 if (found_min && !found_max) {
237 if (allowed) {
238 range_out->max = version;
239 } else {
240 found_max = true;
241 }
242 }
243 if (found_max && allowed) {
244 std::cerr << "Discontiguous version range.\n";
245 return false;
246 }
247 }
248
249 if (!found_min) {
250 std::cerr << "All versions disabled.\n";
251 }
252 return found_min;
253 }
254
SetupOptions()255 bool SetupOptions() {
256 SECStatus rv =
257 SSL_OptionSet(ssl_fd_.get(), SSL_ENABLE_TLS13_COMPAT_MODE, PR_TRUE);
258 if (rv != SECSuccess) return false;
259
260 rv = SSL_OptionSet(ssl_fd_.get(), SSL_ENABLE_SESSION_TICKETS, PR_TRUE);
261 if (rv != SECSuccess) return false;
262
263 SSLVersionRange vrange;
264 if (!GetVersionRange(&vrange, ssl_variant_stream)) return false;
265
266 rv = SSL_VersionRangeSet(ssl_fd_.get(), &vrange);
267 if (rv != SECSuccess) return false;
268
269 SSLVersionRange verify_vrange;
270 rv = SSL_VersionRangeGet(ssl_fd_.get(), &verify_vrange);
271 if (rv != SECSuccess) return false;
272 if (vrange.min != verify_vrange.min || vrange.max != verify_vrange.max)
273 return false;
274
275 rv = SSL_OptionSet(ssl_fd_.get(), SSL_NO_CACHE, false);
276 if (rv != SECSuccess) return false;
277
278 auto alpn = cfg_.get<std::string>("advertise-alpn");
279 if (!alpn.empty()) {
280 assert(!cfg_.get<bool>("server"));
281
282 rv = SSL_OptionSet(ssl_fd_.get(), SSL_ENABLE_ALPN, PR_TRUE);
283 if (rv != SECSuccess) return false;
284
285 rv = SSL_SetNextProtoNego(
286 ssl_fd_.get(), reinterpret_cast<const unsigned char*>(alpn.c_str()),
287 alpn.size());
288 if (rv != SECSuccess) return false;
289 }
290
291 // Set supported signature schemes.
292 auto sign_prefs = cfg_.get<std::vector<int>>("signing-prefs");
293 auto verify_prefs = cfg_.get<std::vector<int>>("verify-prefs");
294 if (sign_prefs.empty()) {
295 sign_prefs = verify_prefs;
296 } else if (!verify_prefs.empty()) {
297 return false; // Both shouldn't be set.
298 }
299 if (!sign_prefs.empty()) {
300 std::vector<SSLSignatureScheme> sig_schemes;
301 std::transform(
302 sign_prefs.begin(), sign_prefs.end(), std::back_inserter(sig_schemes),
303 [](int scheme) { return static_cast<SSLSignatureScheme>(scheme); });
304
305 rv = SSL_SignatureSchemePrefSet(
306 ssl_fd_.get(), sig_schemes.data(),
307 static_cast<unsigned int>(sig_schemes.size()));
308 if (rv != SECSuccess) return false;
309 }
310
311 if (cfg_.get<bool>("fallback-scsv")) {
312 rv = SSL_OptionSet(ssl_fd_.get(), SSL_ENABLE_FALLBACK_SCSV, PR_TRUE);
313 if (rv != SECSuccess) return false;
314 }
315
316 if (cfg_.get<bool>("false-start")) {
317 rv = SSL_OptionSet(ssl_fd_.get(), SSL_ENABLE_FALSE_START, PR_TRUE);
318 if (rv != SECSuccess) return false;
319 }
320
321 if (cfg_.get<bool>("enable-ocsp-stapling")) {
322 rv = SSL_OptionSet(ssl_fd_.get(), SSL_ENABLE_OCSP_STAPLING, PR_TRUE);
323 if (rv != SECSuccess) return false;
324 }
325
326 bool requireClientCert = cfg_.get<bool>("require-any-client-certificate");
327 if (requireClientCert || cfg_.get<bool>("verify-peer")) {
328 assert(cfg_.get<bool>("server"));
329
330 rv = SSL_OptionSet(ssl_fd_.get(), SSL_REQUEST_CERTIFICATE, PR_TRUE);
331 if (rv != SECSuccess) return false;
332
333 rv = SSL_OptionSet(
334 ssl_fd_.get(), SSL_REQUIRE_CERTIFICATE,
335 requireClientCert ? SSL_REQUIRE_ALWAYS : SSL_REQUIRE_NO_ERROR);
336 if (rv != SECSuccess) return false;
337 }
338
339 if (!cfg_.get<bool>("server")) {
340 // Needed to make resumption work.
341 rv = SSL_SetURL(ssl_fd_.get(), "server");
342 if (rv != SECSuccess) return false;
343 }
344
345 rv = SSL_OptionSet(ssl_fd_.get(), SSL_ENABLE_EXTENDED_MASTER_SECRET,
346 PR_TRUE);
347 if (rv != SECSuccess) return false;
348
349 if (!ConfigureCiphers()) return false;
350
351 return true;
352 }
353
ConfigureCiphers()354 bool ConfigureCiphers() {
355 auto cipherList = cfg_.get<std::string>("nss-cipher");
356
357 if (cipherList.empty()) {
358 return EnableNonExportCiphers();
359 }
360
361 for (size_t i = 0; i < SSL_NumImplementedCiphers; ++i) {
362 SSLCipherSuiteInfo csinfo;
363 std::string::size_type n;
364 SECStatus rv = SSL_GetCipherSuiteInfo(SSL_ImplementedCiphers[i], &csinfo,
365 sizeof(csinfo));
366 if (rv != SECSuccess) {
367 return false;
368 }
369
370 // Check if cipherList contains the name of the Cipher Suite and
371 // enable/disable accordingly.
372 n = cipherList.find(csinfo.cipherSuiteName, 0);
373 if (std::string::npos == n) {
374 rv = SSL_CipherPrefSet(ssl_fd_.get(), SSL_ImplementedCiphers[i],
375 PR_FALSE);
376 } else {
377 rv = SSL_CipherPrefSet(ssl_fd_.get(), SSL_ImplementedCiphers[i],
378 PR_TRUE);
379 }
380 if (rv != SECSuccess) {
381 return false;
382 }
383 }
384 return true;
385 }
386
EnableNonExportCiphers()387 bool EnableNonExportCiphers() {
388 for (size_t i = 0; i < SSL_NumImplementedCiphers; ++i) {
389 SSLCipherSuiteInfo csinfo;
390
391 SECStatus rv = SSL_GetCipherSuiteInfo(SSL_ImplementedCiphers[i], &csinfo,
392 sizeof(csinfo));
393 if (rv != SECSuccess) {
394 return false;
395 }
396
397 rv = SSL_CipherPrefSet(ssl_fd_.get(), SSL_ImplementedCiphers[i], PR_TRUE);
398 if (rv != SECSuccess) {
399 return false;
400 }
401 }
402 return true;
403 }
404
405 // Dummy auth certificate hook.
AuthCertificateHook(void * arg,PRFileDesc * fd,PRBool checksig,PRBool isServer)406 static SECStatus AuthCertificateHook(void* arg, PRFileDesc* fd,
407 PRBool checksig, PRBool isServer) {
408 return SECSuccess;
409 }
410
GetClientAuthDataHook(void * self,PRFileDesc * fd,CERTDistNames * caNames,CERTCertificate ** cert,SECKEYPrivateKey ** privKey)411 static SECStatus GetClientAuthDataHook(void* self, PRFileDesc* fd,
412 CERTDistNames* caNames,
413 CERTCertificate** cert,
414 SECKEYPrivateKey** privKey) {
415 TestAgent* a = static_cast<TestAgent*>(self);
416 *cert = CERT_DupCertificate(a->cert_.get());
417 *privKey = SECKEY_CopyPrivateKey(a->key_.get());
418 return SECSuccess;
419 }
420
Handshake()421 SECStatus Handshake() { return SSL_ForceHandshake(ssl_fd_.get()); }
422
423 // Implement a trivial echo client/server. Read bytes from the other side,
424 // flip all the bits, and send them back.
ReadWrite()425 SECStatus ReadWrite() {
426 for (;;) {
427 uint8_t block[512];
428 int32_t rv = PR_Read(ssl_fd_.get(), block, sizeof(block));
429 if (rv < 0) {
430 std::cerr << "Failure reading\n";
431 return SECFailure;
432 }
433 if (rv == 0) return SECSuccess;
434
435 int32_t len = rv;
436 for (int32_t i = 0; i < len; ++i) {
437 block[i] ^= 0xff;
438 }
439
440 rv = PR_Write(ssl_fd_.get(), block, len);
441 if (rv != len) {
442 std::cerr << "Write failure\n";
443 PORT_SetError(SEC_ERROR_OUTPUT_LEN);
444 return SECFailure;
445 }
446 }
447 return SECSuccess;
448 }
449
450 // Write bytes to the other side then read them back and check
451 // that they were correctly XORed as in ReadWrite.
WriteRead()452 SECStatus WriteRead() {
453 static const uint8_t ch = 'E';
454
455 // We do 600-byte blocks to provide mis-alignment of the
456 // reader and writer.
457 uint8_t block[600];
458 memset(block, ch, sizeof(block));
459 int32_t rv = PR_Write(ssl_fd_.get(), block, sizeof(block));
460 if (rv != sizeof(block)) {
461 std::cerr << "Write failure\n";
462 PORT_SetError(SEC_ERROR_OUTPUT_LEN);
463 return SECFailure;
464 }
465
466 size_t left = sizeof(block);
467 while (left) {
468 rv = PR_Read(ssl_fd_.get(), block, left);
469 if (rv < 0) {
470 std::cerr << "Failure reading\n";
471 return SECFailure;
472 }
473 if (rv == 0) {
474 PORT_SetError(SEC_ERROR_INPUT_LEN);
475 return SECFailure;
476 }
477
478 int32_t len = rv;
479 for (int32_t i = 0; i < len; ++i) {
480 if (block[i] != (ch ^ 0xff)) {
481 PORT_SetError(SEC_ERROR_BAD_DATA);
482 return SECFailure;
483 }
484 }
485 left -= len;
486 }
487 return SECSuccess;
488 }
489
DoExchange()490 SECStatus DoExchange() {
491 SECStatus rv = Handshake();
492 if (rv != SECSuccess) {
493 PRErrorCode err = PR_GetError();
494 std::cerr << "Handshake failed with error=" << err << FormatError(err)
495 << std::endl;
496 return SECFailure;
497 }
498
499 if (cfg_.get<bool>("write-then-read")) {
500 rv = WriteRead();
501 if (rv != SECSuccess) {
502 PRErrorCode err = PR_GetError();
503 std::cerr << "WriteRead failed with error=" << FormatError(err)
504 << std::endl;
505 return SECFailure;
506 }
507 } else {
508 rv = ReadWrite();
509 if (rv != SECSuccess) {
510 PRErrorCode err = PR_GetError();
511 std::cerr << "ReadWrite failed with error=" << FormatError(err)
512 << std::endl;
513 return SECFailure;
514 }
515 }
516
517 auto alpn = cfg_.get<std::string>("expect-alpn");
518 if (!alpn.empty()) {
519 SSLNextProtoState state;
520 char chosen[256];
521 unsigned int chosen_len;
522 rv = SSL_GetNextProto(ssl_fd_.get(), &state,
523 reinterpret_cast<unsigned char*>(chosen),
524 &chosen_len, sizeof(chosen));
525 if (rv != SECSuccess) {
526 PRErrorCode err = PR_GetError();
527 std::cerr << "SSL_GetNextProto failed with error=" << FormatError(err)
528 << std::endl;
529 return SECFailure;
530 }
531
532 assert(chosen_len <= sizeof(chosen));
533 if (std::string(chosen, chosen_len) != alpn) {
534 std::cerr << "Unexpected ALPN selection" << std::endl;
535 return SECFailure;
536 }
537 }
538
539 auto sig_alg = cfg_.get<int>("expect-peer-signature-algorithm");
540 if (sig_alg) {
541 SSLChannelInfo info;
542 rv = SSL_GetChannelInfo(ssl_fd_.get(), &info, sizeof(info));
543 if (rv != SECSuccess) {
544 PRErrorCode err = PR_GetError();
545 std::cerr << "SSL_GetChannelInfo failed with error=" << FormatError(err)
546 << std::endl;
547 return SECFailure;
548 }
549
550 auto expected = static_cast<SSLSignatureScheme>(sig_alg);
551 if (info.signatureScheme != expected) {
552 std::cerr << "Unexpected signature scheme" << std::endl;
553 return SECFailure;
554 }
555 }
556
557 return SECSuccess;
558 }
559
560 private:
561 const Config& cfg_;
562 ScopedPRFileDesc pr_fd_;
563 ScopedPRFileDesc ssl_fd_;
564 ScopedCERTCertificate cert_;
565 ScopedSECKEYPrivateKey key_;
566 };
567
ReadConfig(int argc,char ** argv)568 std::unique_ptr<const Config> ReadConfig(int argc, char** argv) {
569 std::unique_ptr<Config> cfg(new Config());
570
571 cfg->AddEntry<int>("port", 0);
572 cfg->AddEntry<bool>("server", false);
573 cfg->AddEntry<int>("resume-count", 0);
574 cfg->AddEntry<std::string>("key-file", "");
575 cfg->AddEntry<std::string>("cert-file", "");
576 cfg->AddEntry<int>("min-version", 0);
577 cfg->AddEntry<int>("max-version", 0xffff);
578 for (auto flag : kVersionDisableFlags) {
579 cfg->AddEntry<bool>(flag, false);
580 }
581 cfg->AddEntry<bool>("fallback-scsv", false);
582 cfg->AddEntry<bool>("false-start", false);
583 cfg->AddEntry<bool>("enable-ocsp-stapling", false);
584 cfg->AddEntry<bool>("write-then-read", false);
585 cfg->AddEntry<bool>("require-any-client-certificate", false);
586 cfg->AddEntry<bool>("verify-peer", false);
587 cfg->AddEntry<bool>("is-handshaker-supported", false);
588 cfg->AddEntry<std::string>("handshaker-path", ""); // Ignore this
589 cfg->AddEntry<std::string>("advertise-alpn", "");
590 cfg->AddEntry<std::string>("expect-alpn", "");
591 cfg->AddEntry<std::vector<int>>("signing-prefs", std::vector<int>());
592 cfg->AddEntry<std::vector<int>>("verify-prefs", std::vector<int>());
593 cfg->AddEntry<int>("expect-peer-signature-algorithm", 0);
594 cfg->AddEntry<std::string>("nss-cipher", "");
595
596 auto rv = cfg->ParseArgs(argc, argv);
597 switch (rv) {
598 case Config::kOK:
599 break;
600 case Config::kUnknownFlag:
601 exitCodeUnimplemented = true;
602 default:
603 return nullptr;
604 }
605
606 // Needed to change to std::unique_ptr<const Config>
607 return std::move(cfg);
608 }
609
RunCycle(std::unique_ptr<const Config> & cfg)610 bool RunCycle(std::unique_ptr<const Config>& cfg) {
611 std::unique_ptr<TestAgent> agent(TestAgent::Create(*cfg));
612 return agent && agent->DoExchange() == SECSuccess;
613 }
614
GetExitCode(bool success)615 int GetExitCode(bool success) {
616 if (exitCodeUnimplemented) {
617 return 89;
618 }
619
620 if (success) {
621 return 0;
622 }
623
624 return 1;
625 }
626
main(int argc,char ** argv)627 int main(int argc, char** argv) {
628 std::unique_ptr<const Config> cfg = ReadConfig(argc, argv);
629 if (!cfg) {
630 return GetExitCode(false);
631 }
632
633 if (cfg->get<bool>("is-handshaker-supported")) {
634 std::cout << "No\n";
635 return 0;
636 }
637
638 if (cfg->get<bool>("server")) {
639 if (SSL_ConfigServerSessionIDCache(1024, 0, 0, ".") != SECSuccess) {
640 std::cerr << "Couldn't configure session cache\n";
641 return 1;
642 }
643 }
644
645 if (NSS_NoDB_Init(nullptr) != SECSuccess) {
646 return 1;
647 }
648
649 // Run a single test cycle.
650 bool success = RunCycle(cfg);
651
652 int resume_count = cfg->get<int>("resume-count");
653 while (success && resume_count-- > 0) {
654 std::cout << "Resuming" << std::endl;
655 success = RunCycle(cfg);
656 }
657
658 SSL_ClearSessionCache();
659
660 if (cfg->get<bool>("server")) {
661 SSL_ShutdownServerSessionIDCache();
662 }
663
664 if (NSS_Shutdown() != SECSuccess) {
665 success = false;
666 }
667
668 return GetExitCode(success);
669 }
670