Skip to content

Instantly share code, notes, and snippets.

@jweyrich
Created July 2, 2020 14:13
Show Gist options
  • Save jweyrich/476da68dffc1b73abd91366add6f9e4d to your computer and use it in GitHub Desktop.
Save jweyrich/476da68dffc1b73abd91366add6f9e4d to your computer and use it in GitHub Desktop.
AbstractKeyGenerator for sslpkix
#pragma once
//#include <cassert>
#include <iostream>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include "sslpkix/iosink.h"
#include "sslpkix/common.h"
namespace sslpkix {
//
// NOTE: With OpenSSL, the private key also contains the public key information
//
class Key {
public:
typedef EVP_PKEY handle_type;
struct Cipher {
enum EnumCipher {
#ifndef OPENSSL_NO_RSA
RSA = 1,
#endif
#ifndef OPENSSL_NO_DSA
DSA = 2,
#endif
#ifndef OPENSSL_NO_DH
DH = 3, // Diffie Hellman
#endif
#ifndef OPENSSL_NO_EC
EC = 4,
#endif
UNKNOWN = 0
};
};
public:
Key()
: _handle(NULL)
, _is_external_handle(false)
{
}
Key(const Key& other)
: _handle(other._handle)
, _is_external_handle(false)
{
// Srsly OpenSSL, Y U NO HAVE EVP_PKEY_dup(EVP_PKEY*) ? :-(
CRYPTO_add(&_handle->references, 1, CRYPTO_LOCK_EVP_PKEY);
if (_handle == NULL) {
// std::cerr << "Failed to copy certificate" << std::endl;
throw std::bad_alloc();
}
//reload_data();
}
Key& operator=(Key other) {
release();
swap(*this, other);
return *this;
}
friend void swap(Key& a, Key& b) { // nothrow
using std::swap; // enable ADL
swap(a._handle, b._handle);
swap(a._is_external_handle, b._is_external_handle);
}
virtual ~Key() {
release();
}
handle_type *handle() const {
//assert(_handle != NULL);
return _handle;
}
bool create() {
release();
_handle = EVP_PKEY_new();
if (_handle == NULL)
std::cerr << "Failed to create key" << std::endl;
return _handle != NULL;
}
Cipher::EnumCipher algorithm() const {
int algorithm = EVP_PKEY_type(_handle->type);
switch (algorithm) {
#ifndef OPENSSL_NO_RSA
case EVP_PKEY_RSA: return Cipher::RSA;
#endif
#ifndef OPENSSL_NO_DSA
case EVP_PKEY_DSA: return Cipher::DSA;
#endif
#ifndef OPENSSL_NO_DH
case EVP_PKEY_DH: return Cipher::DH;
#endif
#ifndef OPENSSL_NO_EC
case EVP_PKEY_EC: return Cipher::EC;
#endif
default: return Cipher::UNKNOWN;
}
}
#ifndef OPENSSL_NO_RSA
bool assign(RSA *key) {
return EVP_PKEY_assign_RSA(_handle, key) != 0;
}
bool copy(RSA *key) {
return EVP_PKEY_set1_RSA(_handle, key) != 0;
}
#endif
#ifndef OPENSSL_NO_DSA
bool assign(DSA *key) {
return EVP_PKEY_assign_DSA(_handle, key) != 0;
}
bool copy(DSA *key) {
return EVP_PKEY_set1_DSA(_handle, key) != 0;
}
#endif
#ifndef OPENSSL_NO_DH
bool assign(DH *key) {
return EVP_PKEY_assign_DH(_handle, key) != 0;
}
bool copy(DH *key) {
return EVP_PKEY_set1_DH(_handle, key) != 0;
}
#endif
#ifndef OPENSSL_NO_EC
bool assign(EC_KEY *key) {
return EVP_PKEY_assign_EC_KEY(_handle, key) != 0;
}
bool copy(EC_KEY *key) {
return EVP_PKEY_set1_EC_KEY(_handle, key) != 0;
}
#endif
virtual bool load(IoSink& sink UNUSED, const char *password UNUSED) {
return false;
}
virtual bool save(IoSink& sink UNUSED) const {
return false;
}
friend bool operator==(const Key& lhs, const Key& rhs) {
// TODO(jweyrich): do we need EVP_PKEY_cmp_parameters() too?
return EVP_PKEY_cmp(lhs._handle, rhs._handle) == 1;
}
friend bool operator!=(const Key& lhs, const Key& rhs) {
return !(lhs == rhs);
}
protected:
void release() {
if (_handle != NULL && !_is_external_handle) {
EVP_PKEY_free(_handle);
}
_handle = NULL;
_is_external_handle = false;
}
void set_handle(handle_type *handle) {
release();
_handle = handle;
_is_external_handle = true;
}
protected:
handle_type *_handle;
bool _is_external_handle;
friend class Certificate;
friend class CertificateRequest;
};
class PrivateKey : public Key {
public:
PrivateKey() {
}
virtual ~PrivateKey() {
}
virtual bool load(IoSink& sink, const char *password) {
release();
_handle = PEM_read_bio_PrivateKey(sink.handle(), NULL, NULL, (void *)password);
if (_handle == NULL)
std::cerr << "Failed to load private key: " << sink.source() << std::endl;
return _handle != NULL;
}
virtual bool save(IoSink& sink) const {
if (_handle == NULL)
return false;
int ret = PEM_write_bio_PrivateKey(sink.handle(), _handle, NULL, NULL, 0, 0, NULL);
if (ret == 0)
std::cerr << "Failed to save private key: " << sink.source() << std::endl;
return ret != 0;
}
};
// -------------------------------------------------------------------
template <class SubClassType, class HandleType>
class AbstractKeyGenerator {
public:
typedef HandleType handle_type;
typedef SubClassType subclass_type;
public:
AbstractKeyGenerator()
: _handle(NULL)
, _is_external_handle(false)
{
}
virtual ~AbstractKeyGenerator() {
release();
}
subclass_type& operator=(subclass_type other) {
release();
swap(*this, other);
return *this;
}
friend void swap(subclass_type& a, subclass_type& b) { // nothrow
using std::swap; // enable ADL
swap(a._handle, b._handle);
swap(a._is_external_handle, b._is_external_handle);
}
handle_type *handle() const {
//assert(_handle != NULL);
return _handle;
}
virtual bool create() = 0;
virtual Key::Cipher::EnumCipher algorithm() const = 0;
virtual std::string algorithmName() const = 0;
virtual bool assignTo(Key::handle_type *target) = 0;
virtual bool copyTo(Key::handle_type *target) = 0;
virtual bool generate(uint16_t bits) = 0;
protected:
virtual void release_handle() = 0;
void release() {
if (_handle != NULL && !_is_external_handle) {
// FIXME(jweyrich): Overrides of 'release_handle'
// in subclasses are not available in the destructor of this class.
release_handle();
}
_handle = NULL;
_is_external_handle = false;
}
void set_handle(handle_type *handle) {
release();
_handle = handle;
_is_external_handle = true;
}
protected:
handle_type *_handle;
bool _is_external_handle;
};
class RSAKey : AbstractKeyGenerator<RSAKey, RSA> {
public:
RSAKey(const RSAKey& other) {
// TODO(jweyrich): Implement copy.
/*
_handle = ...;
if (_handle == NULL) {
// std::cerr << "Failed to copy RSA key" << std::endl;
throw std::bad_alloc();
}
//reload_data();
*/
}
virtual bool create() {
release();
_handle = RSA_new();
if (_handle == NULL)
std::cerr << "Failed to create RSA key" << std::endl;
return _handle != NULL;
}
/*
bool copy(RSAKey& key) {
int ret = PLEASE_IMPLEMENT_THE_COPY(&_handle, key.handle());
if (ret == 0)
std::cerr << "Failed to copy RSA key" << std::endl;
return ret != 0;
}
*/
virtual Key::Cipher::EnumCipher algorithm() const {
return Key::Cipher::RSA;
}
virtual std::string algorithmName() const {
return "RSA";
}
virtual bool assignTo(Key::handle_type *target) {
return EVP_PKEY_assign_RSA(target, _handle) != 0;
}
virtual bool copyTo(Key::handle_type *target) {
return EVP_PKEY_set1_RSA(target, _handle) != 0;
}
virtual bool generate(uint16_t num_bits) {
bool success = false;
int ret = 0;
BN_GENCB callback;
BIGNUM *bignum = BN_new();
if (bignum == NULL) {
goto do_cleanup;
}
// Use the fourth Fermat Number
ret = BN_set_word(bignum, RSA_F4);
if (ret == 0) {
goto do_cleanup;
}
BN_GENCB_set(&callback, prime_generation_callback, NULL);
ret = RSA_generate_key_ex(
_handle, static_cast<int>(num_bits), bignum, &callback);
if (ret == 0) {
goto do_cleanup;
}
success = true;
do_cleanup:
if (bignum != NULL)
BN_free(bignum);
return success;
}
virtual bool load(IoSink& priv, IoSink& pub, const std::string& password) {
bool success = false;
BIO *bio_pub = pub.handle();
BIO *bio_priv = priv.handle();
pem_password_cb *password_cb = NULL;
void *user_data = NULL;
RSA *priv_rsa = NULL;
RSA *pub_rsa = NULL;
priv_rsa = PEM_read_bio_RSAPrivateKey(
bio_priv, &_handle,
password_cb, user_data);
if (priv_rsa == NULL) {
goto do_cleanup;
}
_handle = priv_rsa;
pub_rsa = PEM_read_bio_RSAPublicKey(
bio_pub, &_handle,
password_cb, user_data);
if (pub_rsa == NULL) {
goto do_cleanup;
}
_handle = pub_rsa;
success = true;
do_cleanup:
return success;
}
virtual bool save(IoSink& priv, IoSink& pub, const std::string& password) {
bool success = false;
BIO *bio_pub = pub.handle();
BIO *bio_priv = priv.handle();
const EVP_CIPHER *cipher = NULL;
pem_password_cb *password_cb = NULL;
void *user_data = NULL;
const unsigned char *tmp_pass =
reinterpret_cast<const unsigned char *>(password.c_str());
// Save private key
int ret = PEM_write_bio_RSAPrivateKey(
bio_priv, _handle, cipher,
const_cast<unsigned char *>(tmp_pass), password.length(),
password_cb, user_data);
if (ret != 1) {
goto do_cleanup;
}
// Save public key
ret = PEM_write_bio_RSAPublicKey(bio_pub, _handle);
if (ret != 1) {
goto do_cleanup;
}
success = true;
do_cleanup:
return success;
}
static int prime_generation_callback(int p, int n, BN_GENCB *arg) {
(void)n;
(void)arg;
char c;
switch (p) {
default: c = 'B'; break;
case 0: c = '.'; break;
case 1: c = '+'; break;
case 2: c = '*'; break;
case 3: c = '\n'; break;
}
fputc(c, stderr);
return 1;
}
protected:
virtual void release_handle() {
if (_handle != NULL) {
RSA_free(_handle);
_handle = NULL;
}
}
};
} // namespace sslpkix
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment