in fizz/tool/FizzClientCommand.cpp [494:853]
int fizzClientCommand(const std::vector<std::string>& args) {
std::string host = "localhost";
uint16_t port = 8443;
bool verify = false;
std::string certPath;
std::string keyPath;
std::string keyPass;
std::string caPath;
std::string caFile;
std::string pskSaveFile;
std::string pskLoadFile;
std::string keyLogFile;
bool reconnect = false;
std::string customSNI;
std::vector<std::string> alpns;
folly::Optional<std::vector<CertificateCompressionAlgorithm>> compAlgos;
bool early = false;
std::string proxyHost = "";
uint16_t proxyPort = 0;
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,
};
bool delegatedCredentials = false;
bool ech = false;
std::string echConfigsFile;
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 = {
{"-host", {true, [&host](const std::string& arg) { host = arg; }}},
{"-port", {true, [&port](const std::string& arg) {
port = portFromString(arg, false);
}}},
{"-connect", {true, [&host, &port](const std::string& arg) {
std::tie(host, port) = hostPortFromString(arg);
}}},
{"-verify", {false, [&verify](const std::string&) { verify = true; }}},
{"-cert", {true, [&certPath](const std::string& arg) { certPath = arg; }}},
{"-key", {true, [&keyPath](const std::string& arg) { keyPath = arg; }}},
{"-pass", {true, [&keyPass](const std::string& arg) { keyPass = arg; }}},
{"-capath", {true, [&caPath](const std::string& arg) { caPath = arg; }}},
{"-cafile", {true, [&caFile](const std::string& arg) { caFile = arg; }}},
{"-psk_save", {true, [&pskSaveFile](const std::string& arg) {
pskSaveFile = arg;
}}},
{"-psk_load", {true,[&pskLoadFile](const std::string& arg) {
pskLoadFile = arg;
}}},
{"-keylog", {true,[&keyLogFile](const std::string& arg) {
keyLogFile = arg;
}}},
{"-reconnect", {false, [&reconnect](const std::string&) {
reconnect = true;
}}},
{"-servername", {true, [&customSNI](const std::string& arg) {
customSNI = arg;
}}},
{"-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;
}
}}},
{"-early", {false, [&early](const std::string&) { early = true; }}},
{"-quiet", {false, [](const std::string&) {
FLAGS_minloglevel = google::GLOG_ERROR;
}}},
{"-httpproxy", {true, [&proxyHost, &proxyPort] (const std::string& arg) {
std::tie(proxyHost, proxyPort) = hostPortFromString(arg);
}}},
{"-ciphers", {true, [&ciphers](const std::string& arg) {
ciphers = splitParse<CipherSuite>(arg);
}}},
{"-sigschemes", {true, [&sigSchemes](const std::string& arg) {
sigSchemes = splitParse<SignatureScheme>(arg);
}}},
{"-curves", {true, [&groups](const std::string& arg) {
groups = splitParse<NamedGroup>(arg);
}}},
{"-delegatedcred", {false, [&delegatedCredentials](const std::string&) {
delegatedCredentials = true;
}}},
{"-ech", {false, [&ech](const std::string&) {
ech = true;
}}},
{"-echconfigs", {true, [&echConfigsFile](const std::string& arg) {
echConfigsFile = 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 (certPath.empty() != keyPath.empty()) {
LOG(ERROR) << "-cert and -key are both required when specified";
return 1;
}
EventBase evb(folly::EventBase::Options().setBackendFactory([uring,
uringAsync,
uringRegisterFds,
uringCapacity,
uringMaxSubmit,
uringMaxGet] {
return setupBackend(
uring,
uringAsync,
uringRegisterFds,
uringCapacity,
uringMaxSubmit,
uringMaxGet);
}));
auto clientContext = std::make_shared<FizzClientContext>();
if (!alpns.empty()) {
clientContext->setSupportedAlpns(std::move(alpns));
}
clientContext->setSupportedCiphers(ciphers);
clientContext->setSupportedSigSchemes(sigSchemes);
clientContext->setSupportedGroups(groups);
clientContext->setDefaultShares(groups);
clientContext->setSupportedVersions(
{ProtocolVersion::tls_1_3, ProtocolVersion::tls_1_3_28});
clientContext->setSendEarlyData(early);
if (compAlgos) {
auto mgr = std::make_shared<CertDecompressionManager>();
std::vector<std::shared_ptr<CertificateDecompressor>> decompressors;
for (const auto& algo : *compAlgos) {
switch (algo) {
case CertificateCompressionAlgorithm::zlib:
decompressors.push_back(
std::make_shared<ZlibCertificateDecompressor>());
break;
#ifdef FIZZ_TOOL_ENABLE_BROTLI
case CertificateCompressionAlgorithm::brotli:
decompressors.push_back(
std::make_shared<BrotliCertificateDecompressor>());
break;
#endif
#ifdef FIZZ_TOOL_ENABLE_ZSTD
case CertificateCompressionAlgorithm::zstd:
decompressors.push_back(
std::make_shared<ZstdCertificateDecompressor>());
break;
#endif
default:
LOG(WARNING) << "Don't know what decompressor to use for "
<< toString(algo) << ", ignoring...";
break;
}
}
mgr->setDecompressors(decompressors);
clientContext->setCertDecompressionManager(std::move(mgr));
}
X509StoreUniquePtr connStore;
X509StoreUniquePtr resumptionStore;
if (verify) {
// Initialize CA store first, if given.
if (!caPath.empty() || !caFile.empty()) {
connStore.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(connStore.get(), caFilePtr, caPathPtr) ==
0) {
LOG(ERROR) << "Failed to load CA certificates";
return 1;
}
resumptionStore.reset(connStore.get());
X509_STORE_up_ref(resumptionStore.get());
}
}
auto makeVerifier = [](X509StoreUniquePtr storePtr)
-> std::unique_ptr<StoreCertificateChain> {
std::unique_ptr<CertificateVerifier> verifier;
if (storePtr) {
verifier = std::make_unique<DefaultCertificateVerifier>(
VerificationContext::Client, std::move(storePtr));
} else {
verifier = std::make_unique<InsecureAcceptAnyCertificate>();
}
auto storeChainVerifier =
std::make_unique<StoreCertificateChain>(std::move(verifier));
return storeChainVerifier;
};
auto connVerifier = makeVerifier(std::move(connStore));
auto resumptionVerifier = makeVerifier(std::move(resumptionStore));
if (!certPath.empty()) {
std::string certData;
std::string keyData;
if (!readFile(certPath.c_str(), certData)) {
LOG(ERROR) << "Failed to read certificate";
return 1;
} else if (!readFile(keyPath.c_str(), keyData)) {
LOG(ERROR) << "Failed to read private key";
return 1;
}
std::unique_ptr<SelfCert> cert;
if (!keyPass.empty()) {
cert = CertUtils::makeSelfCert(certData, keyData, keyPass);
} else {
cert = CertUtils::makeSelfCert(certData, keyData);
}
clientContext->setClientCertificate(std::move(cert));
}
std::shared_ptr<ClientExtensions> extensions;
if (delegatedCredentials) {
clientContext->setFactory(
std::make_shared<extensions::DelegatedCredentialFactory>());
extensions =
std::make_shared<extensions::DelegatedCredentialClientExtension>(
clientContext->getSupportedSigSchemes());
}
folly::Optional<std::vector<ech::ECHConfig>> echConfigs = folly::none;
if (ech) {
// Use default ECH config values.
echConfigs = getDefaultECHConfigs();
}
if (!echConfigsFile.empty()) {
// Parse user set ECH configs.
auto echConfigsJson = readECHConfigsJson(echConfigsFile);
if (!echConfigsJson.has_value()) {
LOG(ERROR) << "Unable to load ECH configs from json file";
return 1;
}
auto gotECHConfigs = parseECHConfigs(echConfigsJson.value());
if (!gotECHConfigs.has_value()) {
LOG(ERROR)
<< "Unable to parse JSON file and make ECH config."
<< "Ensure the format matches what is expected."
<< "Rough example of format: {echconfigs: [${your ECH config here with all the fields..}]}"
<< "See FizzCommandCommonTest for a more concrete example.";
return 1;
}
echConfigs = std::move(gotECHConfigs.value());
}
try {
auto sni = customSNI.empty() ? host : customSNI;
auto connectHost = proxyHost.empty() ? host : proxyHost;
auto connectPort = proxyHost.empty() ? port : proxyPort;
auto proxiedHost = proxyHost.empty()
? std::string()
: folly::to<std::string>(host, ":", port);
SocketAddress addr(connectHost, connectPort, true);
Connection conn(
&evb,
clientContext,
sni,
std::move(connVerifier),
reconnect,
proxiedHost,
extensions,
std::move(echConfigs),
uringAsync);
Connection resumptionConn(
&evb,
clientContext,
sni,
std::move(resumptionVerifier),
false,
proxiedHost,
extensions,
folly::none,
uringAsync);
Connection* inputTarget = &conn;
if (reconnect) {
auto pskCache = std::make_shared<ResumptionPskCache>(
&evb, [&conn, &resumptionConn, addr]() {
conn.close();
resumptionConn.connect(addr);
});
clientContext->setPskCache(pskCache);
inputTarget = &resumptionConn;
}
if (!pskSaveFile.empty() || !pskLoadFile.empty()) {
auto pskCache =
std::make_shared<BasicPersistentPskCache>(pskSaveFile, pskLoadFile);
clientContext->setPskCache(pskCache);
}
if (!keyLogFile.empty()) {
conn.setKeyLogWriter(std::make_unique<KeyLogWriter>(keyLogFile));
}
TerminalInputHandler input(&evb, inputTarget);
conn.connect(addr);
evb.loop();
} catch (const std::exception& e) {
LOG(ERROR) << "Error: " << e.what();
return 1;
}
return 0;
}