fizz/tool/FizzCommandCommon.cpp (209 lines of code) (raw):
/*
* Copyright (c) 2018-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <fizz/protocol/ech/Types.h>
#include <fizz/tool/FizzCommandCommon.h>
#include <folly/FileUtil.h>
#include <folly/String.h>
using namespace folly;
namespace fizz {
namespace tool {
int parseArguments(
std::vector<std::string> argv,
FizzArgHandlerMap handlers,
std::function<void()> usageFunc) {
for (size_t idx = 2; idx < argv.size(); idx++) {
auto& argument = argv[idx];
auto handlerIter = handlers.find(argument);
// Ignore these.
if (argument == "-v" || argument == "-vmodule") {
idx++;
continue;
}
if (handlerIter != handlers.end()) {
auto& handlerInfo = handlerIter->second;
std::string variable;
if (handlerInfo.hasVariable) {
if (idx + 1 >= argv.size()) {
std::cerr << "Argument " << argument << " requires an parameter."
<< std::endl;
usageFunc();
return 1;
} else {
idx++;
variable = argv[idx];
}
}
handlerInfo.handler(variable);
} else {
std::cerr << "Unknown argument: " << argument << std::endl;
usageFunc();
return 1;
}
}
return 0;
}
std::unique_ptr<folly::EventBaseBackendBase> setupBackend(
FOLLY_MAYBE_UNUSED bool uring,
FOLLY_MAYBE_UNUSED bool uringAsync,
FOLLY_MAYBE_UNUSED bool uringRegisterFds,
FOLLY_MAYBE_UNUSED int32_t uringCapacity,
FOLLY_MAYBE_UNUSED int32_t uringMaxSubmit,
FOLLY_MAYBE_UNUSED int32_t uringMaxGet) {
#ifdef FIZZ_TOOL_ENABLE_IO_URING
if (uring) {
try {
folly::PollIoBackend::Options options;
options.setCapacity(static_cast<size_t>(uringCapacity))
.setMaxSubmit(static_cast<size_t>(uringMaxSubmit))
.setMaxGet(static_cast<size_t>(uringMaxGet))
.setUseRegisteredFds(uringRegisterFds);
return std::make_unique<folly::IoUringBackend>(options);
} catch (const std::exception& e) {
LOG(ERROR) << "Failed to create io_uring backend: " << e.what();
std::exit(1);
}
}
#endif
return folly::EventBase::getDefaultBackend();
}
TerminalInputHandler::TerminalInputHandler(
EventBase* evb,
InputHandlerCallback* cb)
: EventHandler(evb, folly::NetworkSocket::fromFd(0)), cb_(cb), evb_(evb) {
registerHandler(EventHandler::READ | EventHandler::PERSIST);
}
void TerminalInputHandler::handlerReady(uint16_t events) noexcept {
// Handle read ready on stdin, but only once we're connected.
if (events & EventHandler::READ && cb_->connected()) {
std::array<char, 512> buf;
int result = read(0, buf.data(), buf.size());
if (result > 0) {
cb_->write(IOBuf::wrapBuffer(buf.data(), result));
} else {
if (result < 0) {
LOG(ERROR) << "Error on terminal read: " << folly::errnoStr(errno);
}
hitEOF();
}
}
}
void TerminalInputHandler::hitEOF() {
evb_->runInLoop([cb_ = cb_]() { cb_->close(); });
}
std::vector<Extension> getExtensions(folly::StringPiece hex) {
auto buf = folly::IOBuf::copyBuffer(folly::unhexlify(hex.toString()));
auto outBuf = folly::IOBuf::create(0);
{
folly::io::Appender appender(outBuf.get(), 16);
detail::writeBuf<uint16_t>(buf, appender);
}
folly::io::Cursor cursor(outBuf.get());
std::vector<Extension> extensions;
if (!cursor.isAtEnd()) {
detail::readVector<uint16_t>(extensions, cursor);
}
CHECK(cursor.isAtEnd());
return extensions;
}
hpke::KEMId getKEMId(std::string kemStr) {
if (kemStr == "secp256r1") {
return hpke::KEMId::secp256r1;
} else if (kemStr == "secp384r1") {
return hpke::KEMId::secp384r1;
} else if (kemStr == "secp521r1") {
return hpke::KEMId::secp521r1;
} else if (kemStr == "x25519") {
return hpke::KEMId::x25519;
}
// Input doesn't match any KEM id.
throw std::runtime_error("Input doesn't match any KEM id");
};
folly::Optional<folly::dynamic> readECHConfigsJson(std::string echFile) {
if (echFile.empty()) {
throw std::runtime_error("No file provided");
}
std::string echConfigJson;
if (!folly::readFile(echFile.c_str(), echConfigJson)) {
throw std::runtime_error("Unable to read file provided");
}
auto json = folly::parseJson(echConfigJson);
if (!json.isObject()) {
throw std::runtime_error(
"Unable to parse ECH configs JSON. Please ensure the file matches what is expected."
"The format is roughly { echconfigs: [ ECHConfig{fields..} ] }."
"For an actual example, see the test file.");
}
return json;
}
folly::Optional<std::vector<ech::ECHConfig>> parseECHConfigs(
folly::dynamic json) {
auto getKDFId = [](std::string kdfStr) {
if (kdfStr == "Sha256") {
return hpke::KDFId::Sha256;
} else if (kdfStr == "Sha384") {
return hpke::KDFId::Sha384;
} else if (kdfStr == "Sha512") {
return hpke::KDFId::Sha512;
}
// Input doesn't match any KDF id.
throw std::runtime_error("Input doesn't match any KDF id");
};
auto getAeadId = [](std::string aeadStr) {
if (aeadStr == "TLS_AES_128_GCM_SHA256") {
return hpke::AeadId::TLS_AES_128_GCM_SHA256;
} else if (aeadStr == "TLS_AES_256_GCM_SHA384") {
return hpke::AeadId::TLS_AES_256_GCM_SHA384;
} else if (aeadStr == "TLS_CHACHA20_POLY1305_SHA256") {
return hpke::AeadId::TLS_CHACHA20_POLY1305_SHA256;
}
// Input doesn't match any Aead id.
throw std::runtime_error("Input doesn't match any Aead id");
};
auto echConfigs = std::vector<ech::ECHConfig>();
for (const auto& config : json["echconfigs"]) {
std::string version = config["version"].asString();
ech::ECHVersion echVersion;
if (version == "Draft9") {
echVersion = ech::ECHVersion::Draft9;
} else {
return folly::none;
}
ech::ECHConfigContentDraft configContent;
configContent.public_name =
folly::IOBuf::copyBuffer(config["public_name"].asString());
configContent.public_key = folly::IOBuf::copyBuffer(
folly::unhexlify(config["public_key"].asString()));
configContent.kem_id = getKEMId(config["kem_id"].asString());
configContent.maximum_name_length = config["maximum_name_length"].asInt();
// Get ciphersuites.
auto ciphersuites = std::vector<ech::ECHCipherSuite>();
for (size_t suiteIndex = 0; suiteIndex < config["cipher_suites"].size();
++suiteIndex) {
const auto& suite = config["cipher_suites"][suiteIndex];
ech::ECHCipherSuite parsedSuite;
parsedSuite.kdf_id = getKDFId(suite["kdf_id"].asString());
parsedSuite.aead_id = getAeadId(suite["aead_id"].asString());
ciphersuites.push_back(parsedSuite);
}
configContent.cipher_suites = ciphersuites;
// Get extensions.
configContent.extensions = getExtensions(config["extensions"].asString());
ech::ECHConfig parsedConfig;
parsedConfig.version = echVersion;
parsedConfig.ech_config_content = encode(std::move(configContent));
echConfigs.push_back(parsedConfig);
}
return std::move(echConfigs);
}
std::vector<ech::ECHConfig> getDefaultECHConfigs() {
// TODO: Generate a public and private key pair each time,
// and output it so the user can use it on the client side.
// This allows the user to more easily use ECH without needing
// to generate ECH configs and private keys themselves, without having
// the keys hardcoded.
LOG(INFO) << "Using default ECH configs.";
// Set the ECH config content.
ech::ECHConfigContentDraft echConfigContent;
echConfigContent.public_name = folly::IOBuf::copyBuffer("publicname");
echConfigContent.cipher_suites = {ech::ECHCipherSuite{
hpke::KDFId::Sha256, hpke::AeadId::TLS_AES_128_GCM_SHA256}};
echConfigContent.maximum_name_length = 1000;
folly::StringPiece cookie{"002c00080006636f6f6b6965"};
echConfigContent.extensions = getExtensions(cookie);
// Set default public key (which corresponds to the private key set on the
// server side).
echConfigContent.public_key = folly::IOBuf::copyBuffer(folly::unhexlify(
"8a07563949fac6232936ed6f36c4fa735930ecdeaef6734e314aeac35a56fd0a"));
// Corresponds to the public key set above.
echConfigContent.kem_id = hpke::KEMId::x25519;
// Construct an ECH config to pass in to the client.
ech::ECHConfig echConfig;
echConfig.version = ech::ECHVersion::Draft9;
echConfig.ech_config_content = encode(std::move(echConfigContent));
auto configs = std::vector<ech::ECHConfig>();
configs.push_back(std::move(echConfig));
return configs;
}
} // namespace tool
} // namespace fizz