ConnectionContext to expose client certificate in PKCS #12 format
Summary: RSocket plays nicely with Folly and OpenSSL. Flipper WebSocket-client uses SocketRocket which instead relies on Apple's NSInputStream and NSOutputStream types. SSL options can be set to secure the communication in both. Unfortunately, Apple APIs are a bit limited on the supported cryptographic formats it can accept as arguments. SSL options require the client certificate to be set in PKCS #12 format, contrary to the existing PEM format used by RSocket. This change adds a method to the ConnectionContext which converts and saves the client certificate in PKCS #12 format. The method is always expected to succeed as it will only be called once a valid client certificate is available. An unlikely failure will raise an exception. Reviewed By: fabiomassimo Differential Revision: D30074334 fbshipit-source-id: 91a475d080569cc339b649c7302b1f28793c7de7
This commit is contained in:
committed by
Facebook GitHub Bot
parent
a5b83dc148
commit
43179a7ef4
@@ -10,20 +10,31 @@
|
||||
#include <fcntl.h>
|
||||
#include <folly/portability/Fcntl.h>
|
||||
#include <folly/portability/SysStat.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/pkcs12.h>
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace facebook {
|
||||
namespace flipper {
|
||||
|
||||
void free(
|
||||
void generateCertSigningRequest_free(
|
||||
EVP_PKEY* pKey,
|
||||
X509_REQ* x509_req,
|
||||
BIGNUM* bne,
|
||||
BIO* privateKey,
|
||||
BIO* csrBio);
|
||||
|
||||
void generateCertPKCS12_free(
|
||||
X509* cacert,
|
||||
X509* cert,
|
||||
EVP_PKEY* pKey,
|
||||
STACK_OF(X509) * cacertstack,
|
||||
PKCS12* pkcs12bundle);
|
||||
|
||||
bool generateCertSigningRequest(
|
||||
const char* appId,
|
||||
const char* csrFile,
|
||||
@@ -58,13 +69,13 @@ bool generateCertSigningRequest(
|
||||
BN_set_flags(bne, BN_FLG_CONSTTIME);
|
||||
ret = BN_set_word(bne, e);
|
||||
if (ret != 1) {
|
||||
free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
generateCertSigningRequest_free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = RSA_generate_key_ex(rsa, bits, bne, NULL);
|
||||
if (ret != 1) {
|
||||
free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
generateCertSigningRequest_free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -73,19 +84,19 @@ bool generateCertSigningRequest(
|
||||
int privateKeyFd =
|
||||
open(privateKeyFile, O_CREAT | O_WRONLY, S_IWUSR | S_IRUSR);
|
||||
if (privateKeyFd < 0) {
|
||||
free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
generateCertSigningRequest_free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
return -1;
|
||||
}
|
||||
FILE* privateKeyFp = fdopen(privateKeyFd, "w");
|
||||
if (privateKeyFp == NULL) {
|
||||
free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
generateCertSigningRequest_free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
return -1;
|
||||
}
|
||||
privateKey = BIO_new_fp(privateKeyFp, BIO_CLOSE);
|
||||
ret =
|
||||
PEM_write_bio_RSAPrivateKey(privateKey, rsa, NULL, NULL, 0, NULL, NULL);
|
||||
if (ret != 1) {
|
||||
free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
generateCertSigningRequest_free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@@ -94,13 +105,13 @@ bool generateCertSigningRequest(
|
||||
|
||||
ret = BIO_flush(privateKey);
|
||||
if (ret != 1) {
|
||||
free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
generateCertSigningRequest_free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = X509_REQ_set_version(x509_req, nVersion);
|
||||
if (ret != 1) {
|
||||
free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
generateCertSigningRequest_free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -115,7 +126,7 @@ bool generateCertSigningRequest(
|
||||
-1,
|
||||
0);
|
||||
if (ret != 1) {
|
||||
free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
generateCertSigningRequest_free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -128,7 +139,7 @@ bool generateCertSigningRequest(
|
||||
-1,
|
||||
0);
|
||||
if (ret != 1) {
|
||||
free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
generateCertSigningRequest_free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -141,7 +152,7 @@ bool generateCertSigningRequest(
|
||||
-1,
|
||||
0);
|
||||
if (ret != 1) {
|
||||
free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
generateCertSigningRequest_free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -154,7 +165,7 @@ bool generateCertSigningRequest(
|
||||
-1,
|
||||
0);
|
||||
if (ret != 1) {
|
||||
free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
generateCertSigningRequest_free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -167,20 +178,20 @@ bool generateCertSigningRequest(
|
||||
-1,
|
||||
0);
|
||||
if (ret != 1) {
|
||||
free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
generateCertSigningRequest_free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = X509_REQ_set_pubkey(x509_req, pKey);
|
||||
if (ret != 1) {
|
||||
free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
generateCertSigningRequest_free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = X509_REQ_sign(
|
||||
x509_req, pKey, EVP_sha256()); // returns x509_req->signature->length
|
||||
if (ret <= 0) {
|
||||
free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
generateCertSigningRequest_free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -188,29 +199,29 @@ bool generateCertSigningRequest(
|
||||
// Write CSR to a file
|
||||
int csrFd = open(csrFile, O_CREAT | O_WRONLY, S_IWUSR | S_IRUSR);
|
||||
if (csrFd < 0) {
|
||||
free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
generateCertSigningRequest_free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
return -1;
|
||||
}
|
||||
FILE* csrFp = fdopen(csrFd, "w");
|
||||
if (csrFp == NULL) {
|
||||
free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
generateCertSigningRequest_free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
return -1;
|
||||
}
|
||||
csrBio = BIO_new_fp(csrFp, BIO_CLOSE);
|
||||
ret = PEM_write_bio_X509_REQ(csrBio, x509_req);
|
||||
if (ret != 1) {
|
||||
free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
generateCertSigningRequest_free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
ret = BIO_flush(csrBio);
|
||||
|
||||
free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
generateCertSigningRequest_free(pKey, x509_req, bne, privateKey, csrBio);
|
||||
return (ret == 1);
|
||||
}
|
||||
|
||||
void free(
|
||||
void generateCertSigningRequest_free(
|
||||
EVP_PKEY* pKey,
|
||||
X509_REQ* x509_req,
|
||||
BIGNUM* bne,
|
||||
@@ -223,5 +234,156 @@ void free(
|
||||
BIO_free_all(csrBio);
|
||||
}
|
||||
|
||||
bool generateCertPKCS12(
|
||||
const char* cacertFilepath,
|
||||
const char* certFilepath,
|
||||
const char* keyFilepath,
|
||||
const char* pkcs12Filepath,
|
||||
const char* pkcs12Name,
|
||||
const char* pkcs12Password) {
|
||||
X509 *cert = NULL, *cacert = NULL;
|
||||
STACK_OF(X509)* cacertstack = NULL;
|
||||
PKCS12* pkcs12bundle = NULL;
|
||||
EVP_PKEY* cert_privkey = NULL;
|
||||
FILE *cacertfile = NULL, *certfile = NULL, *keyfile = NULL,
|
||||
*pkcs12file = NULL;
|
||||
int bytes = 0;
|
||||
|
||||
/* 1) These function calls are essential to make PEM_read and
|
||||
* other openssl functions work.
|
||||
*/
|
||||
OpenSSL_add_all_algorithms();
|
||||
ERR_load_crypto_strings();
|
||||
|
||||
/* 2) load the certificate's private key
|
||||
*/
|
||||
if ((cert_privkey = EVP_PKEY_new()) == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(keyfile = fopen(keyFilepath, "r"))) {
|
||||
generateCertPKCS12_free(
|
||||
cacert, cert, cert_privkey, cacertstack, pkcs12bundle);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(cert_privkey = PEM_read_PrivateKey(keyfile, NULL, NULL, NULL))) {
|
||||
fclose(keyfile);
|
||||
generateCertPKCS12_free(
|
||||
cacert, cert, cert_privkey, cacertstack, pkcs12bundle);
|
||||
return false;
|
||||
}
|
||||
|
||||
fclose(keyfile);
|
||||
|
||||
/* 3) Load the corresponding certificate
|
||||
*/
|
||||
if (!(certfile = fopen(certFilepath, "r"))) {
|
||||
generateCertPKCS12_free(
|
||||
cacert, cert, cert_privkey, cacertstack, pkcs12bundle);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(cert = PEM_read_X509(certfile, NULL, NULL, NULL))) {
|
||||
fclose(certfile);
|
||||
generateCertPKCS12_free(
|
||||
cacert, cert, cert_privkey, cacertstack, pkcs12bundle);
|
||||
return false;
|
||||
}
|
||||
|
||||
fclose(certfile);
|
||||
|
||||
/* 4) Load the CA certificate who signed it
|
||||
*/
|
||||
if (!(cacertfile = fopen(cacertFilepath, "r"))) {
|
||||
generateCertPKCS12_free(
|
||||
cacert, cert, cert_privkey, cacertstack, pkcs12bundle);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(cacert = PEM_read_X509(cacertfile, NULL, NULL, NULL))) {
|
||||
fclose(cacertfile);
|
||||
generateCertPKCS12_free(
|
||||
cacert, cert, cert_privkey, cacertstack, pkcs12bundle);
|
||||
return false;
|
||||
}
|
||||
|
||||
fclose(cacertfile);
|
||||
|
||||
/* 5) Load the CA certificate on the stack
|
||||
*/
|
||||
if ((cacertstack = sk_X509_new_null()) == NULL) {
|
||||
generateCertPKCS12_free(
|
||||
cacert, cert, cert_privkey, cacertstack, pkcs12bundle);
|
||||
return false;
|
||||
}
|
||||
|
||||
sk_X509_push(cacertstack, cacert);
|
||||
|
||||
/* 6) we create the PKCS12 structure and fill it with our data
|
||||
*/
|
||||
if ((pkcs12bundle = PKCS12_new()) == NULL) {
|
||||
generateCertPKCS12_free(
|
||||
cacert, cert, cert_privkey, cacertstack, pkcs12bundle);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* values of zero use the openssl default values */
|
||||
pkcs12bundle = PKCS12_create(
|
||||
const_cast<char*>(pkcs12Password), // certbundle access password
|
||||
const_cast<char*>(pkcs12Name), // friendly certificate name
|
||||
cert_privkey, // the certificate private key
|
||||
cert, // the main certificate
|
||||
cacertstack, // stack of CA cert chain
|
||||
0, // int nid_key (default 3DES)
|
||||
0, // int nid_cert (40bitRC2)
|
||||
0, // int iter (default 2048)
|
||||
0, // int mac_iter (default 1)
|
||||
0 // int keytype (default no flag)
|
||||
);
|
||||
|
||||
if (pkcs12bundle == nullptr) {
|
||||
generateCertPKCS12_free(
|
||||
cacert, cert, cert_privkey, cacertstack, pkcs12bundle);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 7) Write the PKCS12 structure out to file
|
||||
*/
|
||||
if (!(pkcs12file = fopen(pkcs12Filepath, "w"))) {
|
||||
generateCertPKCS12_free(
|
||||
cacert, cert, cert_privkey, cacertstack, pkcs12bundle);
|
||||
return false;
|
||||
}
|
||||
|
||||
bytes = i2d_PKCS12_fp(pkcs12file, pkcs12bundle);
|
||||
if (bytes <= 0) {
|
||||
generateCertPKCS12_free(
|
||||
cacert, cert, cert_privkey, cacertstack, pkcs12bundle);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 8) Done, free resources.
|
||||
*/
|
||||
fclose(pkcs12file);
|
||||
generateCertPKCS12_free(
|
||||
cacert, cert, cert_privkey, cacertstack, pkcs12bundle);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void generateCertPKCS12_free(
|
||||
X509* cacert,
|
||||
X509* cert,
|
||||
EVP_PKEY* pKey,
|
||||
STACK_OF(X509) * cacertstack,
|
||||
PKCS12* pkcs12bundle) {
|
||||
X509_free(cacert);
|
||||
X509_free(cert);
|
||||
EVP_PKEY_free(pKey);
|
||||
sk_X509_free(cacertstack);
|
||||
PKCS12_free(pkcs12bundle);
|
||||
}
|
||||
|
||||
} // namespace flipper
|
||||
} // namespace facebook
|
||||
|
||||
@@ -20,6 +20,14 @@ bool generateCertSigningRequest(
|
||||
const char* csrFile,
|
||||
const char* privateKeyFile);
|
||||
|
||||
bool generateCertPKCS12(
|
||||
const char* caCertificateFile,
|
||||
const char* certificateFile,
|
||||
const char* keyFile,
|
||||
const char* pkcs12File,
|
||||
const char* pkcs12Name,
|
||||
const char* pkcs12Password);
|
||||
|
||||
} // namespace flipper
|
||||
} // namespace facebook
|
||||
|
||||
|
||||
@@ -8,12 +8,17 @@
|
||||
#include "ConnectionContextStore.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 {
|
||||
|
||||
@@ -21,6 +26,8 @@ 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);
|
||||
@@ -72,7 +79,7 @@ std::string ConnectionContextStore::getCertificateSigningRequest() {
|
||||
return csr;
|
||||
}
|
||||
|
||||
std::shared_ptr<SSLContext> ConnectionContextStore::getSSLContext() {
|
||||
std::shared_ptr<folly::SSLContext> ConnectionContextStore::getSSLContext() {
|
||||
std::shared_ptr<folly::SSLContext> sslContext =
|
||||
std::make_shared<folly::SSLContext>();
|
||||
sslContext->loadTrustedCertificates(
|
||||
@@ -132,7 +139,8 @@ bool ConnectionContextStore::resetState() {
|
||||
FLIPPER_CA_FILE_NAME,
|
||||
CLIENT_CERT_FILE_NAME,
|
||||
PRIVATE_KEY_FILE,
|
||||
CONNECTION_CONFIG_FILE}) {
|
||||
CONNECTION_CONFIG_FILE,
|
||||
CERTIFICATE_FILE_NAME}) {
|
||||
std::remove(absoluteFilePath(file).c_str());
|
||||
}
|
||||
return true;
|
||||
@@ -142,6 +150,30 @@ bool ConnectionContextStore::resetState() {
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
std::string loadStringFromFile(std::string fileName) {
|
||||
if (!fileExists(fileName)) {
|
||||
return "";
|
||||
|
||||
@@ -12,8 +12,6 @@
|
||||
#include <string>
|
||||
#include "FlipperInitConfig.h"
|
||||
|
||||
using namespace folly;
|
||||
|
||||
namespace facebook {
|
||||
namespace flipper {
|
||||
|
||||
@@ -22,12 +20,19 @@ class ConnectionContextStore {
|
||||
ConnectionContextStore(DeviceData deviceData);
|
||||
bool hasRequiredFiles();
|
||||
std::string getCertificateSigningRequest();
|
||||
std::shared_ptr<SSLContext> getSSLContext();
|
||||
std::shared_ptr<folly::SSLContext> getSSLContext();
|
||||
std::string getCertificateDirectoryPath();
|
||||
std::string getDeviceId();
|
||||
void storeConnectionConfig(folly::dynamic& config);
|
||||
bool resetState();
|
||||
|
||||
/** Convert and save to disk the existing certificate to PKCS #12 format.
|
||||
* @return Returns a pair where `first` contains the certificate file path and
|
||||
* `second` contains the certificate export password. If there's an error, the
|
||||
* pair will contain both empty strings.
|
||||
*/
|
||||
std::pair<std::string, std::string> getCertificate();
|
||||
|
||||
private:
|
||||
DeviceData deviceData_;
|
||||
std::string csr = "";
|
||||
|
||||
@@ -37,6 +37,8 @@ static constexpr int maxPayloadSize = 0xFFFFFF;
|
||||
// To be bumped for every core platform interface change.
|
||||
static constexpr int sdkVersion = 4;
|
||||
|
||||
using namespace folly;
|
||||
|
||||
namespace facebook {
|
||||
namespace flipper {
|
||||
|
||||
|
||||
@@ -20,10 +20,10 @@ class ConnectionContextStoreMock : public ConnectionContextStore {
|
||||
std::string createCertificateSigningRequest() {
|
||||
return "thisIsACsr";
|
||||
}
|
||||
std::shared_ptr<SSLContext> getSSLContext() {
|
||||
std::shared_ptr<folly::SSLContext> getSSLContext() {
|
||||
return nullptr;
|
||||
}
|
||||
dynamic getConnectionConfig() {
|
||||
folly::dynamic getConnectionConfig() {
|
||||
return nullptr;
|
||||
}
|
||||
std::string getCertificateDirectoryPath() {
|
||||
|
||||
Reference in New Issue
Block a user