Files
flipper/xplat/Flipper/CertificateUtils.cpp
Lorenzo Blasa 1beae3230b Remove OpenSSL file BIO operations
Summary:
^
Change necessary when OpenSSL is compiled without STDIO for file operations. Basically, don't use the BIO file API's. Instead use in-memory BIO and do file operations manually.

UPDATE:

The changes were good, but have been simplified and fixed. A fixed was needed as to read and write the file in binary mode. This has no effect in POSIX systems but it does on Windows and this change was made for Windows. It meant that the BIO was incorrectly written to disk thus corrupting its content.

Changelog: Remove OpenSSL file BIO operations

Reviewed By: jknoxville

Differential Revision: D36060992

fbshipit-source-id: 21b30582dd0b32c24b8ba001d6993034d92de1da
2022-05-09 05:27:21 -07:00

413 lines
9.8 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 "CertificateUtils.h"
#include <fcntl.h>
#include <folly/portability/Fcntl.h>
#include <folly/portability/SysStat.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/pem.h>
#include <openssl/pkcs12.h>
#include <openssl/rsa.h>
#include <openssl/x509.h>
#include <stdio.h>
#include <cstring>
#include <stdexcept>
namespace facebook {
namespace flipper {
BIO* bioFromFile(const char* filename);
bool bioToFile(const char* filename, BIO* bio);
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);
BIO* bioFromFile(const char* filename) {
if (filename == nullptr) {
return nullptr;
}
FILE* fp = fopen(filename, "rb");
if (fp == nullptr) {
return nullptr;
}
BIO* bio = BIO_new(BIO_s_mem());
#define BUFFER_SIZE 512
char buffer[BUFFER_SIZE];
size_t r = 0;
while ((r = fread(buffer, 1, BUFFER_SIZE, fp)) > 0) {
BIO_write(bio, buffer, (int)r);
}
fclose(fp);
return bio;
}
bool bioToFile(const char* filename, BIO* bio) {
if (bio == nullptr || filename == nullptr) {
return false;
}
FILE* fp = fopen(filename, "wb");
if (fp == nullptr) {
return false;
}
BUF_MEM* bptr;
BIO_get_mem_ptr(bio, &bptr);
if (bptr != nullptr) {
fwrite(bptr->data, 1, bptr->length, fp);
}
fclose(fp);
return true;
}
bool generateCertSigningRequest(
const char* appId,
const char* csrFile,
const char* privateKeyFile) {
int ret = 0;
BIGNUM* bne = NULL;
int nVersion = 1;
int bits = 2048;
// Using 65537 as exponent
unsigned long e = RSA_F4;
X509_NAME* x509_name = NULL;
const char* subjectCountry = "US";
const char* subjectProvince = "CA";
const char* subjectCity = "Menlo Park";
const char* subjectOrganization = "Flipper";
const char* subjectCommon = strlen(appId) >= 64 ? "com.flipper" : appId;
X509_REQ* x509_req = X509_REQ_new();
EVP_PKEY* pKey = EVP_PKEY_new();
RSA* rsa = RSA_new();
BIO* privateKey = NULL;
BIO* csrBio = NULL;
EVP_PKEY_assign(pKey, EVP_PKEY_RSA, rsa);
// Generate rsa key
bne = BN_new();
BN_set_flags(bne, BN_FLG_CONSTTIME);
ret = BN_set_word(bne, e);
if (ret != 1) {
generateCertSigningRequest_free(pKey, x509_req, bne, privateKey, csrBio);
return false;
}
ret = RSA_generate_key_ex(rsa, bits, bne, NULL);
if (ret != 1) {
generateCertSigningRequest_free(pKey, x509_req, bne, privateKey, csrBio);
return false;
}
{
privateKey = BIO_new(BIO_s_mem());
ret =
PEM_write_bio_RSAPrivateKey(privateKey, rsa, NULL, NULL, 0, NULL, NULL);
if (ret != 1) {
generateCertSigningRequest_free(pKey, x509_req, bne, privateKey, csrBio);
return false;
}
if (!bioToFile(privateKeyFile, privateKey)) {
generateCertSigningRequest_free(pKey, x509_req, bne, privateKey, csrBio);
return false;
}
}
rsa = NULL;
ret = BIO_flush(privateKey);
if (ret != 1) {
generateCertSigningRequest_free(pKey, x509_req, bne, privateKey, csrBio);
return ret;
}
ret = X509_REQ_set_version(x509_req, nVersion);
if (ret != 1) {
generateCertSigningRequest_free(pKey, x509_req, bne, privateKey, csrBio);
return ret;
}
x509_name = X509_REQ_get_subject_name(x509_req);
ret = X509_NAME_add_entry_by_txt(
x509_name,
"C",
MBSTRING_ASC,
(const unsigned char*)subjectCountry,
-1,
-1,
0);
if (ret != 1) {
generateCertSigningRequest_free(pKey, x509_req, bne, privateKey, csrBio);
return ret;
}
ret = X509_NAME_add_entry_by_txt(
x509_name,
"ST",
MBSTRING_ASC,
(const unsigned char*)subjectProvince,
-1,
-1,
0);
if (ret != 1) {
generateCertSigningRequest_free(pKey, x509_req, bne, privateKey, csrBio);
return ret;
}
ret = X509_NAME_add_entry_by_txt(
x509_name,
"L",
MBSTRING_ASC,
(const unsigned char*)subjectCity,
-1,
-1,
0);
if (ret != 1) {
generateCertSigningRequest_free(pKey, x509_req, bne, privateKey, csrBio);
return ret;
}
ret = X509_NAME_add_entry_by_txt(
x509_name,
"O",
MBSTRING_ASC,
(const unsigned char*)subjectOrganization,
-1,
-1,
0);
if (ret != 1) {
generateCertSigningRequest_free(pKey, x509_req, bne, privateKey, csrBio);
return ret;
}
ret = X509_NAME_add_entry_by_txt(
x509_name,
"CN",
MBSTRING_ASC,
(const unsigned char*)subjectCommon,
-1,
-1,
0);
if (ret != 1) {
generateCertSigningRequest_free(pKey, x509_req, bne, privateKey, csrBio);
return ret;
}
ret = X509_REQ_set_pubkey(x509_req, pKey);
if (ret != 1) {
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) {
generateCertSigningRequest_free(pKey, x509_req, bne, privateKey, csrBio);
return ret;
}
{
// Write CSR to a file
csrBio = BIO_new(BIO_s_mem());
ret = PEM_write_bio_X509_REQ(csrBio, x509_req);
if (ret != 1) {
generateCertSigningRequest_free(pKey, x509_req, bne, privateKey, csrBio);
return ret;
}
if (!bioToFile(csrFile, csrBio)) {
generateCertSigningRequest_free(pKey, x509_req, bne, privateKey, csrBio);
return false;
}
}
ret = BIO_flush(csrBio);
generateCertSigningRequest_free(pKey, x509_req, bne, privateKey, csrBio);
return (ret == 1);
}
void generateCertSigningRequest_free(
EVP_PKEY* pKey,
X509_REQ* x509_req,
BIGNUM* bne,
BIO* privateKey,
BIO* csrBio) {
BN_free(bne);
X509_REQ_free(x509_req);
EVP_PKEY_free(pKey);
BIO_free_all(privateKey);
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;
int bytes = 0;
OpenSSL_add_all_algorithms();
ERR_load_crypto_strings();
// Load the certificate's private key
if ((cert_privkey = EVP_PKEY_new()) == NULL) {
return false;
}
BIO* privateKeyBio = bioFromFile(keyFilepath);
if (privateKeyBio == nullptr) {
generateCertPKCS12_free(
cacert, cert, cert_privkey, cacertstack, pkcs12bundle);
return false;
}
if (!(cert_privkey =
PEM_read_bio_PrivateKey(privateKeyBio, NULL, NULL, NULL))) {
generateCertPKCS12_free(
cacert, cert, cert_privkey, cacertstack, pkcs12bundle);
return false;
}
// Load the certificate
BIO* certificateBio = bioFromFile(certFilepath);
if (certificateBio == nullptr) {
generateCertPKCS12_free(
cacert, cert, cert_privkey, cacertstack, pkcs12bundle);
return false;
}
if (!(cert = PEM_read_bio_X509(certificateBio, NULL, NULL, NULL))) {
generateCertPKCS12_free(
cacert, cert, cert_privkey, cacertstack, pkcs12bundle);
return false;
}
// Load the CA certificate who signed it
BIO* cacertBio = bioFromFile(cacertFilepath);
if (cacertBio == nullptr) {
generateCertPKCS12_free(
cacert, cert, cert_privkey, cacertstack, pkcs12bundle);
return false;
}
if (!(cacert = PEM_read_bio_X509(cacertBio, NULL, NULL, NULL))) {
generateCertPKCS12_free(
cacert, cert, cert_privkey, cacertstack, pkcs12bundle);
return false;
}
// 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);
// 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;
}
// Write the PKCS12 structure out to file
BIO* pkcs12Bio = BIO_new(BIO_s_mem());
bytes = i2d_PKCS12_bio(pkcs12Bio, pkcs12bundle);
if (bytes <= 0) {
generateCertPKCS12_free(
cacert, cert, cert_privkey, cacertstack, pkcs12bundle);
return false;
}
if (!bioToFile(pkcs12Filepath, pkcs12Bio)) {
BIO_free(pkcs12Bio);
generateCertPKCS12_free(
cacert, cert, cert_privkey, cacertstack, pkcs12bundle);
return false;
}
// Done, free resources
BIO_free(pkcs12Bio);
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