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;
}