int fizzServerCommand()

in fizz/tool/FizzServerCommand.cpp [683:1204]


int fizzServerCommand(const std::vector<std::string>& args) {
  uint16_t port = 8443;
  std::vector<std::string> certPaths;
  std::vector<std::string> keyPaths;
  std::string keyPass;
  ClientAuthMode clientAuthMode = ClientAuthMode::None;
  std::string caPath;
  std::string caFile;
  std::string keyLogFile;
  bool early = false;
  std::vector<std::string> alpns;
  folly::Optional<std::vector<CertificateCompressionAlgorithm>> compAlgos;
  bool loop = false;
  bool fallback = false;
  bool http = false;
  uint32_t earlyDataSize = std::numeric_limits<uint32_t>::max();
  std::vector<std::vector<CipherSuite>> ciphers {
    {CipherSuite::TLS_AES_128_GCM_SHA256, CipherSuite::TLS_AES_256_GCM_SHA384},
#if FOLLY_OPENSSL_HAS_CHACHA
    {
      CipherSuite::TLS_CHACHA20_POLY1305_SHA256
    }
#endif
  };
  std::vector<SignatureScheme> sigSchemes{
      SignatureScheme::ecdsa_secp256r1_sha256,
      SignatureScheme::ecdsa_secp384r1_sha384,
      SignatureScheme::ecdsa_secp521r1_sha512,
      SignatureScheme::rsa_pss_sha256,
  };
  std::vector<NamedGroup> groups{
      NamedGroup::secp256r1,
      NamedGroup::x25519,
  };
  std::string credPath;
  bool ech = false;
  std::string echConfigsFile;
  std::string echPrivateKeyFile;
  bool uring = false;
  bool uringAsync = false;
  bool uringRegisterFds = false;
  int32_t uringCapacity = 128;
  int32_t uringMaxSubmit = 64;
  int32_t uringMaxGet = -1;

  // clang-format off
  FizzArgHandlerMap handlers = {
    {"-accept", {true, [&port](const std::string& arg) {
        port = portFromString(arg, true);
    }}},
    {"-ciphers", {true, [&ciphers](const std::string& arg) {
        ciphers.clear();
        std::vector<std::string> list;
        folly::split(":", arg, list);
        for (const auto& item : list) {
          try {
            ciphers.push_back(splitParse<CipherSuite>(item, ","));
          }
          catch (const std::exception& e) {
            LOG(ERROR) << "Error parsing cipher suites: " << e.what();
            throw;
          }
        }
    }}},
    {"-sigschemes", {true, [&sigSchemes](const std::string& arg) {
        sigSchemes = splitParse<SignatureScheme>(arg);
    }}},
    {"-curves", {true, [&groups](const std::string& arg) {
        groups = splitParse<NamedGroup>(arg);
    }}},
    {"-cert", {true, [&certPaths](const std::string& arg) { certPaths.push_back(arg); }}},
    {"-key", {true, [&keyPaths](const std::string& arg) { keyPaths.push_back(arg); }}},
    {"-pass", {true, [&keyPass](const std::string& arg) { keyPass = arg; }}},
    {"-requestcert", {false, [&clientAuthMode](const std::string&) {
      clientAuthMode = ClientAuthMode::Optional;
    }}},
    {"-requirecert", {false, [&clientAuthMode](const std::string&) {
      clientAuthMode = ClientAuthMode::Required;
    }}},
    {"-capath", {true, [&caPath](const std::string& arg) { caPath = arg; }}},
    {"-cafile", {true, [&caFile](const std::string& arg) { caFile = arg; }}},
    {"-keylog", {true,[&keyLogFile](const std::string& arg) {
      keyLogFile = arg;
    }}},
    {"-early", {false, [&early](const std::string&) { early = true; }}},
    {"-alpn", {true, [&alpns](const std::string& arg) {
        alpns.clear();
        folly::split(":", arg, alpns);
    }}},
    {"-certcompression", {true, [&compAlgos](const std::string& arg) {
        try {
          compAlgos = splitParse<CertificateCompressionAlgorithm>(arg);
        } catch (const std::exception& e) {
          LOG(ERROR) << "Error parsing certificate compression algorithms: " << e.what();
          throw;
        }
    }}},
    {"-loop", {false, [&loop](const std::string&) { loop = true; }}},
    {"-quiet", {false, [](const std::string&) {
        FLAGS_minloglevel = google::GLOG_ERROR;
    }}},
    {"-fallback", {false, [&fallback](const std::string&) {
        fallback = true;
    }}},
    {"-http", {false, [&http](const std::string&) { http = true; }}},
    {"-early_max", {true, [&earlyDataSize](const std::string& arg) {
        earlyDataSize = folly::to<uint32_t>(arg);
    }}},
    {"-delegatedcred", {true, [&credPath](const std::string& arg) {
        credPath = arg;
    }}},
    {"-ech", {false, [&ech](const std::string&) {
        ech = true;
    }}},
    {"-echconfigs", {true, [&echConfigsFile](const std::string& arg) {
        echConfigsFile = arg;
    }}},
    {"-echprivatekey", {true, [&echPrivateKeyFile](const std::string& arg) {
        echPrivateKeyFile = arg;
    }}}
#ifdef FIZZ_TOOL_ENABLE_IO_URING
    ,{"-io_uring", {false, [&uring](const std::string&) { uring = true; }}},
    {"-io_uring_async_recv", {false, [&uringAsync](const std::string&) {
        uringAsync = true;
    }}},
    {"-io_uring_register_fds", {false, [&uringRegisterFds](const std::string&) {
        uringRegisterFds = true;
    }}},
    {"-io_uring_capacity", {true, [&uringCapacity](const std::string& arg) {
        uringCapacity = folly::to<int32_t>(arg);
    }}},
    {"-io_uring_max_get", {true, [&uringMaxGet](const std::string& arg) {
        uringMaxGet = folly::to<int32_t>(arg);
    }}},
    {"-io_uring_max_submit", {true, [&uringMaxSubmit](const std::string& arg) {
        uringMaxSubmit = folly::to<int32_t>(arg);
    }}}
#endif
  };
  // clang-format on

  try {
    if (parseArguments(args, handlers, printUsage)) {
      // Parsing failed, return
      return 1;
    }
  } catch (const std::exception& e) {
    LOG(ERROR) << "Error: " << e.what();
    return 1;
  }

  // Sanity check input.
  if (certPaths.empty() != keyPaths.empty()) {
    LOG(ERROR) << "-cert and -key are both required when specified";
    return 1;
  }

  if (!credPath.empty() && (certPaths.empty() || keyPaths.empty())) {
    LOG(ERROR)
        << "-cert and -key are both required when delegated credentials are in use";
    return 1;
  }

  EventBase evb(folly::EventBase::Options().setBackendFactory([uring,
                                                               uringAsync,
                                                               uringRegisterFds,
                                                               uringCapacity,
                                                               uringMaxSubmit,
                                                               uringMaxGet] {
    return setupBackend(
        uring,
        uringAsync,
        uringRegisterFds,
        uringCapacity,
        uringMaxSubmit,
        uringMaxGet);
  }));
  std::shared_ptr<const CertificateVerifier> verifier;

  if (clientAuthMode != ClientAuthMode::None) {
    // Initialize CA store first, if given.
    folly::ssl::X509StoreUniquePtr storePtr;
    if (!caPath.empty() || !caFile.empty()) {
      storePtr.reset(X509_STORE_new());
      auto caFilePtr = caFile.empty() ? nullptr : caFile.c_str();
      auto caPathPtr = caPath.empty() ? nullptr : caPath.c_str();

      if (X509_STORE_load_locations(storePtr.get(), caFilePtr, caPathPtr) ==
          0) {
        LOG(ERROR) << "Failed to load CA certificates";
        return 1;
      }
    }

    verifier = std::make_shared<const DefaultCertificateVerifier>(
        VerificationContext::Server, std::move(storePtr));
  }

  auto serverContext = std::make_shared<FizzServerContext>();

  if (ech) {
    // Use ECH  default values.
    serverContext->setECHDecrypter(setupDefaultDecrypter());
  }

  if ((echConfigsFile.empty() && !echPrivateKeyFile.empty()) ||
      (!echConfigsFile.empty() && echPrivateKeyFile.empty())) {
    LOG(ERROR)
        << "Must provide both an ECH configs file (\"-echconfigs [config file]\") and an ECH private key (\"-echprivatekey [key file]\") or neither.";
    return 1;
  }

  // ECH is implicitly enabled if ECH configs and a private key are provided.
  // Note that if there are ECH configs provided, there must be an associated
  // key file.
  if (!echConfigsFile.empty()) {
    // Setup ECH decrypting tools based on user provided ECH configs and private
    // key.
    auto decrypter =
        setupDecrypterFromInputs(echConfigsFile, echPrivateKeyFile);
    if (!decrypter) {
      LOG(ERROR) << "Unable to setup decrypter.";
      return 1;
    }
    serverContext->setECHDecrypter(decrypter);
  }

  serverContext->setSupportedCiphers(std::move(ciphers));
  serverContext->setSupportedSigSchemes(std::move(sigSchemes));
  serverContext->setSupportedGroups(std::move(groups));
  serverContext->setClientAuthMode(clientAuthMode);
  serverContext->setClientCertVerifier(verifier);

  auto ticketCipher = std::make_shared<
      Aead128GCMTicketCipher<TicketCodec<CertificateStorage::X509>>>(
      std::make_shared<OpenSSLFactory>(), std::make_shared<CertManager>());
  auto ticketSeed = RandomGenerator<32>().generateRandom();
  ticketCipher->setTicketSecrets({{range(ticketSeed)}});
  serverContext->setTicketCipher(ticketCipher);

  // Store a vector of compressors and algorithms for which there are
  // compressors.
  auto certManager =
      std::make_unique<fizz::extensions::DelegatedCredentialCertManager>();
  std::vector<std::shared_ptr<CertificateCompressor>> compressors;
  std::vector<CertificateCompressionAlgorithm> finalAlgos;
  if (compAlgos) {
    for (const auto& algo : *compAlgos) {
      switch (algo) {
        case CertificateCompressionAlgorithm::zlib:
          compressors.push_back(std::make_shared<ZlibCertificateCompressor>(9));
          finalAlgos.push_back(algo);
          break;
#ifdef FIZZ_TOOL_ENABLE_BROTLI
        case CertificateCompressionAlgorithm::brotli:
          compressors.push_back(
              std::make_shared<BrotliCertificateCompressor>());
          finalAlgos.push_back(algo);
          break;
#endif
#ifdef FIZZ_TOOL_ENABLE_ZSTD
        case CertificateCompressionAlgorithm::zstd:
          compressors.push_back(
              std::make_shared<ZstdCertificateCompressor>(19));
          finalAlgos.push_back(algo);
          break;
#endif
        default:
          LOG(WARNING) << "Don't know what compressor to use for "
                       << toString(algo) << ", ignoring.";
          break;
      }
    }
  }
  serverContext->setSupportedCompressionAlgorithms(finalAlgos);

  // Keeps track of whether or not the credential has been matched to
  // a cert passed in (if a credential is provided).
  bool credentialMatchNeeded = !credPath.empty();

  // Flag to indicate whether we've loaded the first cert (for default cert)
  bool first = true;

  // SSL context for fallback (if enabled).
  std::shared_ptr<SSLContext> sslContext;
  if (fallback) {
    if (certPaths.empty()) {
      LOG(ERROR) << "Fallback mode requires explicit certificates";
      return 1;
    }
  }

  // If we have specific certs to load (as opposed to autogenerated certs), we
  // load them in the following way:
  // 1) Load all private keys passed in and add them to the PrivateKeyMatcher
  // 2) Parse a credential (if passed in)
  // 3) For every cert:
  // 3a) If we have a delegated credential to match to a cert, check if this is
  //     a match. If so, find the corresponding privkey matching the
  //     credential's pubkey, and create the DC + add it.
  // 3b) If there is no credential or it's not a match, find the privkey
  // associated
  //     with this cert's pubkey and create a regular SelfCert with the certs +
  //     keys.
  //
  // If the cert doesn't match any known keys or the credential fails to be
  // associated with a private key and parent cert, the tool will exit with an
  // error.
  if (!certPaths.empty()) {
    // First, let's read the private keys
    PrivateKeyMatcher matcher;
    for (const auto& keyPath : keyPaths) {
      std::string keyData;
      if (!readFile(keyPath.c_str(), keyData)) {
        LOG(ERROR) << "Failed to read private key: " << keyPath;
        return 1;
      }
      matcher.addKey(
          keyPath,
          CertUtils::readPrivateKeyFromBuffer(
              keyData, keyPass.empty() ? nullptr : &keyPass[0]));
    }

    // Parse the credential if passed in.
    folly::Optional<fizz::extensions::DelegatedCredential> cred;
    if (credentialMatchNeeded) {
      std::string credData;
      if (!readFile(credPath.c_str(), credData)) {
        LOG(ERROR) << "Failed to read credential: " << credPath;
        return 1;
      }
      std::vector<Extension> credVec;
      credVec.emplace_back(Extension{
          ExtensionType::delegated_credential,
          folly::IOBuf::copyBuffer(std::move(credData))});

      try {
        cred = getExtension<fizz::extensions::DelegatedCredential>(
            std::move(credVec));
      } catch (const std::exception& e) {
        LOG(ERROR) << "Credential parsing failed: " << e.what();
        return 1;
      }
    }

    // Now, match certs to keys (and the credential if passed in).
    for (const auto& certPath : certPaths) {
      std::string certData;
      if (!readFile(certPath.c_str(), certData)) {
        LOG(ERROR) << "Failed to read certificate: " << certPath;
        return 1;
      }
      auto certs = folly::ssl::OpenSSLCertUtils::readCertsFromBuffer(
          folly::StringPiece(certData));
      if (certs.empty()) {
        LOG(ERROR) << "Failed to read any certs from path: " << certPath;
        return 1;
      }

      if (credentialMatchNeeded) {
        // Check if the signature matches.
        auto leafCert = certs.front().get();
        if (X509_up_ref(leafCert) != 1) {
          LOG(ERROR) << "Failed to upref leaf cert";
          return 1;
        }
        auto leafPeer =
            CertUtils::makePeerCert(folly::ssl::X509UniquePtr(leafCert));
        auto toSign =
            fizz::extensions::DelegatedCredentialUtils::prepareSignatureBuffer(
                *cred, folly::ssl::OpenSSLCertUtils::derEncode(*leafCert));
        try {
          leafPeer->verify(
              cred->credential_scheme,
              CertificateVerifyContext::DelegatedCredential,
              toSign->coalesce(),
              cred->signature->coalesce());
          // Verification succeeded, so make the credential.
          auto pubKeyRange = cred->public_key->coalesce();
          auto addr = pubKeyRange.data();
          folly::ssl::EvpPkeyUniquePtr pubKey(
              d2i_PUBKEY(nullptr, &addr, pubKeyRange.size()));
          if (!pubKey) {
            LOG(ERROR) << "Failed to parse credential pubkey";
            return 1;
          }

          auto credPrivKey = matcher.fetchKey(pubKey);
          if (!credPrivKey) {
            LOG(ERROR)
                << "Failed to match credential pubkey to any of the provided keys.";
            return 1;
          }

          std::unique_ptr<fizz::extensions::SelfDelegatedCredential> cert;
          switch (CertUtils::getKeyType(credPrivKey)) {
            case KeyType::RSA:
              cert = std::make_unique<
                  fizz::extensions::SelfDelegatedCredentialImpl<KeyType::RSA>>(
                  std::move(certs),
                  std::move(credPrivKey),
                  std::move(*cred),
                  compressors);
              break;
            case KeyType::P256:
              cert = std::make_unique<
                  fizz::extensions::SelfDelegatedCredentialImpl<KeyType::P256>>(
                  std::move(certs),
                  std::move(credPrivKey),
                  std::move(*cred),
                  compressors);
              break;
            case KeyType::P384:
              cert = std::make_unique<
                  fizz::extensions::SelfDelegatedCredentialImpl<KeyType::P384>>(
                  std::move(certs),
                  std::move(credPrivKey),
                  std::move(*cred),
                  compressors);
              break;
            case KeyType::P521:
              cert = std::make_unique<
                  fizz::extensions::SelfDelegatedCredentialImpl<KeyType::P521>>(
                  std::move(certs),
                  std::move(credPrivKey),
                  std::move(*cred),
                  compressors);
              break;
            case KeyType::ED25519:
              cert = std::make_unique<
                  fizz::extensions::SelfDelegatedCredentialImpl<
                      KeyType::ED25519>>(
                  std::move(certs),
                  std::move(credPrivKey),
                  std::move(*cred),
                  compressors);
              break;
          }

          certManager->addDelegatedCredential(std::move(cert));

          credentialMatchNeeded = false;

          // Skip the normal cert code at the end since it's a match.
          continue;
        } catch (const std::runtime_error&) {
          // Signature isn't a match, so this isn't for the credential. Exit out
          // of this block and add it as a regular cert.
        }
      }

      // Not a credential, add it as a normal cert.
      auto pkey = matcher.fetchKey(certs.front().get());
      if (!pkey.second) {
        LOG(ERROR) << "No matching private key for cert at path: " << certPath;
        return 1;
      }

      auto cert = CertUtils::makeSelfCert(
          std::move(certs), std::move(pkey.second), compressors);
      certManager->addCert(std::move(cert), first);
      if (first) {
        if (fallback) {
          // Fallback mode requires additional callback work for SNI to be
          // supported. As such, we just assign the default cert to it.
          sslContext = std::make_shared<SSLContext>();
          sslContext->loadCertKeyPairFromFiles(
              certPath.c_str(), pkey.first.c_str());
          SSL_CTX_set_ecdh_auto(sslContext->getSSLCtx(), 1);
        }
        first = false;
      }
    }
  } else {
    auto certData = fizz::test::createCert("fizz-self-signed", false, nullptr);
    std::vector<folly::ssl::X509UniquePtr> certChain;
    certChain.push_back(std::move(certData.cert));
    auto cert = std::make_unique<SelfCertImpl<KeyType::P256>>(
        std::move(certData.key), std::move(certChain), compressors);
    certManager->addCert(std::move(cert), true);
  }

  if (credentialMatchNeeded) {
    LOG(INFO) << "Credential did not match any keys provided.";
    return 1;
  }

  if (fallback && first) {
    // There was no default cert; this can only happen if you configured only
    // one cert (which is a delegated credential) and requested fallback. This
    // will invariably fail (as fallback doesn't support DC certs).
    LOG(ERROR) << "Fallback requested but no valid default cert found.";
    return 1;
  }

  serverContext->setCertManager(std::move(certManager));

  if (early) {
    serverContext->setEarlyDataSettings(
        true,
        {std::chrono::seconds(-10), std::chrono::seconds(10)},
        std::make_shared<SlidingBloomReplayCache>(240, 140000, 0.0005, &evb));
    serverContext->setMaxEarlyDataSize(earlyDataSize);
  }

  serverContext->setVersionFallbackEnabled(fallback);

  if (!alpns.empty()) {
    serverContext->setSupportedAlpns(std::move(alpns));
  }

  serverContext->setSupportedVersions(
      {ProtocolVersion::tls_1_3, ProtocolVersion::tls_1_3_28});
  FizzServerAcceptor acceptor(
      port, serverContext, loop, &evb, sslContext, uringAsync);
  if (!keyLogFile.empty()) {
    acceptor.setKeyLogWriter(std::make_unique<KeyLogWriter>(keyLogFile));
  }
  acceptor.setHttpEnabled(http);
  evb.loop();
  return 0;
}