proxygen
FizzClientCommand.cpp
Go to the documentation of this file.
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 
11 #include <folly/FileUtil.h>
13 
14 #include <iostream>
15 #include <string>
16 #include <vector>
17 
18 using namespace fizz::client;
19 using namespace folly;
20 
21 namespace fizz {
22 namespace tool {
23 namespace {
24 
25 void printUsage() {
26  // clang-format off
27  std::cerr
28  << "Usage: s_client args\n"
29  << "\n"
30  << "Supported arguments:\n"
31  << " -host host (use connect instead)\n"
32  << " -port port (use connect instead)\n"
33  << " -connect host:port (set the address to connect to. Default: localhost:4433)\n"
34  << " -verify (enable server cert verification. Default: false)\n"
35  << " -cert cert (PEM format client certificate to send if requested. Default: none)\n"
36  << " -key key (PEM format private key for client certificate. Default: none)\n"
37  << " -pass password (private key password. Default: none)\n"
38  << " -capaths d1:... (colon-separated paths to directories of CA certs used for verification)\n"
39  << " -cafile file (path to bundle of CA certs used for verification)\n"
40  << " -reconnect (after connecting, open another connection using a psk. Default: false)\n"
41  << " -servername name (server name to send in SNI. Default: same as host)\n"
42  << " -alpn alpn1,... (comma-separated list of ALPNs to send. Default: none)\n"
43  << " -early (enables sending early data during resumption. Default: false)\n"
44  << " -quiet (hide informational logging. Default: false)\n";
45  // clang-format on
46 }
47 
48 class Connection : public AsyncSocket::ConnectCallback,
52  public InputHandlerCallback {
53  public:
54  Connection(
55  EventBase* evb,
56  std::shared_ptr<FizzClientContext> clientContext,
58  std::shared_ptr<const CertificateVerifier> verifier,
59  bool willResume)
60  : evb_(evb),
61  clientContext_(clientContext),
62  sni_(sni),
63  verifier_(std::move(verifier)),
64  willResume_(willResume) {}
65 
66  void connect(const SocketAddress& addr) {
68  sock_->connect(this, addr);
69  }
70 
71  void close() override {
72  if (transport_) {
73  transport_->close();
74  } else if (sock_) {
75  sock_->close();
76  }
77  }
78 
79  void connectErr(const AsyncSocketException& ex) noexcept override {
80  LOG(ERROR) << "Connect error: " << ex.what();
82  }
83 
84  void connectSuccess() noexcept override {
85  LOG(INFO) << (willResume_ ? "Initial connection" : "Connection")
86  << " established.";
89  transport_->connect(this, verifier_, sni_, sni_);
90  }
91 
92  void fizzHandshakeSuccess(AsyncFizzClient* /*client*/) noexcept override {
93  if (transport_->isReplaySafe()) {
94  printHandshakeSuccess();
95  } else {
96  LOG(INFO) << "Early handshake success.";
97  transport_->setReplaySafetyCallback(this);
98  }
99  transport_->setReadCB(this);
100  }
101 
102  void fizzHandshakeError(
103  AsyncFizzClient* /*client*/,
104  exception_wrapper ex) noexcept override {
105  LOG(ERROR) << "Handshake error: " << ex.what();
107  }
108 
109  void getReadBuffer(void** /* bufReturn */, size_t* /* lenReturn */) override {
110  throw std::runtime_error("getReadBuffer not implemented");
111  }
112 
113  void readDataAvailable(size_t /* len */) noexcept override {
114  throw std::runtime_error("readDataAvailable not implemented");
115  }
116 
117  bool isBufferMovable() noexcept override {
118  return true;
119  }
120 
121  void readBufferAvailable(std::unique_ptr<IOBuf> buf) noexcept override {
122  std::cout << StringPiece(buf->coalesce()).str();
123  }
124 
125  void readEOF() noexcept override {
126  LOG(INFO) << (willResume_ ? "Initial EOF" : "EOF");
127  if (!willResume_) {
129  }
130  }
131 
132  void readErr(const AsyncSocketException& ex) noexcept override {
133  LOG(ERROR) << "Read error: " << ex.what();
135  }
136 
137  void onReplaySafe() override {
138  printHandshakeSuccess();
139  }
140 
141  bool connected() const override {
142  return transport_ && !transport_->connecting() && transport_->good();
143  }
144 
145  void write(std::unique_ptr<IOBuf> msg) override {
146  if (transport_) {
147  transport_->writeChain(nullptr, std::move(msg));
148  }
149  }
150 
151  private:
152  void printHandshakeSuccess() {
153  auto& state = transport_->getState();
154  auto serverCert = state.serverCert();
155  auto clientCert = state.clientCert();
156  LOG(INFO) << (willResume_ ? "Initial handshake" : "Handshake")
157  << " succeeded.";
158  LOG(INFO) << " TLS Version: " << toString(*state.version());
159  LOG(INFO) << " Cipher Suite: " << toString(*state.cipher());
160  LOG(INFO) << " Named Group: "
161  << (state.group() ? toString(*state.group()) : "(none)");
162  LOG(INFO) << " Signature Scheme: "
163  << (state.sigScheme() ? toString(*state.sigScheme()) : "(none)");
164  LOG(INFO) << " PSK: " << toString(*state.pskType());
165  LOG(INFO) << " PSK Mode: "
166  << (state.pskMode() ? toString(*state.pskMode()) : "(none)");
167  LOG(INFO) << " Key Exchange Type: " << toString(*state.keyExchangeType());
168  LOG(INFO) << " Early: " << toString(*state.earlyDataType());
169  LOG(INFO) << " Server Identity: "
170  << (serverCert ? serverCert->getIdentity() : "(none)");
171  LOG(INFO) << " Client Identity: "
172  << (clientCert ? clientCert->getIdentity() : "(none)");
173  if (serverCert) {
174  folly::ssl::BioUniquePtr bio(BIO_new(BIO_s_mem()));
175  if (!PEM_write_bio_X509(bio.get(), serverCert->getX509().get())) {
176  LOG(ERROR) << " Couldn't convert server certificate to PEM: "
177  << SSLContext::getErrors();
178  } else {
179  BUF_MEM* bptr = nullptr;
180  BIO_get_mem_ptr(bio.get(), &bptr);
181  LOG(INFO) << " Server Certificate:\n"
182  << std::string(bptr->data, bptr->length);
183  }
184  }
185 
186  if (clientCert) {
187  folly::ssl::BioUniquePtr bio(BIO_new(BIO_s_mem()));
188  if (!PEM_write_bio_X509(bio.get(), clientCert->getX509().get())) {
189  LOG(ERROR) << " Couldn't convert client certificate to PEM: "
190  << SSLContext::getErrors();
191  } else {
192  BUF_MEM* bptr = nullptr;
193  BIO_get_mem_ptr(bio.get(), &bptr);
194  LOG(INFO) << " Client Certificate:\n"
195  << std::string(bptr->data, bptr->length);
196  }
197  }
198  LOG(INFO) << " ALPN: " << state.alpn().value_or("(none)");
199  }
200 
202  std::shared_ptr<FizzClientContext> clientContext_;
204  std::shared_ptr<const CertificateVerifier> verifier_;
207  bool willResume_{false};
208 };
209 
210 class ResumptionPskCache : public BasicPskCache {
211  public:
212  ResumptionPskCache(folly::EventBase* evb, folly::Function<void()> callback)
213  : evb_(evb), callback_(std::move(callback)) {}
214 
215  void putPsk(const std::string& identity, CachedPsk psk) override {
216  BasicPskCache::putPsk(identity, std::move(psk));
217  if (callback_) {
219  callback_ = nullptr;
220  }
221  }
222 
223  private:
226 };
227 
228 } // namespace
229 
230 int fizzClientCommand(const std::vector<std::string>& args) {
231  std::string host = "localhost";
232  uint16_t port = 4433;
233  bool verify = false;
234  std::string certPath;
235  std::string keyPath;
236  std::string keyPass;
237  std::string caPath;
238  std::string caFile;
239  bool reconnect = false;
240  std::string customSNI;
241  std::vector<std::string> alpns;
242  bool early = false;
243 
244  // clang-format off
245  FizzArgHandlerMap handlers = {
246  {"-host", {true, [&host](const std::string& arg) { host = arg; }}},
247  {"-port", {true, [&port](const std::string& arg) {
248  port = portFromString(arg, false);
249  }}},
250  {"-connect", {true, [&host, &port](const std::string& arg) {
251  size_t colonIdx = arg.find(':');
252  if (colonIdx == std::string::npos) {
253  throw std::runtime_error("-connect requires a host:port pair.");
254  }
255  host = arg.substr(0, colonIdx);
256  port = portFromString(arg.substr(colonIdx+1), false);
257  }}},
258  {"-verify", {false, [&verify](const std::string&) { verify = true; }}},
259  {"-cert", {true, [&certPath](const std::string& arg) { certPath = arg; }}},
260  {"-key", {true, [&keyPath](const std::string& arg) { keyPath = arg; }}},
261  {"-pass", {true, [&keyPass](const std::string& arg) { keyPass = arg; }}},
262  {"-capath", {true, [&caPath](const std::string& arg) { caPath = arg; }}},
263  {"-cafile", {true, [&caFile](const std::string& arg) { caFile = arg; }}},
264  {"-reconnect", {false, [&reconnect](const std::string&) {
265  reconnect = true;
266  }}},
267  {"-servername", {true, [&customSNI](const std::string& arg) {
268  customSNI = arg;
269  }}},
270  {"-alpn", {true, [&alpns](const std::string& arg) {
271  alpns.clear();
272  auto remainder = arg;
273  for (auto commaPos = remainder.find(',');
274  commaPos != std::string::npos;
275  commaPos = remainder.find(',')) {
276  alpns.push_back(remainder.substr(0, commaPos));
277  remainder = remainder.substr(commaPos+1);
278  }
279 
280  alpns.push_back(remainder);
281  }}},
282  {"-early", {false, [&early](const std::string&) { early = true; }}},
283  {"-quiet", {false, [](const std::string&) {
284  FLAGS_minloglevel = google::GLOG_ERROR;
285  }}}
286  };
287  // clang-format on
288 
289  try {
290  if (parseArguments(args, handlers, printUsage)) {
291  // Parsing failed, return
292  return 1;
293  }
294  } catch (const std::exception& e) {
295  LOG(ERROR) << "Error: " << e.what();
296  return 1;
297  }
298 
299  // Sanity check input.
300  if (certPath.empty() != keyPath.empty()) {
301  LOG(ERROR) << "-cert and -key are both required when specified";
302  return 1;
303  }
304 
305  EventBase evb;
306  std::shared_ptr<const CertificateVerifier> verifier;
307  auto clientContext = std::make_shared<FizzClientContext>();
308 
309  if (!alpns.empty()) {
310  clientContext->setSupportedAlpns(std::move(alpns));
311  }
312 
313  clientContext->setSupportedVersions(
315  clientContext->setSendEarlyData(early);
316 
317  if (verify) {
318  // Initialize CA store first, if given.
320  if (!caPath.empty() || !caFile.empty()) {
321  storePtr.reset(X509_STORE_new());
322  auto caFilePtr = caFile.empty() ? nullptr : caFile.c_str();
323  auto caPathPtr = caPath.empty() ? nullptr : caPath.c_str();
324 
325  if (X509_STORE_load_locations(storePtr.get(), caFilePtr, caPathPtr) ==
326  0) {
327  LOG(ERROR) << "Failed to load CA certificates";
328  return 1;
329  }
330  }
331 
332  verifier = std::make_shared<const DefaultCertificateVerifier>(
334  }
335 
336  if (!certPath.empty()) {
337  std::string certData;
338  std::string keyData;
339  if (!readFile(certPath.c_str(), certData)) {
340  LOG(ERROR) << "Failed to read certificate";
341  return 1;
342  } else if (!readFile(keyPath.c_str(), keyData)) {
343  LOG(ERROR) << "Failed to read private key";
344  return 1;
345  }
346 
347  std::unique_ptr<SelfCert> cert;
348  if (!keyPass.empty()) {
349  cert = CertUtils::makeSelfCert(certData, keyData, keyPass);
350  } else {
351  cert = CertUtils::makeSelfCert(certData, keyData);
352  }
353  clientContext->setClientCertificate(std::move(cert));
354  }
355 
356  try {
357  SocketAddress addr(host, port, true);
358  auto sni = customSNI.empty() ? host : customSNI;
359  Connection conn(&evb, clientContext, sni, verifier, reconnect);
360  Connection resumptionConn(&evb, clientContext, sni, verifier, false);
361  Connection* inputTarget = &conn;
362  if (reconnect) {
363  auto pskCache = std::make_shared<ResumptionPskCache>(
364  &evb, [&conn, &resumptionConn, addr]() {
365  conn.close();
366  resumptionConn.connect(addr);
367  });
368  clientContext->setPskCache(pskCache);
369  inputTarget = &resumptionConn;
370  }
371  TerminalInputHandler input(&evb, inputTarget);
372  conn.connect(addr);
373  evb.loop();
374  } catch (const std::exception& e) {
375  LOG(ERROR) << "Error: " << e.what();
376  return 1;
377  }
378 
379  return 0;
380 }
381 
382 } // namespace tool
383 } // namespace fizz
folly::StringPiece toString(StateEnum state)
Definition: State.cpp:16
bool readFile(int fd, Container &out, size_t num_bytes=std::numeric_limits< size_t >::max())
Definition: FileUtil.h:125
void verify(int extras)
void write(const T &in, folly::io::Appender &appender)
Definition: Types-inl.h:112
int connect(NetworkSocket s, const sockaddr *name, socklen_t namelen)
Definition: NetOps.cpp:94
static std::unique_ptr< SelfCert > makeSelfCert(std::string certData, std::string keyData, const std::vector< std::shared_ptr< CertificateCompressor >> &compressors={})
std::unique_ptr< BIO, BioDeleter > BioUniquePtr
constexpr detail::Map< Move > move
Definition: Base-inl.h:2567
STL namespace.
—— Concurrent Priority Queue Implementation ——
Definition: AtomicBitSet.h:29
requires E e noexcept(noexcept(s.error(std::move(e))))
int parseArguments(std::vector< std::string > argv, FizzArgHandlerMap handlers, std::function< void()> usageFunc)
std::unique_ptr< AsyncFizzClientT, folly::DelayedDestruction::Destructor > UniquePtr
void putPsk(const std::string &identity, CachedPsk psk) override
Definition: PskCache.h:76
AsyncFizzClientT< ClientStateMachine > AsyncFizzClient
constexpr bool empty() const
Definition: Range.h:443
void runInLoop(LoopCallback *callback, bool thisIteration=false)
Definition: EventBase.cpp:520
EventBase * evb_
Optional< std::string > sni_
void terminateLoopSoon()
Definition: EventBase.cpp:493
uint16_t portFromString(const std::string &portStr, bool serverSide)
std::unique_ptr< X509_STORE, X509StoreDeleter > X509StoreUniquePtr
Definition: Actions.h:16
AsyncSocket::UniquePtr sock_
std::shared_ptr< const Cert > serverCert
std::shared_ptr< FizzClientContext > clientContext_
bool willResume_
StringPiece sni
const char * string
Definition: Conv.cpp:212
AsyncFizzClient::UniquePtr transport_
std::shared_ptr< const CertificateVerifier > verifier_
std::shared_ptr< const Cert > clientCert
std::map< std::string, FizzCommandArgHandlerInfo > FizzArgHandlerMap
folly::Function< void()> callback_
int fizzClientCommand(const std::vector< std::string > &args)
Range< const char * > StringPiece
int close(NetworkSocket s)
Definition: NetOps.cpp:90
ThreadPoolListHook * addr
std::unique_ptr< AsyncSocket, Destructor > UniquePtr
Definition: AsyncSocket.h:83
state
Definition: http_parser.c:272