1 /*
2  *  Copyright (c) 2018-present, Facebook, Inc.
3  *  All rights reserved.
4  *
5  *  This source code is licensed under the BSD-style license found in the
6  *  LICENSE file in the root directory of this source tree.
7  */
8 
9 #include <fizz/client/AsyncFizzClient.h>
10 #include <fizz/crypto/hpke/Utils.h>
11 #include <fizz/extensions/delegatedcred/DelegatedCredentialClientExtension.h>
12 #include <fizz/extensions/delegatedcred/DelegatedCredentialFactory.h>
13 #ifdef FIZZ_TOOL_ENABLE_BROTLI
14 #include <fizz/protocol/BrotliCertificateDecompressor.h>
15 #endif
16 #include <fizz/protocol/ZlibCertificateDecompressor.h>
17 #ifdef FIZZ_TOOL_ENABLE_ZSTD
18 #include <fizz/protocol/ZstdCertificateDecompressor.h>
19 #endif
20 #include <fizz/client/PskSerializationUtils.h>
21 #include <fizz/protocol/DefaultCertificateVerifier.h>
22 #include <fizz/tool/CertificateVerifiers.h>
23 #include <fizz/tool/FizzCommandCommon.h>
24 #include <fizz/util/KeyLogWriter.h>
25 #include <fizz/util/Parse.h>
26 #include <folly/FileUtil.h>
27 #include <folly/Format.h>
28 #include <folly/io/async/SSLContext.h>
29 #include <folly/ssl/OpenSSLCertUtils.h>
30 
31 #include <iostream>
32 #include <string>
33 #include <vector>
34 
35 using namespace fizz::client;
36 using namespace folly;
37 using namespace folly::ssl;
38 
39 namespace fizz {
40 namespace tool {
41 namespace {
42 
printUsage()43 void printUsage() {
44   // clang-format off
45   std::cerr
46     << "Usage: client args\n"
47     << "\n"
48     << "Supported arguments:\n"
49     << " -host host               (use connect instead)\n"
50     << " -port port               (use connect instead)\n"
51     << " -connect host:port       (set the address to connect to. Default: localhost:8443)\n"
52     << " -verify                  (enable server cert verification. Default: false)\n"
53     << " -cert cert               (PEM format client certificate to send if requested. Default: none)\n"
54     << " -key key                 (PEM format private key for client certificate. Default: none)\n"
55     << " -pass password           (private key password. Default: none)\n"
56     << " -capath directory        (path to a directory of hashed formed CA certs used for verification.\n"
57     << "                           The directory should contain one certificate or CRL per file in PEM format,\n"
58     << "                           with a file name of the form hash.N for a certificate, or hash.rN for a CRL.\n"
59     << "                           Refer to https://www.openssl.org/docs/man1.1.1/man1/rehash.html for how to generate such files.)\n"
60     << " -cafile file             (path to a bundle file of CA certs used for verification; can be used with or without -capath.)\n"
61     << " -reconnect               (after connecting, open another connection using a psk. Default: false)\n"
62     << " -psk_save file           (after connecting, save the psk to file )\n"
63     << " -psk_load file           (given file that contains a serialized psk, deserialize psk and open a connection with it)\n"
64     << " -keylog file             (dump TLS secrets to a NSS key log file; for debugging purpose only)\n"
65     << " -servername name         (server name to send in SNI. Default: same as host)\n"
66     << " -alpn alpn1:...          (colon-separated list of ALPNs to send. Default: none)\n"
67     << " -ciphers c1:...          (colon-separated list of ciphers in preference order. Default:\n"
68     << "                           TLS_AES_128_GCM_SHA256,TLS_AES_256_GCM_SHA384,TLS_CHACHA20_POLY1305_SHA256)\n"
69     << " -sigschemes s1:...       (colon-separated list of signature schemes in preference order.\n"
70     << " -curves c1:...           (colon-separated list of supported ECDSA curves. Default: secp256r1, x25519)\n"
71     << " -certcompression a1:...  (enables certificate compression support for given algorithms. Default: None)\n"
72     << " -early                   (enables sending early data during resumption. Default: false)\n"
73     << " -quiet                   (hide informational logging. Default: false)\n"
74     << " -v verbosity             (set verbose log level for VLOG macros. Default: 0)\n"
75     << " -vmodule m1=N,...        (set per-module verbose log level for VLOG macros. Default: none)\n"
76     << " -httpproxy host:port     (set an HTTP proxy to use. Default: none)\n"
77     << " -delegatedcred           (enable delegated credential support. Default: false)\n"
78     << " -ech                     (use default values to simulate the sending of an encrypted client hello.)\n"
79     << " -echconfigs file         (path to read ECH configs from. Format for contents is JSON.)\n"
80     << "                          (JSON format: {echconfigs: [${your ECH config here with all the fields..}]})\n"
81     << "                          (See FizzCommandCommonTest for an example.)\n"
82     << "                          (Note: Setting ech configs implicitly enables ECH.)\n"
83 #ifdef FIZZ_TOOL_ENABLE_IO_URING
84     << " -io_uring                (use io_uring for I/O. Default: false)\n"
85     << " -io_uring_capacity N     (backend capacity for io_uring. Default: 128)\n"
86     << " -io_uring_max_submit N   (maximum submit size for io_uring. Default: 64)\n"
87     << " -io_uring_max_get N      (maximum get size for io_uring. Default: no limit)\n"
88     << " -io_uring_register_fds   (use registered fds with io_uring. Default: false)\n"
89     << " -io_uring_async_recv     (use async recv for io_uring. Default: false)\n"
90 #endif
91   ;
92   // clang-format on
93 }
94 
95 class Connection : public AsyncSocket::ConnectCallback,
96                    public AsyncFizzClient::HandshakeCallback,
97                    public AsyncTransportWrapper::ReadCallback,
98                    public AsyncTransport::ReplaySafetyCallback,
99                    public InputHandlerCallback,
100                    public SecretCollector {
101  public:
Connection(EventBase * evb,std::shared_ptr<FizzClientContext> clientContext,Optional<std::string> sni,std::shared_ptr<StoreCertificateChain> verifier,bool willResume,std::string proxyTarget,std::shared_ptr<ClientExtensions> extensions,folly::Optional<std::vector<ech::ECHConfig>> echConfigs,bool registerEventCallback)102   Connection(
103       EventBase* evb,
104       std::shared_ptr<FizzClientContext> clientContext,
105       Optional<std::string> sni,
106       std::shared_ptr<StoreCertificateChain> verifier,
107       bool willResume,
108       std::string proxyTarget,
109       std::shared_ptr<ClientExtensions> extensions,
110       folly::Optional<std::vector<ech::ECHConfig>> echConfigs,
111       bool registerEventCallback)
112       : evb_(evb),
113         clientContext_(clientContext),
114         sni_(sni),
115         verifier_(std::move(verifier)),
116         willResume_(willResume),
117         proxyTarget_(proxyTarget),
118         extensions_(extensions),
119         echConfigs_(std::move(echConfigs)),
120         registerEventCallback_(registerEventCallback) {}
121 
connect(const SocketAddress & addr)122   void connect(const SocketAddress& addr) {
123     sock_ = AsyncSocket::UniquePtr(new AsyncSocket(evb_));
124     sock_->connect(this, addr);
125   }
126 
close()127   void close() override {
128     if (transport_) {
129       transport_->close();
130     } else if (sock_) {
131       sock_->close();
132     }
133   }
134 
connectErr(const AsyncSocketException & ex)135   void connectErr(const AsyncSocketException& ex) noexcept override {
136     LOG(ERROR) << "Connect error: " << ex.what();
137     evb_->terminateLoopSoon();
138   }
139 
connectSuccess()140   void connectSuccess() noexcept override {
141     LOG(INFO) << (willResume_ ? "Initial connection" : "Connection")
142               << " established.";
143     if (!proxyTarget_.empty()) {
144       auto connectCommand = IOBuf::create(0);
145       folly::io::Appender appender(connectCommand.get(), 10);
146       format(
147           "CONNECT {} HTTP/1.1\r\n"
148           "Host: {}\r\n\r\n",
149           proxyTarget_,
150           proxyTarget_)(appender);
151       sock_->setReadCB(this);
152       sock_->writeChain(nullptr, std::move(connectCommand));
153     } else {
154       doHandshake();
155     }
156   }
157 
doHandshake()158   void doHandshake() {
159     AsyncFizzBase::TransportOptions transportOpts;
160     transportOpts.registerEventCallback = registerEventCallback_;
161     transport_ = AsyncFizzClient::UniquePtr(new AsyncFizzClient(
162         std::move(sock_),
163         clientContext_,
164         extensions_,
165         std::move(transportOpts)));
166     transport_->setSecretCallback(this);
167     auto echConfigs = echConfigs_;
168     transport_->connect(this, verifier_, sni_, sni_, std::move(echConfigs));
169   }
170 
fizzHandshakeSuccess(AsyncFizzClient *)171   void fizzHandshakeSuccess(AsyncFizzClient* /*client*/) noexcept override {
172     if (transport_->isReplaySafe()) {
173       printHandshakeSuccess();
174     } else {
175       LOG(INFO) << "Early handshake success.";
176       transport_->setReplaySafetyCallback(this);
177     }
178     transport_->setReadCB(this);
179   }
180 
fizzHandshakeError(AsyncFizzClient *,exception_wrapper ex)181   void fizzHandshakeError(
182       AsyncFizzClient* /*client*/,
183       exception_wrapper ex) noexcept override {
184     LOG(ERROR) << "Handshake error: " << ex.what();
185     evb_->terminateLoopSoon();
186   }
187 
getReadBuffer(void ** bufReturn,size_t * lenReturn)188   void getReadBuffer(void** bufReturn, size_t* lenReturn) override {
189     *bufReturn = readBuf_.data();
190     *lenReturn = readBuf_.size();
191   }
192 
readDataAvailable(size_t len)193   void readDataAvailable(size_t len) noexcept override {
194     readBufferAvailable(IOBuf::copyBuffer(readBuf_.data(), len));
195   }
196 
isBufferMovable()197   bool isBufferMovable() noexcept override {
198     return true;
199   }
200 
readBufferAvailable(std::unique_ptr<IOBuf> buf)201   void readBufferAvailable(std::unique_ptr<IOBuf> buf) noexcept override {
202     if (!transport_) {
203       if (!proxyResponseBuffer_) {
204         proxyResponseBuffer_ = std::move(buf);
205       } else {
206         proxyResponseBuffer_->prependChain(std::move(buf));
207       }
208       auto currentContents = StringPiece(proxyResponseBuffer_->coalesce());
209       auto statusEndPos = currentContents.find("\r\n");
210       if (statusEndPos == std::string::npos) {
211         // No complete line yet
212         return;
213       }
214       auto statusLine = currentContents.subpiece(0, statusEndPos).str();
215       unsigned int httpVer;
216       unsigned int httpStatus;
217       if (sscanf(statusLine.c_str(), "HTTP/1.%u %u", &httpVer, &httpStatus) !=
218           2) {
219         LOG(ERROR) << "Failed to parse status: " << statusLine;
220         close();
221       }
222 
223       if (httpStatus / 100 != 2) {
224         LOG(ERROR) << "Got non-200 status: " << httpStatus;
225         close();
226       }
227 
228       auto endPos = currentContents.find("\r\n\r\n");
229       if (endPos != std::string::npos) {
230         endPos += 4;
231         auto remainder = currentContents.subpiece(endPos);
232         sock_->setReadCB(nullptr);
233         if (remainder.size()) {
234           sock_->setPreReceivedData(IOBuf::copyBuffer(remainder));
235         }
236         doHandshake();
237       }
238     } else {
239       std::cout << StringPiece(buf->coalesce()).str();
240     }
241   }
242 
readEOF()243   void readEOF() noexcept override {
244     LOG(INFO) << (willResume_ ? "Initial EOF" : "EOF");
245     if (!willResume_) {
246       evb_->terminateLoopSoon();
247     }
248   }
249 
readErr(const AsyncSocketException & ex)250   void readErr(const AsyncSocketException& ex) noexcept override {
251     LOG(ERROR) << "Read error: " << ex.what();
252     evb_->terminateLoopSoon();
253   }
254 
onReplaySafe()255   void onReplaySafe() override {
256     printHandshakeSuccess();
257   }
258 
connected() const259   bool connected() const override {
260     return transport_ && !transport_->connecting() && transport_->good();
261   }
262 
write(std::unique_ptr<IOBuf> msg)263   void write(std::unique_ptr<IOBuf> msg) override {
264     if (transport_) {
265       transport_->writeChain(nullptr, std::move(msg));
266     }
267   }
268 
setKeyLogWriter(std::unique_ptr<KeyLogWriter> keyLogWriter)269   void setKeyLogWriter(std::unique_ptr<KeyLogWriter> keyLogWriter) {
270     keyLogger_ = std::move(keyLogWriter);
271   }
272 
273  private:
printHandshakeSuccess()274   void printHandshakeSuccess() {
275     auto& state = transport_->getState();
276     auto serverCert = state.serverCert();
277     auto clientCert = state.clientCert();
278     LOG(INFO) << (willResume_ ? "Initial handshake" : "Handshake")
279               << " succeeded.";
280     LOG(INFO) << "  TLS Version: " << toString(*state.version());
281     LOG(INFO) << "  Cipher Suite:  " << toString(*state.cipher());
282     LOG(INFO) << "  Named Group: "
283               << (state.group() ? toString(*state.group()) : "(none)");
284     LOG(INFO) << "  Signature Scheme: "
285               << (state.sigScheme() ? toString(*state.sigScheme()) : "(none)");
286     LOG(INFO) << "  PSK: " << toString(*state.pskType());
287     LOG(INFO) << "  PSK Mode: "
288               << (state.pskMode() ? toString(*state.pskMode()) : "(none)");
289     LOG(INFO) << "  Key Exchange Type: " << toString(*state.keyExchangeType());
290     LOG(INFO) << "  Early: " << toString(*state.earlyDataType());
291     LOG(INFO) << "  Server Identity: "
292               << (serverCert ? serverCert->getIdentity() : "(none)");
293     LOG(INFO) << "  Client Identity: "
294               << (clientCert ? clientCert->getIdentity() : "(none)");
295 
296     LOG(INFO) << "  Certificate Chain:";
297     auto certs = verifier_->getCerts();
298     for (size_t i = 0; i < certs.size(); i++) {
299       auto x509Cert = certs[i]->getX509();
300       LOG(INFO) << "   " << i
301                 << " s:" << OpenSSLCertUtils::getSubject(*x509Cert).value();
302       LOG(INFO) << "     i:" << OpenSSLCertUtils::getIssuer(*x509Cert).value();
303     }
304 
305     if (auto opensslCert = dynamic_cast<const OpenSSLCert*>(serverCert.get())) {
306       BioUniquePtr bio(BIO_new(BIO_s_mem()));
307       if (!PEM_write_bio_X509(bio.get(), opensslCert->getX509().get())) {
308         LOG(ERROR) << "  Couldn't convert server certificate to PEM: "
309                    << SSLContext::getErrors();
310       } else {
311         BUF_MEM* bptr = nullptr;
312         BIO_get_mem_ptr(bio.get(), &bptr);
313         LOG(INFO) << "  Server Certificate:\n"
314                   << std::string(bptr->data, bptr->length);
315       }
316     }
317 
318     if (auto opensslCert = dynamic_cast<const OpenSSLCert*>(clientCert.get())) {
319       BioUniquePtr bio(BIO_new(BIO_s_mem()));
320       if (!PEM_write_bio_X509(bio.get(), opensslCert->getX509().get())) {
321         LOG(ERROR) << "  Couldn't convert client certificate to PEM: "
322                    << SSLContext::getErrors();
323       } else {
324         BUF_MEM* bptr = nullptr;
325         BIO_get_mem_ptr(bio.get(), &bptr);
326         LOG(INFO) << "  Client Certificate:\n"
327                   << std::string(bptr->data, bptr->length);
328       }
329     }
330     LOG(INFO) << "  Server Certificate Compression: "
331               << (state.serverCertCompAlgo()
332                       ? toString(*state.serverCertCompAlgo())
333                       : "(none)");
334     LOG(INFO) << "  ALPN: " << state.alpn().value_or("(none)");
335     LOG(INFO) << "  Client Random: "
336               << folly::hexlify(*transport_->getClientRandom());
337     LOG(INFO) << "  Secrets:";
338     LOG(INFO) << "    External PSK Binder: " << secretStr(externalPskBinder_);
339     LOG(INFO) << "    Resumption PSK Binder: "
340               << secretStr(resumptionPskBinder_);
341     LOG(INFO) << "    Early Exporter: " << secretStr(earlyExporterSecret_);
342     LOG(INFO) << "    Early Client Data: "
343               << secretStr(clientEarlyTrafficSecret_);
344     LOG(INFO) << "    Client Handshake: "
345               << secretStr(clientHandshakeTrafficSecret_);
346     LOG(INFO) << "    Server Handshake: "
347               << secretStr(serverHandshakeTrafficSecret_);
348     LOG(INFO) << "    Exporter Master: " << secretStr(exporterMasterSecret_);
349     LOG(INFO) << "    Resumption Master: "
350               << secretStr(resumptionMasterSecret_);
351     LOG(INFO) << "    Client Traffic: " << secretStr(clientAppTrafficSecret_);
352     LOG(INFO) << "    Server Traffic: " << secretStr(serverAppTrafficSecret_);
353 
354     if (echConfigs_.has_value()) {
355       printECHSuccess(state);
356     }
357 
358     if (keyLogger_) {
359       if (clientEarlyTrafficSecret_) {
360         keyLogger_->write(
361             *transport_->getClientRandom(),
362             KeyLogWriter::Label::CLIENT_EARLY_TRAFFIC_SECRET,
363             folly::range(*clientEarlyTrafficSecret_));
364       }
365       if (clientHandshakeTrafficSecret_) {
366         keyLogger_->write(
367             *transport_->getClientRandom(),
368             KeyLogWriter::Label::CLIENT_HANDSHAKE_TRAFFIC_SECRET,
369             folly::range(*clientHandshakeTrafficSecret_));
370       }
371       if (serverHandshakeTrafficSecret_) {
372         keyLogger_->write(
373             *transport_->getClientRandom(),
374             KeyLogWriter::Label::SERVER_HANDSHAKE_TRAFFIC_SECRET,
375             folly::range(*serverHandshakeTrafficSecret_));
376       }
377       if (exporterMasterSecret_) {
378         keyLogger_->write(
379             *transport_->getClientRandom(),
380             KeyLogWriter::Label::EXPORTER_SECRET,
381             folly::range(*exporterMasterSecret_));
382       }
383       if (clientAppTrafficSecret_) {
384         keyLogger_->write(
385             *transport_->getClientRandom(),
386             KeyLogWriter::Label::CLIENT_TRAFFIC_SECRET_0,
387             folly::range(*clientAppTrafficSecret_));
388       }
389       if (serverAppTrafficSecret_) {
390         keyLogger_->write(
391             *transport_->getClientRandom(),
392             KeyLogWriter::Label::SERVER_TRAFFIC_SECRET_0,
393             folly::range(*serverAppTrafficSecret_));
394       }
395     }
396   }
397 
printECHSuccess(const State & state)398   void printECHSuccess(const State& state) {
399     LOG(INFO) << "  Encrypted client hello (ECH) enabled: ";
400     auto echResult = state.encodedECH().has_value()
401         ? "    Successfully sent the server an ECH"
402         : "    Unable to send server an ECH";
403     LOG(INFO) << echResult;
404 
405     // Get ECH config content
406     const auto& echConfig = echConfigs_.value()[0];
407     const auto& configContent = echConfig.ech_config_content;
408     folly::io::Cursor cursor(configContent.get());
409     auto echConfigContent = decode<ech::ECHConfigContentDraft>(cursor);
410 
411     auto ciphersuite = echConfigContent.cipher_suites[0];
412     LOG(INFO) << "    Hash function: "
413               << toString(getHashFunction(ciphersuite.kdf_id));
414     LOG(INFO) << "    Cipher Suite: "
415               << toString(getCipherSuite(ciphersuite.aead_id));
416     LOG(INFO) << "    Named Group: "
417               << toString(getKexGroup(echConfigContent.kem_id));
418     LOG(INFO) << "    Fake SNI Used: "
419               << echConfigContent.public_name->clone()->moveToFbString();
420   }
421 
422   EventBase* evb_;
423   std::shared_ptr<FizzClientContext> clientContext_;
424   Optional<std::string> sni_;
425   std::shared_ptr<StoreCertificateChain> verifier_;
426   AsyncSocket::UniquePtr sock_;
427   AsyncFizzClient::UniquePtr transport_;
428   bool willResume_{false};
429   std::array<char, 8192> readBuf_;
430   std::string proxyTarget_;
431   std::unique_ptr<IOBuf> proxyResponseBuffer_;
432   std::shared_ptr<ClientExtensions> extensions_;
433   std::unique_ptr<KeyLogWriter> keyLogger_;
434   folly::Optional<std::vector<ech::ECHConfig>> echConfigs_;
435   bool registerEventCallback_{false};
436 };
437 
438 class ResumptionPskCache : public BasicPskCache {
439  public:
ResumptionPskCache(folly::EventBase * evb,folly::Function<void ()> callback)440   ResumptionPskCache(folly::EventBase* evb, folly::Function<void()> callback)
441       : evb_(evb), callback_(std::move(callback)) {}
442 
putPsk(const std::string & identity,CachedPsk psk)443   void putPsk(const std::string& identity, CachedPsk psk) override {
444     BasicPskCache::putPsk(identity, std::move(psk));
445     if (callback_) {
446       evb_->runInLoop(std::move(callback_));
447       callback_ = nullptr;
448     }
449   }
450 
451  private:
452   folly::EventBase* evb_;
453   folly::Function<void()> callback_;
454 };
455 
456 class BasicPersistentPskCache : public BasicPskCache {
457  public:
BasicPersistentPskCache(std::string save_file,std::string load_file)458   BasicPersistentPskCache(std::string save_file, std::string load_file)
459       : saveFile_(save_file), loadFile_(load_file) {}
460 
putPsk(const std::string &,CachedPsk psk)461   void putPsk(const std::string& /* unused */, CachedPsk psk) override {
462     if (saveFile_.empty()) {
463       return;
464     }
465     std::string serializedPsk = serializePsk(psk);
466     if (writeFile(serializedPsk, saveFile_.c_str())) {
467       LOG(INFO) << "\n Saved PSK to " << saveFile_ << " \n";
468     } else {
469       LOG(ERROR) << "\n Unable to save PSK " << saveFile_ << " \n";
470     }
471   }
472 
getPsk(const std::string &)473   folly::Optional<CachedPsk> getPsk(const std::string& /* unused */) override {
474     if (loadFile_.empty()) {
475       return folly::none;
476     }
477     LOG(INFO) << "\n Loading PSK from " << loadFile_ << " \n";
478     std::string serializedPsk;
479     readFile(loadFile_.c_str(), serializedPsk);
480     try {
481       return deserializePsk(serializedPsk, OpenSSLFactory());
482     } catch (const std::exception& e) {
483       LOG(ERROR) << "Error deserializing: " << loadFile_ << "\n" << e.what();
484       throw;
485     }
486   }
487 
488  private:
489   std::string saveFile_, loadFile_;
490 };
491 
492 } // namespace
493 
fizzClientCommand(const std::vector<std::string> & args)494 int fizzClientCommand(const std::vector<std::string>& args) {
495   std::string host = "localhost";
496   uint16_t port = 8443;
497   bool verify = false;
498   std::string certPath;
499   std::string keyPath;
500   std::string keyPass;
501   std::string caPath;
502   std::string caFile;
503   std::string pskSaveFile;
504   std::string pskLoadFile;
505   std::string keyLogFile;
506   bool reconnect = false;
507   std::string customSNI;
508   std::vector<std::string> alpns;
509   folly::Optional<std::vector<CertificateCompressionAlgorithm>> compAlgos;
510   bool early = false;
511   std::string proxyHost = "";
512   uint16_t proxyPort = 0;
513   std::vector<CipherSuite> ciphers {
514     CipherSuite::TLS_AES_128_GCM_SHA256, CipherSuite::TLS_AES_256_GCM_SHA384,
515 #if FOLLY_OPENSSL_HAS_CHACHA
516         CipherSuite::TLS_CHACHA20_POLY1305_SHA256,
517 #endif
518   };
519   std::vector<SignatureScheme> sigSchemes{
520       SignatureScheme::ecdsa_secp256r1_sha256,
521       SignatureScheme::ecdsa_secp384r1_sha384,
522       SignatureScheme::ecdsa_secp521r1_sha512,
523       SignatureScheme::rsa_pss_sha256,
524   };
525   std::vector<NamedGroup> groups{
526       NamedGroup::secp256r1,
527       NamedGroup::x25519,
528   };
529   bool delegatedCredentials = false;
530   bool ech = false;
531   std::string echConfigsFile;
532   bool uring = false;
533   bool uringAsync = false;
534   bool uringRegisterFds = false;
535   int32_t uringCapacity = 128;
536   int32_t uringMaxSubmit = 64;
537   int32_t uringMaxGet = -1;
538 
539   // clang-format off
540   FizzArgHandlerMap handlers = {
541     {"-host", {true, [&host](const std::string& arg) { host = arg; }}},
542     {"-port", {true, [&port](const std::string& arg) {
543         port = portFromString(arg, false);
544     }}},
545     {"-connect", {true, [&host, &port](const std::string& arg) {
546         std::tie(host, port) = hostPortFromString(arg);
547      }}},
548     {"-verify", {false, [&verify](const std::string&) { verify = true; }}},
549     {"-cert", {true, [&certPath](const std::string& arg) { certPath = arg; }}},
550     {"-key", {true, [&keyPath](const std::string& arg) { keyPath = arg; }}},
551     {"-pass", {true, [&keyPass](const std::string& arg) { keyPass = arg; }}},
552     {"-capath", {true, [&caPath](const std::string& arg) { caPath = arg; }}},
553     {"-cafile", {true, [&caFile](const std::string& arg) { caFile = arg; }}},
554     {"-psk_save", {true, [&pskSaveFile](const std::string& arg) {
555       pskSaveFile = arg;
556     }}},
557     {"-psk_load", {true,[&pskLoadFile](const std::string& arg) {
558       pskLoadFile = arg;
559     }}},
560     {"-keylog", {true,[&keyLogFile](const std::string& arg) {
561       keyLogFile = arg;
562     }}},
563     {"-reconnect", {false, [&reconnect](const std::string&) {
564         reconnect = true;
565     }}},
566     {"-servername", {true, [&customSNI](const std::string& arg) {
567         customSNI = arg;
568     }}},
569     {"-alpn", {true, [&alpns](const std::string& arg) {
570         alpns.clear();
571         folly::split(",", arg, alpns);
572     }}},
573     {"-certcompression", {true, [&compAlgos](const std::string& arg) {
574         try {
575           compAlgos = splitParse<CertificateCompressionAlgorithm>(arg);
576         } catch (const std::exception& e) {
577           LOG(ERROR) << "Error parsing certificate compression algorithms: " << e.what();
578           throw;
579         }
580     }}},
581     {"-early", {false, [&early](const std::string&) { early = true; }}},
582     {"-quiet", {false, [](const std::string&) {
583         FLAGS_minloglevel = google::GLOG_ERROR;
584     }}},
585     {"-httpproxy", {true, [&proxyHost, &proxyPort] (const std::string& arg) {
586         std::tie(proxyHost, proxyPort) = hostPortFromString(arg);
587     }}},
588     {"-ciphers", {true, [&ciphers](const std::string& arg) {
589         ciphers = splitParse<CipherSuite>(arg);
590     }}},
591     {"-sigschemes", {true, [&sigSchemes](const std::string& arg) {
592         sigSchemes = splitParse<SignatureScheme>(arg);
593     }}},
594     {"-curves", {true, [&groups](const std::string& arg) {
595         groups = splitParse<NamedGroup>(arg);
596     }}},
597     {"-delegatedcred", {false, [&delegatedCredentials](const std::string&) {
598         delegatedCredentials = true;
599     }}},
600     {"-ech", {false, [&ech](const std::string&) {
601         ech = true;
602     }}},
603     {"-echconfigs", {true, [&echConfigsFile](const std::string& arg) {
604         echConfigsFile = arg;
605     }}}
606 #ifdef FIZZ_TOOL_ENABLE_IO_URING
607     ,{"-io_uring", {false, [&uring](const std::string&) { uring = true; }}},
608     {"-io_uring_async_recv", {false, [&uringAsync](const std::string&) {
609         uringAsync = true;
610     }}},
611     {"-io_uring_register_fds", {false, [&uringRegisterFds](const std::string&) {
612         uringRegisterFds = true;
613     }}},
614     {"-io_uring_capacity", {true, [&uringCapacity](const std::string& arg) {
615         uringCapacity = folly::to<int32_t>(arg);
616     }}},
617     {"-io_uring_max_get", {true, [&uringMaxGet](const std::string& arg) {
618         uringMaxGet = folly::to<int32_t>(arg);
619     }}},
620     {"-io_uring_max_submit", {true, [&uringMaxSubmit](const std::string& arg) {
621         uringMaxSubmit = folly::to<int32_t>(arg);
622     }}}
623 #endif
624   };
625   // clang-format on
626 
627   try {
628     if (parseArguments(args, handlers, printUsage)) {
629       // Parsing failed, return
630       return 1;
631     }
632   } catch (const std::exception& e) {
633     LOG(ERROR) << "Error: " << e.what();
634     return 1;
635   }
636 
637   // Sanity check input.
638   if (certPath.empty() != keyPath.empty()) {
639     LOG(ERROR) << "-cert and -key are both required when specified";
640     return 1;
641   }
642 
643   EventBase evb(folly::EventBase::Options().setBackendFactory([uring,
644                                                                uringAsync,
645                                                                uringRegisterFds,
646                                                                uringCapacity,
647                                                                uringMaxSubmit,
648                                                                uringMaxGet] {
649     return setupBackend(
650         uring,
651         uringAsync,
652         uringRegisterFds,
653         uringCapacity,
654         uringMaxSubmit,
655         uringMaxGet);
656   }));
657 
658   auto clientContext = std::make_shared<FizzClientContext>();
659 
660   if (!alpns.empty()) {
661     clientContext->setSupportedAlpns(std::move(alpns));
662   }
663 
664   clientContext->setSupportedCiphers(ciphers);
665   clientContext->setSupportedSigSchemes(sigSchemes);
666   clientContext->setSupportedGroups(groups);
667   clientContext->setDefaultShares(groups);
668 
669   clientContext->setSupportedVersions(
670       {ProtocolVersion::tls_1_3, ProtocolVersion::tls_1_3_28});
671   clientContext->setSendEarlyData(early);
672 
673   if (compAlgos) {
674     auto mgr = std::make_shared<CertDecompressionManager>();
675     std::vector<std::shared_ptr<CertificateDecompressor>> decompressors;
676     for (const auto& algo : *compAlgos) {
677       switch (algo) {
678         case CertificateCompressionAlgorithm::zlib:
679           decompressors.push_back(
680               std::make_shared<ZlibCertificateDecompressor>());
681           break;
682 #ifdef FIZZ_TOOL_ENABLE_BROTLI
683         case CertificateCompressionAlgorithm::brotli:
684           decompressors.push_back(
685               std::make_shared<BrotliCertificateDecompressor>());
686           break;
687 #endif
688 #ifdef FIZZ_TOOL_ENABLE_ZSTD
689         case CertificateCompressionAlgorithm::zstd:
690           decompressors.push_back(
691               std::make_shared<ZstdCertificateDecompressor>());
692           break;
693 #endif
694         default:
695           LOG(WARNING) << "Don't know what decompressor to use for "
696                        << toString(algo) << ", ignoring...";
697           break;
698       }
699     }
700     mgr->setDecompressors(decompressors);
701     clientContext->setCertDecompressionManager(std::move(mgr));
702   }
703 
704   X509StoreUniquePtr connStore;
705   X509StoreUniquePtr resumptionStore;
706   if (verify) {
707     // Initialize CA store first, if given.
708     if (!caPath.empty() || !caFile.empty()) {
709       connStore.reset(X509_STORE_new());
710       auto caFilePtr = caFile.empty() ? nullptr : caFile.c_str();
711       auto caPathPtr = caPath.empty() ? nullptr : caPath.c_str();
712 
713       if (X509_STORE_load_locations(connStore.get(), caFilePtr, caPathPtr) ==
714           0) {
715         LOG(ERROR) << "Failed to load CA certificates";
716         return 1;
717       }
718       resumptionStore.reset(connStore.get());
719       X509_STORE_up_ref(resumptionStore.get());
720     }
721   }
722 
723   auto makeVerifier = [](X509StoreUniquePtr storePtr)
724       -> std::unique_ptr<StoreCertificateChain> {
725     std::unique_ptr<CertificateVerifier> verifier;
726     if (storePtr) {
727       verifier = std::make_unique<DefaultCertificateVerifier>(
728           VerificationContext::Client, std::move(storePtr));
729     } else {
730       verifier = std::make_unique<InsecureAcceptAnyCertificate>();
731     }
732     auto storeChainVerifier =
733         std::make_unique<StoreCertificateChain>(std::move(verifier));
734 
735     return storeChainVerifier;
736   };
737 
738   auto connVerifier = makeVerifier(std::move(connStore));
739   auto resumptionVerifier = makeVerifier(std::move(resumptionStore));
740 
741   if (!certPath.empty()) {
742     std::string certData;
743     std::string keyData;
744     if (!readFile(certPath.c_str(), certData)) {
745       LOG(ERROR) << "Failed to read certificate";
746       return 1;
747     } else if (!readFile(keyPath.c_str(), keyData)) {
748       LOG(ERROR) << "Failed to read private key";
749       return 1;
750     }
751 
752     std::unique_ptr<SelfCert> cert;
753     if (!keyPass.empty()) {
754       cert = CertUtils::makeSelfCert(certData, keyData, keyPass);
755     } else {
756       cert = CertUtils::makeSelfCert(certData, keyData);
757     }
758     clientContext->setClientCertificate(std::move(cert));
759   }
760 
761   std::shared_ptr<ClientExtensions> extensions;
762   if (delegatedCredentials) {
763     clientContext->setFactory(
764         std::make_shared<extensions::DelegatedCredentialFactory>());
765     extensions =
766         std::make_shared<extensions::DelegatedCredentialClientExtension>(
767             clientContext->getSupportedSigSchemes());
768   }
769 
770   folly::Optional<std::vector<ech::ECHConfig>> echConfigs = folly::none;
771 
772   if (ech) {
773     // Use default ECH config values.
774     echConfigs = getDefaultECHConfigs();
775   }
776 
777   if (!echConfigsFile.empty()) {
778     // Parse user set ECH configs.
779     auto echConfigsJson = readECHConfigsJson(echConfigsFile);
780     if (!echConfigsJson.has_value()) {
781       LOG(ERROR) << "Unable to load ECH configs from json file";
782       return 1;
783     }
784     auto gotECHConfigs = parseECHConfigs(echConfigsJson.value());
785     if (!gotECHConfigs.has_value()) {
786       LOG(ERROR)
787           << "Unable to parse JSON file and make ECH config."
788           << "Ensure the format matches what is expected."
789           << "Rough example of format: {echconfigs: [${your ECH config here with all the fields..}]}"
790           << "See FizzCommandCommonTest for a more concrete example.";
791       return 1;
792     }
793     echConfigs = std::move(gotECHConfigs.value());
794   }
795 
796   try {
797     auto sni = customSNI.empty() ? host : customSNI;
798     auto connectHost = proxyHost.empty() ? host : proxyHost;
799     auto connectPort = proxyHost.empty() ? port : proxyPort;
800     auto proxiedHost = proxyHost.empty()
801         ? std::string()
802         : folly::to<std::string>(host, ":", port);
803 
804     SocketAddress addr(connectHost, connectPort, true);
805     Connection conn(
806         &evb,
807         clientContext,
808         sni,
809         std::move(connVerifier),
810         reconnect,
811         proxiedHost,
812         extensions,
813         std::move(echConfigs),
814         uringAsync);
815     Connection resumptionConn(
816         &evb,
817         clientContext,
818         sni,
819         std::move(resumptionVerifier),
820         false,
821         proxiedHost,
822         extensions,
823         folly::none,
824         uringAsync);
825 
826     Connection* inputTarget = &conn;
827     if (reconnect) {
828       auto pskCache = std::make_shared<ResumptionPskCache>(
829           &evb, [&conn, &resumptionConn, addr]() {
830             conn.close();
831             resumptionConn.connect(addr);
832           });
833       clientContext->setPskCache(pskCache);
834       inputTarget = &resumptionConn;
835     }
836     if (!pskSaveFile.empty() || !pskLoadFile.empty()) {
837       auto pskCache =
838           std::make_shared<BasicPersistentPskCache>(pskSaveFile, pskLoadFile);
839       clientContext->setPskCache(pskCache);
840     }
841     if (!keyLogFile.empty()) {
842       conn.setKeyLogWriter(std::make_unique<KeyLogWriter>(keyLogFile));
843     }
844     TerminalInputHandler input(&evb, inputTarget);
845     conn.connect(addr);
846     evb.loop();
847   } catch (const std::exception& e) {
848     LOG(ERROR) << "Error: " << e.what();
849     return 1;
850   }
851 
852   return 0;
853 }
854 
855 } // namespace tool
856 } // namespace fizz
857