Skip to content

Commit

Permalink
ConnectionContext to expose client certificate in PKCS #12 format
Browse files Browse the repository at this point in the history
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
  • Loading branch information
lblasa authored and facebook-github-bot committed Aug 4, 2021
1 parent a5b83dc commit 43179a7
Show file tree
Hide file tree
Showing 6 changed files with 237 additions and 28 deletions.
202 changes: 182 additions & 20 deletions xplat/Flipper/CertificateUtils.cpp
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
}

Expand All @@ -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;
}
}
Expand All @@ -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;
}

Expand All @@ -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;
}

Expand All @@ -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;
}

Expand All @@ -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;
}

Expand All @@ -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;
}

Expand All @@ -167,50 +178,50 @@ 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;
}

{
// 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,
Expand All @@ -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
8 changes: 8 additions & 0 deletions xplat/Flipper/CertificateUtils.h
Expand Up @@ -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

Expand Down

0 comments on commit 43179a7

Please sign in to comment.