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