11cb0ef41Sopenharmony_ci// Copyright Joyent, Inc. and other Node contributors.
21cb0ef41Sopenharmony_ci//
31cb0ef41Sopenharmony_ci// Permission is hereby granted, free of charge, to any person obtaining a
41cb0ef41Sopenharmony_ci// copy of this software and associated documentation files (the
51cb0ef41Sopenharmony_ci// "Software"), to deal in the Software without restriction, including
61cb0ef41Sopenharmony_ci// without limitation the rights to use, copy, modify, merge, publish,
71cb0ef41Sopenharmony_ci// distribute, sublicense, and/or sell copies of the Software, and to permit
81cb0ef41Sopenharmony_ci// persons to whom the Software is furnished to do so, subject to the
91cb0ef41Sopenharmony_ci// following conditions:
101cb0ef41Sopenharmony_ci//
111cb0ef41Sopenharmony_ci// The above copyright notice and this permission notice shall be included
121cb0ef41Sopenharmony_ci// in all copies or substantial portions of the Software.
131cb0ef41Sopenharmony_ci//
141cb0ef41Sopenharmony_ci// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
151cb0ef41Sopenharmony_ci// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
161cb0ef41Sopenharmony_ci// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
171cb0ef41Sopenharmony_ci// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
181cb0ef41Sopenharmony_ci// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
191cb0ef41Sopenharmony_ci// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
201cb0ef41Sopenharmony_ci// USE OR OTHER DEALINGS IN THE SOFTWARE.
211cb0ef41Sopenharmony_ci
221cb0ef41Sopenharmony_ci'use strict';
231cb0ef41Sopenharmony_ciconst common = require('../common');
241cb0ef41Sopenharmony_ci
251cb0ef41Sopenharmony_ciif (!common.hasCrypto)
261cb0ef41Sopenharmony_ci  common.skip('missing crypto');
271cb0ef41Sopenharmony_ci
281cb0ef41Sopenharmony_ciif (!common.opensslCli)
291cb0ef41Sopenharmony_ci  common.skip('node compiled without OpenSSL CLI.');
301cb0ef41Sopenharmony_ci
311cb0ef41Sopenharmony_ci// This is a rather complex test which sets up various TLS servers with node
321cb0ef41Sopenharmony_ci// and connects to them using the 'openssl s_client' command line utility
331cb0ef41Sopenharmony_ci// with various keys. Depending on the certificate authority and other
341cb0ef41Sopenharmony_ci// parameters given to the server, the various clients are
351cb0ef41Sopenharmony_ci// - rejected,
361cb0ef41Sopenharmony_ci// - accepted and "unauthorized", or
371cb0ef41Sopenharmony_ci// - accepted and "authorized".
381cb0ef41Sopenharmony_ci
391cb0ef41Sopenharmony_ciconst assert = require('assert');
401cb0ef41Sopenharmony_ciconst { spawn } = require('child_process');
411cb0ef41Sopenharmony_ciconst { SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION } =
421cb0ef41Sopenharmony_ci  require('crypto').constants;
431cb0ef41Sopenharmony_ciconst tls = require('tls');
441cb0ef41Sopenharmony_ciconst fixtures = require('../common/fixtures');
451cb0ef41Sopenharmony_ci
461cb0ef41Sopenharmony_ciconst testCases =
471cb0ef41Sopenharmony_ci  [{ title: 'Do not request certs. Everyone is unauthorized.',
481cb0ef41Sopenharmony_ci     requestCert: false,
491cb0ef41Sopenharmony_ci     rejectUnauthorized: false,
501cb0ef41Sopenharmony_ci     renegotiate: false,
511cb0ef41Sopenharmony_ci     CAs: ['ca1-cert'],
521cb0ef41Sopenharmony_ci     clients:
531cb0ef41Sopenharmony_ci     [{ name: 'agent1', shouldReject: false, shouldAuth: false },
541cb0ef41Sopenharmony_ci      { name: 'agent2', shouldReject: false, shouldAuth: false },
551cb0ef41Sopenharmony_ci      { name: 'agent3', shouldReject: false, shouldAuth: false },
561cb0ef41Sopenharmony_ci      { name: 'nocert', shouldReject: false, shouldAuth: false },
571cb0ef41Sopenharmony_ci     ] },
581cb0ef41Sopenharmony_ci
591cb0ef41Sopenharmony_ci   { title: 'Allow both authed and unauthed connections with CA1',
601cb0ef41Sopenharmony_ci     requestCert: true,
611cb0ef41Sopenharmony_ci     rejectUnauthorized: false,
621cb0ef41Sopenharmony_ci     renegotiate: false,
631cb0ef41Sopenharmony_ci     CAs: ['ca1-cert'],
641cb0ef41Sopenharmony_ci     clients:
651cb0ef41Sopenharmony_ci    [{ name: 'agent1', shouldReject: false, shouldAuth: true },
661cb0ef41Sopenharmony_ci     { name: 'agent2', shouldReject: false, shouldAuth: false },
671cb0ef41Sopenharmony_ci     { name: 'agent3', shouldReject: false, shouldAuth: false },
681cb0ef41Sopenharmony_ci     { name: 'nocert', shouldReject: false, shouldAuth: false },
691cb0ef41Sopenharmony_ci    ] },
701cb0ef41Sopenharmony_ci
711cb0ef41Sopenharmony_ci   { title: 'Do not request certs at connection. Do that later',
721cb0ef41Sopenharmony_ci     requestCert: false,
731cb0ef41Sopenharmony_ci     rejectUnauthorized: false,
741cb0ef41Sopenharmony_ci     renegotiate: true,
751cb0ef41Sopenharmony_ci     CAs: ['ca1-cert'],
761cb0ef41Sopenharmony_ci     clients:
771cb0ef41Sopenharmony_ci    [{ name: 'agent1', shouldReject: false, shouldAuth: true },
781cb0ef41Sopenharmony_ci     { name: 'agent2', shouldReject: false, shouldAuth: false },
791cb0ef41Sopenharmony_ci     { name: 'agent3', shouldReject: false, shouldAuth: false },
801cb0ef41Sopenharmony_ci     { name: 'nocert', shouldReject: false, shouldAuth: false },
811cb0ef41Sopenharmony_ci    ] },
821cb0ef41Sopenharmony_ci
831cb0ef41Sopenharmony_ci   { title: 'Allow only authed connections with CA1',
841cb0ef41Sopenharmony_ci     requestCert: true,
851cb0ef41Sopenharmony_ci     rejectUnauthorized: true,
861cb0ef41Sopenharmony_ci     renegotiate: false,
871cb0ef41Sopenharmony_ci     CAs: ['ca1-cert'],
881cb0ef41Sopenharmony_ci     clients:
891cb0ef41Sopenharmony_ci    [{ name: 'agent1', shouldReject: false, shouldAuth: true },
901cb0ef41Sopenharmony_ci     { name: 'agent2', shouldReject: true },
911cb0ef41Sopenharmony_ci     { name: 'agent3', shouldReject: true },
921cb0ef41Sopenharmony_ci     { name: 'nocert', shouldReject: true },
931cb0ef41Sopenharmony_ci    ] },
941cb0ef41Sopenharmony_ci
951cb0ef41Sopenharmony_ci   { title: 'Allow only authed connections with CA1 and CA2',
961cb0ef41Sopenharmony_ci     requestCert: true,
971cb0ef41Sopenharmony_ci     rejectUnauthorized: true,
981cb0ef41Sopenharmony_ci     renegotiate: false,
991cb0ef41Sopenharmony_ci     CAs: ['ca1-cert', 'ca2-cert'],
1001cb0ef41Sopenharmony_ci     clients:
1011cb0ef41Sopenharmony_ci    [{ name: 'agent1', shouldReject: false, shouldAuth: true },
1021cb0ef41Sopenharmony_ci     { name: 'agent2', shouldReject: true },
1031cb0ef41Sopenharmony_ci     { name: 'agent3', shouldReject: false, shouldAuth: true },
1041cb0ef41Sopenharmony_ci     { name: 'nocert', shouldReject: true },
1051cb0ef41Sopenharmony_ci    ] },
1061cb0ef41Sopenharmony_ci
1071cb0ef41Sopenharmony_ci
1081cb0ef41Sopenharmony_ci   { title: 'Allow only certs signed by CA2 but not in the CRL',
1091cb0ef41Sopenharmony_ci     requestCert: true,
1101cb0ef41Sopenharmony_ci     rejectUnauthorized: true,
1111cb0ef41Sopenharmony_ci     renegotiate: false,
1121cb0ef41Sopenharmony_ci     CAs: ['ca2-cert'],
1131cb0ef41Sopenharmony_ci     crl: 'ca2-crl',
1141cb0ef41Sopenharmony_ci     clients: [
1151cb0ef41Sopenharmony_ci       { name: 'agent1', shouldReject: true, shouldAuth: false },
1161cb0ef41Sopenharmony_ci       { name: 'agent2', shouldReject: true, shouldAuth: false },
1171cb0ef41Sopenharmony_ci       { name: 'agent3', shouldReject: false, shouldAuth: true },
1181cb0ef41Sopenharmony_ci       // Agent4 has a cert in the CRL.
1191cb0ef41Sopenharmony_ci       { name: 'agent4', shouldReject: true, shouldAuth: false },
1201cb0ef41Sopenharmony_ci       { name: 'nocert', shouldReject: true },
1211cb0ef41Sopenharmony_ci     ] },
1221cb0ef41Sopenharmony_ci  ];
1231cb0ef41Sopenharmony_ci
1241cb0ef41Sopenharmony_cifunction filenamePEM(n) {
1251cb0ef41Sopenharmony_ci  return fixtures.path('keys', `${n}.pem`);
1261cb0ef41Sopenharmony_ci}
1271cb0ef41Sopenharmony_ci
1281cb0ef41Sopenharmony_cifunction loadPEM(n) {
1291cb0ef41Sopenharmony_ci  return fixtures.readKey(`${n}.pem`);
1301cb0ef41Sopenharmony_ci}
1311cb0ef41Sopenharmony_ci
1321cb0ef41Sopenharmony_ci
1331cb0ef41Sopenharmony_ciconst serverKey = loadPEM('agent2-key');
1341cb0ef41Sopenharmony_ciconst serverCert = loadPEM('agent2-cert');
1351cb0ef41Sopenharmony_ci
1361cb0ef41Sopenharmony_ci
1371cb0ef41Sopenharmony_cifunction runClient(prefix, port, options, cb) {
1381cb0ef41Sopenharmony_ci
1391cb0ef41Sopenharmony_ci  // Client can connect in three ways:
1401cb0ef41Sopenharmony_ci  // - Self-signed cert
1411cb0ef41Sopenharmony_ci  // - Certificate, but not signed by CA.
1421cb0ef41Sopenharmony_ci  // - Certificate signed by CA.
1431cb0ef41Sopenharmony_ci
1441cb0ef41Sopenharmony_ci  const args = ['s_client', '-connect', `127.0.0.1:${port}`];
1451cb0ef41Sopenharmony_ci
1461cb0ef41Sopenharmony_ci  console.log(`${prefix}  connecting with`, options.name);
1471cb0ef41Sopenharmony_ci
1481cb0ef41Sopenharmony_ci  switch (options.name) {
1491cb0ef41Sopenharmony_ci    case 'agent1':
1501cb0ef41Sopenharmony_ci      // Signed by CA1
1511cb0ef41Sopenharmony_ci      args.push('-key');
1521cb0ef41Sopenharmony_ci      args.push(filenamePEM('agent1-key'));
1531cb0ef41Sopenharmony_ci      args.push('-cert');
1541cb0ef41Sopenharmony_ci      args.push(filenamePEM('agent1-cert'));
1551cb0ef41Sopenharmony_ci      break;
1561cb0ef41Sopenharmony_ci
1571cb0ef41Sopenharmony_ci    case 'agent2':
1581cb0ef41Sopenharmony_ci      // Self-signed
1591cb0ef41Sopenharmony_ci      // This is also the key-cert pair that the server will use.
1601cb0ef41Sopenharmony_ci      args.push('-key');
1611cb0ef41Sopenharmony_ci      args.push(filenamePEM('agent2-key'));
1621cb0ef41Sopenharmony_ci      args.push('-cert');
1631cb0ef41Sopenharmony_ci      args.push(filenamePEM('agent2-cert'));
1641cb0ef41Sopenharmony_ci      break;
1651cb0ef41Sopenharmony_ci
1661cb0ef41Sopenharmony_ci    case 'agent3':
1671cb0ef41Sopenharmony_ci      // Signed by CA2
1681cb0ef41Sopenharmony_ci      args.push('-key');
1691cb0ef41Sopenharmony_ci      args.push(filenamePEM('agent3-key'));
1701cb0ef41Sopenharmony_ci      args.push('-cert');
1711cb0ef41Sopenharmony_ci      args.push(filenamePEM('agent3-cert'));
1721cb0ef41Sopenharmony_ci      break;
1731cb0ef41Sopenharmony_ci
1741cb0ef41Sopenharmony_ci    case 'agent4':
1751cb0ef41Sopenharmony_ci      // Signed by CA2 (rejected by ca2-crl)
1761cb0ef41Sopenharmony_ci      args.push('-key');
1771cb0ef41Sopenharmony_ci      args.push(filenamePEM('agent4-key'));
1781cb0ef41Sopenharmony_ci      args.push('-cert');
1791cb0ef41Sopenharmony_ci      args.push(filenamePEM('agent4-cert'));
1801cb0ef41Sopenharmony_ci      break;
1811cb0ef41Sopenharmony_ci
1821cb0ef41Sopenharmony_ci    case 'nocert':
1831cb0ef41Sopenharmony_ci      // Do not send certificate
1841cb0ef41Sopenharmony_ci      break;
1851cb0ef41Sopenharmony_ci
1861cb0ef41Sopenharmony_ci    default:
1871cb0ef41Sopenharmony_ci      throw new Error(`${prefix}Unknown agent name`);
1881cb0ef41Sopenharmony_ci  }
1891cb0ef41Sopenharmony_ci
1901cb0ef41Sopenharmony_ci  // To test use: openssl s_client -connect localhost:8000
1911cb0ef41Sopenharmony_ci  const client = spawn(common.opensslCli, args);
1921cb0ef41Sopenharmony_ci
1931cb0ef41Sopenharmony_ci  let out = '';
1941cb0ef41Sopenharmony_ci
1951cb0ef41Sopenharmony_ci  let rejected = true;
1961cb0ef41Sopenharmony_ci  let authed = false;
1971cb0ef41Sopenharmony_ci  let goodbye = false;
1981cb0ef41Sopenharmony_ci
1991cb0ef41Sopenharmony_ci  client.stdout.setEncoding('utf8');
2001cb0ef41Sopenharmony_ci  client.stdout.on('data', function(d) {
2011cb0ef41Sopenharmony_ci    out += d;
2021cb0ef41Sopenharmony_ci
2031cb0ef41Sopenharmony_ci    if (!goodbye && /_unauthed/.test(out)) {
2041cb0ef41Sopenharmony_ci      console.error(`${prefix}  * unauthed`);
2051cb0ef41Sopenharmony_ci      goodbye = true;
2061cb0ef41Sopenharmony_ci      client.kill();
2071cb0ef41Sopenharmony_ci      authed = false;
2081cb0ef41Sopenharmony_ci      rejected = false;
2091cb0ef41Sopenharmony_ci    }
2101cb0ef41Sopenharmony_ci
2111cb0ef41Sopenharmony_ci    if (!goodbye && /_authed/.test(out)) {
2121cb0ef41Sopenharmony_ci      console.error(`${prefix}  * authed`);
2131cb0ef41Sopenharmony_ci      goodbye = true;
2141cb0ef41Sopenharmony_ci      client.kill();
2151cb0ef41Sopenharmony_ci      authed = true;
2161cb0ef41Sopenharmony_ci      rejected = false;
2171cb0ef41Sopenharmony_ci    }
2181cb0ef41Sopenharmony_ci  });
2191cb0ef41Sopenharmony_ci
2201cb0ef41Sopenharmony_ci  client.on('exit', function(code) {
2211cb0ef41Sopenharmony_ci    if (options.shouldReject) {
2221cb0ef41Sopenharmony_ci      assert.strictEqual(
2231cb0ef41Sopenharmony_ci        rejected, true,
2241cb0ef41Sopenharmony_ci        `${prefix}${options.name} NOT rejected, but should have been`);
2251cb0ef41Sopenharmony_ci    } else {
2261cb0ef41Sopenharmony_ci      assert.strictEqual(
2271cb0ef41Sopenharmony_ci        rejected, false,
2281cb0ef41Sopenharmony_ci        `${prefix}${options.name} rejected, but should NOT have been`);
2291cb0ef41Sopenharmony_ci      assert.strictEqual(
2301cb0ef41Sopenharmony_ci        authed, options.shouldAuth,
2311cb0ef41Sopenharmony_ci        `${prefix}${options.name} authed is ${authed} but should have been ${
2321cb0ef41Sopenharmony_ci          options.shouldAuth}`);
2331cb0ef41Sopenharmony_ci    }
2341cb0ef41Sopenharmony_ci
2351cb0ef41Sopenharmony_ci    cb();
2361cb0ef41Sopenharmony_ci  });
2371cb0ef41Sopenharmony_ci}
2381cb0ef41Sopenharmony_ci
2391cb0ef41Sopenharmony_ci
2401cb0ef41Sopenharmony_ci// Run the tests
2411cb0ef41Sopenharmony_cilet successfulTests = 0;
2421cb0ef41Sopenharmony_cifunction runTest(port, testIndex) {
2431cb0ef41Sopenharmony_ci  const prefix = `${testIndex} `;
2441cb0ef41Sopenharmony_ci  const tcase = testCases[testIndex];
2451cb0ef41Sopenharmony_ci  if (!tcase) return;
2461cb0ef41Sopenharmony_ci
2471cb0ef41Sopenharmony_ci  console.error(`${prefix}Running '${tcase.title}'`);
2481cb0ef41Sopenharmony_ci
2491cb0ef41Sopenharmony_ci  const cas = tcase.CAs.map(loadPEM);
2501cb0ef41Sopenharmony_ci
2511cb0ef41Sopenharmony_ci  const crl = tcase.crl ? loadPEM(tcase.crl) : null;
2521cb0ef41Sopenharmony_ci
2531cb0ef41Sopenharmony_ci  const serverOptions = {
2541cb0ef41Sopenharmony_ci    key: serverKey,
2551cb0ef41Sopenharmony_ci    cert: serverCert,
2561cb0ef41Sopenharmony_ci    ca: cas,
2571cb0ef41Sopenharmony_ci    crl: crl,
2581cb0ef41Sopenharmony_ci    requestCert: tcase.requestCert,
2591cb0ef41Sopenharmony_ci    rejectUnauthorized: tcase.rejectUnauthorized
2601cb0ef41Sopenharmony_ci  };
2611cb0ef41Sopenharmony_ci
2621cb0ef41Sopenharmony_ci  // If renegotiating - session might be resumed and openssl won't request
2631cb0ef41Sopenharmony_ci  // client's certificate (probably because of bug in the openssl)
2641cb0ef41Sopenharmony_ci  if (tcase.renegotiate) {
2651cb0ef41Sopenharmony_ci    serverOptions.secureOptions =
2661cb0ef41Sopenharmony_ci        SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION;
2671cb0ef41Sopenharmony_ci    // Renegotiation as a protocol feature was dropped after TLS1.2.
2681cb0ef41Sopenharmony_ci    serverOptions.maxVersion = 'TLSv1.2';
2691cb0ef41Sopenharmony_ci  }
2701cb0ef41Sopenharmony_ci
2711cb0ef41Sopenharmony_ci  let renegotiated = false;
2721cb0ef41Sopenharmony_ci  const server = tls.Server(serverOptions, function handleConnection(c) {
2731cb0ef41Sopenharmony_ci    c.on('error', function(e) {
2741cb0ef41Sopenharmony_ci      // child.kill() leads ECONNRESET error in the TLS connection of
2751cb0ef41Sopenharmony_ci      // openssl s_client via spawn(). A test result is already
2761cb0ef41Sopenharmony_ci      // checked by the data of client.stdout before child.kill() so
2771cb0ef41Sopenharmony_ci      // these tls errors can be ignored.
2781cb0ef41Sopenharmony_ci    });
2791cb0ef41Sopenharmony_ci    if (tcase.renegotiate && !renegotiated) {
2801cb0ef41Sopenharmony_ci      renegotiated = true;
2811cb0ef41Sopenharmony_ci      setTimeout(function() {
2821cb0ef41Sopenharmony_ci        console.error(`${prefix}- connected, renegotiating`);
2831cb0ef41Sopenharmony_ci        c.write('\n_renegotiating\n');
2841cb0ef41Sopenharmony_ci        return c.renegotiate({
2851cb0ef41Sopenharmony_ci          requestCert: true,
2861cb0ef41Sopenharmony_ci          rejectUnauthorized: false
2871cb0ef41Sopenharmony_ci        }, function(err) {
2881cb0ef41Sopenharmony_ci          assert.ifError(err);
2891cb0ef41Sopenharmony_ci          c.write('\n_renegotiated\n');
2901cb0ef41Sopenharmony_ci          handleConnection(c);
2911cb0ef41Sopenharmony_ci        });
2921cb0ef41Sopenharmony_ci      }, 200);
2931cb0ef41Sopenharmony_ci      return;
2941cb0ef41Sopenharmony_ci    }
2951cb0ef41Sopenharmony_ci
2961cb0ef41Sopenharmony_ci    if (c.authorized) {
2971cb0ef41Sopenharmony_ci      console.error(`${prefix}- authed connection: ${
2981cb0ef41Sopenharmony_ci        c.getPeerCertificate().subject.CN}`);
2991cb0ef41Sopenharmony_ci      c.write('\n_authed\n');
3001cb0ef41Sopenharmony_ci    } else {
3011cb0ef41Sopenharmony_ci      console.error(`${prefix}- unauthed connection: %s`, c.authorizationError);
3021cb0ef41Sopenharmony_ci      c.write('\n_unauthed\n');
3031cb0ef41Sopenharmony_ci    }
3041cb0ef41Sopenharmony_ci  });
3051cb0ef41Sopenharmony_ci
3061cb0ef41Sopenharmony_ci  function runNextClient(clientIndex) {
3071cb0ef41Sopenharmony_ci    const options = tcase.clients[clientIndex];
3081cb0ef41Sopenharmony_ci    if (options) {
3091cb0ef41Sopenharmony_ci      runClient(`${prefix}${clientIndex} `, port, options, function() {
3101cb0ef41Sopenharmony_ci        runNextClient(clientIndex + 1);
3111cb0ef41Sopenharmony_ci      });
3121cb0ef41Sopenharmony_ci    } else {
3131cb0ef41Sopenharmony_ci      server.close();
3141cb0ef41Sopenharmony_ci      successfulTests++;
3151cb0ef41Sopenharmony_ci      runTest(0, nextTest++);
3161cb0ef41Sopenharmony_ci    }
3171cb0ef41Sopenharmony_ci  }
3181cb0ef41Sopenharmony_ci
3191cb0ef41Sopenharmony_ci  server.listen(port, function() {
3201cb0ef41Sopenharmony_ci    port = server.address().port;
3211cb0ef41Sopenharmony_ci    if (tcase.debug) {
3221cb0ef41Sopenharmony_ci      console.error(`${prefix}TLS server running on port ${port}`);
3231cb0ef41Sopenharmony_ci    } else if (tcase.renegotiate) {
3241cb0ef41Sopenharmony_ci      runNextClient(0);
3251cb0ef41Sopenharmony_ci    } else {
3261cb0ef41Sopenharmony_ci      let clientsCompleted = 0;
3271cb0ef41Sopenharmony_ci      for (let i = 0; i < tcase.clients.length; i++) {
3281cb0ef41Sopenharmony_ci        runClient(`${prefix}${i} `, port, tcase.clients[i], function() {
3291cb0ef41Sopenharmony_ci          clientsCompleted++;
3301cb0ef41Sopenharmony_ci          if (clientsCompleted === tcase.clients.length) {
3311cb0ef41Sopenharmony_ci            server.close();
3321cb0ef41Sopenharmony_ci            successfulTests++;
3331cb0ef41Sopenharmony_ci            runTest(0, nextTest++);
3341cb0ef41Sopenharmony_ci          }
3351cb0ef41Sopenharmony_ci        });
3361cb0ef41Sopenharmony_ci      }
3371cb0ef41Sopenharmony_ci    }
3381cb0ef41Sopenharmony_ci  });
3391cb0ef41Sopenharmony_ci}
3401cb0ef41Sopenharmony_ci
3411cb0ef41Sopenharmony_ci
3421cb0ef41Sopenharmony_cilet nextTest = 0;
3431cb0ef41Sopenharmony_cirunTest(0, nextTest++);
3441cb0ef41Sopenharmony_ci
3451cb0ef41Sopenharmony_ci
3461cb0ef41Sopenharmony_ciprocess.on('exit', function() {
3471cb0ef41Sopenharmony_ci  assert.strictEqual(successfulTests, testCases.length);
3481cb0ef41Sopenharmony_ci});
349