Summary: Never really liked this code. Before this change, calls to connect were blocking. Because of this, we had to make use of promises and a bit of really not that good-looking code. So, this change makes connect non-blocking meaning that we make full use of our event handler. These changes contain: - CSR is not getting generated after each failed attempt. - Connect is no longer blocking. - Do not report events via the handler when explicitly disconnecting. Reviewed By: jknoxville Differential Revision: D46853228 fbshipit-source-id: 00e6a9c7c039a756175fe14982959e078d92bacb
254 lines
7.6 KiB
C++
254 lines
7.6 KiB
C++
/*
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
#include "ConnectionContextStore.h"
|
|
#include <folly/Optional.h>
|
|
#include <folly/json.h>
|
|
#include <folly/portability/SysStat.h>
|
|
#include <openssl/err.h>
|
|
#include <openssl/pem.h>
|
|
#include <openssl/pkcs12.h>
|
|
#include <openssl/x509.h>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <stdexcept>
|
|
#include <utility>
|
|
#include "CertificateUtils.h"
|
|
#include "Log.h"
|
|
namespace facebook {
|
|
namespace flipper {
|
|
|
|
static constexpr auto CSR_FILE_NAME = "app.csr";
|
|
static constexpr auto FLIPPER_CA_FILE_NAME = "sonarCA.crt";
|
|
static constexpr auto CLIENT_CERT_FILE_NAME = "device.crt";
|
|
static constexpr auto PRIVATE_KEY_FILE = "privateKey.pem";
|
|
static constexpr auto CERTIFICATE_FILE_NAME = "device.p12";
|
|
static constexpr auto CERTIFICATE_PASSWORD = "fl1pp3r";
|
|
static constexpr auto CONNECTION_CONFIG_FILE = "connection_config.json";
|
|
|
|
bool fileExists(std::string fileName);
|
|
std::string loadStringFromFile(std::string fileName);
|
|
void writeStringToFile(std::string content, std::string fileName);
|
|
|
|
ConnectionContextStore::ConnectionContextStore(DeviceData deviceData)
|
|
: deviceData_(deviceData) {}
|
|
|
|
bool ConnectionContextStore::hasRequiredFiles() {
|
|
std::string caCert =
|
|
loadStringFromFile(absoluteFilePath(FLIPPER_CA_FILE_NAME));
|
|
std::string clientCert =
|
|
loadStringFromFile(absoluteFilePath(CLIENT_CERT_FILE_NAME));
|
|
std::string privateKey =
|
|
loadStringFromFile(absoluteFilePath(PRIVATE_KEY_FILE));
|
|
std::string config =
|
|
loadStringFromFile(absoluteFilePath(CONNECTION_CONFIG_FILE));
|
|
|
|
if (caCert.empty() || clientCert.empty() || privateKey.empty() ||
|
|
config.empty()) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
std::string ConnectionContextStore::getCertificateSigningRequest() {
|
|
// Use in-memory CSR if already loaded
|
|
if (!csr_.empty()) {
|
|
return csr_;
|
|
}
|
|
|
|
// Attempt to load existing CSR from previous run of the app
|
|
csr_ = loadStringFromFile(absoluteFilePath(CSR_FILE_NAME));
|
|
if (!csr_.empty()) {
|
|
return csr_;
|
|
}
|
|
|
|
// Clean all state and generate a new one
|
|
resetState();
|
|
bool success = facebook::flipper::generateCertSigningRequest(
|
|
deviceData_.appId.c_str(),
|
|
absoluteFilePath(CSR_FILE_NAME).c_str(),
|
|
absoluteFilePath(PRIVATE_KEY_FILE).c_str());
|
|
if (!success) {
|
|
throw new std::runtime_error("Failed to generate CSR");
|
|
}
|
|
csr_ = loadStringFromFile(absoluteFilePath(CSR_FILE_NAME));
|
|
|
|
return csr_;
|
|
}
|
|
|
|
std::string ConnectionContextStore::getDeviceId() {
|
|
/* On android we can't reliably get the serial of the current device
|
|
So rely on our locally written config, which is provided by the
|
|
desktop app.
|
|
For backwards compatibility, when this isn't present, fall back to the
|
|
unreliable source. */
|
|
try {
|
|
std::string config =
|
|
loadStringFromFile(absoluteFilePath(CONNECTION_CONFIG_FILE));
|
|
auto maybeDeviceId = folly::parseJson(config)["deviceId"];
|
|
return maybeDeviceId.isString() ? maybeDeviceId.getString()
|
|
: deviceData_.deviceId;
|
|
} catch (std::exception&) {
|
|
return deviceData_.deviceId;
|
|
}
|
|
}
|
|
|
|
folly::Optional<FlipperCertificateExchangeMedium>
|
|
ConnectionContextStore::getLastKnownMedium() {
|
|
try {
|
|
auto configurationFilePath = absoluteFilePath(CONNECTION_CONFIG_FILE);
|
|
if (!fileExists(configurationFilePath)) {
|
|
return folly::none;
|
|
}
|
|
std::string data = loadStringFromFile(configurationFilePath);
|
|
auto config = folly::parseJson(data);
|
|
if (config.count("medium") == 0) {
|
|
return folly::none;
|
|
}
|
|
auto maybeMedium = config["medium"];
|
|
return maybeMedium.isInt()
|
|
? folly::Optional<FlipperCertificateExchangeMedium>{static_cast<
|
|
FlipperCertificateExchangeMedium>(maybeMedium.getInt())}
|
|
: folly::none;
|
|
} catch (std::exception&) {
|
|
return folly::none;
|
|
}
|
|
}
|
|
|
|
void ConnectionContextStore::storeConnectionConfig(folly::dynamic& config) {
|
|
std::string json = folly::toJson(config);
|
|
writeStringToFile(json, absoluteFilePath(CONNECTION_CONFIG_FILE));
|
|
}
|
|
|
|
std::string ConnectionContextStore::absoluteFilePath(
|
|
const char* filename) const {
|
|
#ifndef WIN32
|
|
return std::string(deviceData_.privateAppDirectory + "/sonar/" + filename);
|
|
#else
|
|
return std::string(deviceData_.privateAppDirectory + "\\sonar\\" + filename);
|
|
#endif
|
|
}
|
|
|
|
std::string ConnectionContextStore::getCertificateDirectoryPath() {
|
|
return absoluteFilePath("");
|
|
}
|
|
|
|
std::string ConnectionContextStore::getCACertificatePath() {
|
|
return absoluteFilePath(FLIPPER_CA_FILE_NAME);
|
|
}
|
|
|
|
std::string ConnectionContextStore::getPath(StoreItem storeItem) {
|
|
switch (storeItem) {
|
|
case CSR:
|
|
return absoluteFilePath(CSR_FILE_NAME);
|
|
case FLIPPER_CA:
|
|
return absoluteFilePath(FLIPPER_CA_FILE_NAME);
|
|
case CLIENT_CERT:
|
|
return absoluteFilePath(CLIENT_CERT_FILE_NAME);
|
|
case PRIVATE_KEY:
|
|
return absoluteFilePath(PRIVATE_KEY_FILE);
|
|
case CERTIFICATE:
|
|
return absoluteFilePath(CERTIFICATE_FILE_NAME);
|
|
case CONNECTION_CONFIG:
|
|
return absoluteFilePath(CONNECTION_CONFIG_FILE);
|
|
}
|
|
}
|
|
|
|
bool ConnectionContextStore::resetState() {
|
|
// Clear in-memory state
|
|
csr_ = "";
|
|
|
|
// Delete state from disk
|
|
std::string dirPath = absoluteFilePath("");
|
|
struct stat info;
|
|
if (stat(dirPath.c_str(), &info) != 0) {
|
|
int ret = mkdir(dirPath.c_str(), S_IRUSR | S_IWUSR | S_IXUSR);
|
|
return ret == 0;
|
|
} else if (info.st_mode & S_IFDIR) {
|
|
for (auto file :
|
|
{CSR_FILE_NAME,
|
|
FLIPPER_CA_FILE_NAME,
|
|
CLIENT_CERT_FILE_NAME,
|
|
PRIVATE_KEY_FILE,
|
|
CONNECTION_CONFIG_FILE,
|
|
CERTIFICATE_FILE_NAME}) {
|
|
std::remove(absoluteFilePath(file).c_str());
|
|
}
|
|
return true;
|
|
} else {
|
|
log("ERROR: Flipper path exists but is not a directory: " + dirPath);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
std::pair<std::string, std::string> ConnectionContextStore::getCertificate() {
|
|
auto cacertFilepath = absoluteFilePath(FLIPPER_CA_FILE_NAME);
|
|
auto certFilepath = absoluteFilePath(CLIENT_CERT_FILE_NAME);
|
|
auto keyFilepath = absoluteFilePath(PRIVATE_KEY_FILE);
|
|
auto certificate_path = absoluteFilePath(CERTIFICATE_FILE_NAME);
|
|
|
|
if (fileExists(certificate_path.c_str())) {
|
|
std::remove(certificate_path.c_str());
|
|
}
|
|
|
|
if (!facebook::flipper::generateCertPKCS12(
|
|
cacertFilepath.c_str(),
|
|
certFilepath.c_str(),
|
|
keyFilepath.c_str(),
|
|
certificate_path.c_str(),
|
|
CERTIFICATE_FILE_NAME,
|
|
CERTIFICATE_PASSWORD)) {
|
|
log("ERROR: Unable to genereate certificate pkcs#12");
|
|
return std::make_pair("", "");
|
|
}
|
|
|
|
return std::make_pair(certificate_path, std::string(CERTIFICATE_PASSWORD));
|
|
}
|
|
|
|
bool ConnectionContextStore::hasCertificateSigningRequest() const {
|
|
std::string csr = loadStringFromFile(absoluteFilePath(CSR_FILE_NAME));
|
|
return !csr.empty();
|
|
}
|
|
|
|
bool ConnectionContextStore::hasClientCertificate() const {
|
|
std::string clientCertificate =
|
|
loadStringFromFile(absoluteFilePath(CLIENT_CERT_FILE_NAME));
|
|
|
|
return !clientCertificate.empty();
|
|
}
|
|
|
|
std::string loadStringFromFile(std::string fileName) {
|
|
if (!fileExists(fileName)) {
|
|
return "";
|
|
}
|
|
std::stringstream buffer;
|
|
std::ifstream stream;
|
|
std::string line;
|
|
stream.open(fileName.c_str());
|
|
if (!stream) {
|
|
log("ERROR: Unable to open ifstream: " + fileName);
|
|
return "";
|
|
}
|
|
buffer << stream.rdbuf();
|
|
std::string s = buffer.str();
|
|
return s;
|
|
}
|
|
|
|
void writeStringToFile(std::string content, std::string fileName) {
|
|
std::ofstream out(fileName);
|
|
out << content;
|
|
}
|
|
|
|
bool fileExists(std::string fileName) {
|
|
struct stat buffer;
|
|
return stat(fileName.c_str(), &buffer) == 0;
|
|
}
|
|
|
|
} // namespace flipper
|
|
} // namespace facebook
|