sslbd/mkcert.cpp
#include "mkcert.hpp"
#include <openssl/x509v3.h>
#include <openssl/opensslv.h>
typedef struct ssl_cert_s {
X509* cert;
EVP_PKEY* key;
ssl_cert_s(): cert(NULL), key(NULL) {}
~ssl_cert_s() {
if (cert) X509_free(cert);
if (key) EVP_PKEY_free(key);
}
} ssl_cert_t;
static bool ca_config(void*& ctx, char* value) {
char* p = strchr(value, ':');
if (!p) {
log(error, "syntax: certfile:keyfile");
return false;
}
*p = '\0'; ++p;
ssl_cert_t* ca = new ssl_cert_t();
if (!mkcert(value, p, ca->cert, ca->key)) {
delete ca;
return false;
}
ctx = (void*)ca;
return true;
}
static void ca_unconfig(void*& ctx) {
if (ctx) {
delete (ssl_cert_t*)ctx;
ctx = NULL;
}
}
CONF_DEF(config) {
ConfigKey ca;
};
CONF_INIT(config) {
CONF_KEY_INIT(ca, false, false, &ca_config, &ca_unconfig); // not reconfigurable atm as it is blocking
}
//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//
static bool add_ext(X509* cert, int nid, const char* value) {
// This sets the 'context' of the extensions; No configuration database.
X509V3_CTX ctx;
X509V3_set_ctx_nodb(&ctx);
// Issuer and subject certs: both the target since it is self signed, no request and no CRL.
X509V3_set_ctx(&ctx, cert, cert, NULL, NULL, 0);
X509_EXTENSION* ex = X509V3_EXT_conf_nid(NULL, &ctx, nid, (char*)value); // XXX: this const cast ok?
unless (ex) {
ssl_log_err(notice);
return false;
}
X509_add_ext(cert, ex, -1);
X509_EXTENSION_free(ex);
return true;
}
// http://www.opensource.apple.com/source/OpenSSL/OpenSSL-22/openssl/demos/x509/mkcert.c
// http://fm4dd.com/openssl/
static bool mkcert(const char* commonname, int bits, int days, X509*& x, EVP_PKEY*& k) {
k = NULL;
x = NULL;
RSA* rsa = NULL;
BIGNUM* bne = NULL;
time_t epoch = 0;
ssl_cert_t* ca = config? config->ca.ctx_as<ssl_cert_t>(): NULL;
k = EVP_PKEY_new();
unless (k) goto err;
x = X509_new();
unless (x) goto err;
rsa = RSA_new();
unless (rsa) goto err;
bne = BN_new();
unless (BN_set_word(bne, RSA_F4) == 1) goto err;
if (RSA_generate_key_ex(rsa, bits, bne, NULL) != 1) { // The pseudo-random number generator must be seeded prior to calling RSA_generate_key()
goto err;
}
unless (EVP_PKEY_assign_RSA(k, rsa)) goto err;
X509_set_version(x, 2); // v3 m)
X509_set_pubkey(x, k);
// set the commonname (only, as this could be stated in some CA nameConstraint)
unless (X509_NAME_add_entry_by_txt(X509_get_subject_name(x), "CN", MBSTRING_ASC, (const unsigned char*)commonname, -1, -1, 0)) goto err;
unless (add_ext(x, NID_netscape_ssl_server_name, commonname)) goto err;
// set the serial number for the cert
unless (ASN1_INTEGER_set(X509_get_serialNumber(x), (long)hash(commonname)+(long)NOW)) goto err; // The CA can choose the serial number in any way as it sees fit, not necessarily randomly (and it has to fit in 20 bytes). A CA is supposed to choose unique serial numbers, that is, unique for the CA;
// set notBefore/notAfter
unless (X509_time_adj(X509_get_notBefore(x), 0, &epoch)) goto err;
unless (X509_time_adj(X509_get_notAfter(x), 60*60*24*days, &NOW)) goto err;
// Add various extensions: standard extensions and some Netscape specific extensions
if (!add_ext(x, NID_basic_constraints, "CA:FALSE") || // is no CA/root cert
!add_ext(x, NID_key_usage, "keyEncipherment,dataEncipherment,keyAgreement") ||
!add_ext(x, NID_ext_key_usage, "serverAuth") ||
!add_ext(x, NID_subject_key_identifier, "hash") ||
!add_ext(x, NID_netscape_cert_type, "server")) goto err;
// (self-)signing & done
if (ca) {
unless (X509_set_issuer_name(x, X509_get_subject_name(ca->cert))) goto err;
unless (X509_sign(x, ca->key, EVP_sha256())) goto err; // browsers might reject MD5
} else {
log(debug, "self-signing for '%s'", commonname);
unless (X509_set_issuer_name(x, X509_get_subject_name(x))) goto err; // Its self signed so set the issuer name to be the same as the subject.
unless (X509_sign(x, k, EVP_sha256())) goto err;
}
// TODO: somehow set preferred/allowed cipher suites
return true;
// rollback
err:;
ssl_log_err(notice);
if (rsa) RSA_free(rsa);
if (bne) BN_free(bne);
if (x) X509_free(x);
if (k) EVP_PKEY_free(k);
x = NULL;
k = NULL;
return false;
}
bool mkcert(const char* cn, X509*& x, EVP_PKEY*& k) {
return mkcert(cn, 1024, 365, x, k);
}
bool mkcert(const char* cert, const char* key, X509*& x, EVP_PKEY*& k) {
x = NULL;
k = NULL;
FILE* xfp = NULL;
FILE* kfp = NULL;
if ((xfp = fopen(cert, "r")) == NULL) {
log_errno(error, "fopen(%s)", cert);
goto err;
}
if ((kfp = fopen(key, "r")) == NULL) {
log_errno(error, "fopen(%s)", key);
goto err;
}
if ((x = PEM_read_X509(xfp, &x, NULL, NULL)) == NULL) {
ssl_log_err(notice);
goto err;
}
if ((k = PEM_read_PrivateKey(kfp, &k, NULL, NULL)) == NULL) {
ssl_log_err(notice);
goto err;
}
fclose(xfp);
fclose(kfp);
return true;
err:;
if (xfp) fclose(xfp);
if (kfp) fclose(kfp);
if (x) X509_free(x);
if (k) EVP_PKEY_free(k);
x = NULL;
k = NULL;
return false;
}
bool mkcert(const char* cn, char*& cert, size_t& certlen, char*& key, size_t& keylen) {
char* tmp;
cert = NULL;
key = NULL;
X509* x = NULL;
EVP_PKEY* k = NULL;
if (!mkcert(cn, x, k)) {
return false;
}
BIO* out = BIO_new(BIO_s_mem());
unless (PEM_write_bio_X509(out, x)) {
ssl_log_err(notice);
goto err;
}
certlen = BIO_get_mem_data(out, &tmp);
cert = strndup(tmp, certlen);
BIO_vfree(out);
out = BIO_new(BIO_s_mem());
unless (PEM_write_bio_PrivateKey(out, k, NULL, NULL, 0, NULL, NULL)) {
ssl_log_err(notice);
goto err;
}
keylen = BIO_get_mem_data(out, &tmp);
key = strndup(tmp, keylen);
BIO_vfree(out);
X509_free(x);
EVP_PKEY_free(k);
return true;
err:;
BIO_vfree(out);
X509_free(x);
EVP_PKEY_free(k);
if (cert) free(cert);
if (key) free(key);
return false;
}
bool mkcert(const char* certbuf, size_t certlen, const char* keybuf, size_t keylen, X509*& cert, EVP_PKEY*& key) {
BIO* out;
out = BIO_new_mem_buf((void*)certbuf, certlen);
cert = PEM_read_bio_X509(out, NULL, NULL, NULL);
unless (cert) {
ssl_log_err(notice);
BIO_vfree(out);
cert = NULL;
key = NULL;
return false;
}
BIO_vfree(out);
out = BIO_new_mem_buf((void*)keybuf, keylen);
key = PEM_read_bio_PrivateKey(out, NULL, NULL, NULL);
unless (key) {
ssl_log_err(notice);
BIO_vfree(out);
X509_free(cert);
cert = NULL;
key = NULL;
return false;
}
BIO_vfree(out);
return true;
}
SSL_CTX* ssl_ctx_get(bool server) {
SSL_CTX* rv = SSL_CTX_new(server? SSLv23_server_method(): SSLv23_method());
if (!rv) {
ssl_log_err(notice);
return NULL;
}
unless (SSL_CTX_set_cipher_list(rv, "ALL:!ADH:!EXP:!LOW:!RC2:!3DES:!SEED:!eNULL:!aNULL:!SRP:!CAMELLIA:!DSS:+HIGH:+ECDH:+MEDIUM")) die("no given cipher supported"); // TODO:
SSL_CTX_set_verify(rv, SSL_VERIFY_NONE, NULL); // TODO: should we abort upon some errors, as we cannot pass these along?
SSL_CTX_clear_options(rv, SSL_OP_SINGLE_DH_USE); // TODO: check whether this weakening has i.e. performance benefits
SSL_CTX_set_options(rv, SSL_OP_ALL|SSL_OP_NO_SSLv2|SSL_OP_NO_TICKET|SSL_OP_LEGACY_SERVER_CONNECT); // SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION?
if (server) SSL_CTX_set_options(rv, SSL_OP_CIPHER_SERVER_PREFERENCE); // honor upstream?
if (!server) SSL_CTX_set_quiet_shutdown(rv, 1); // don't cause client warnings but violate the standard for upstream
SSL_CTX_set_mode(rv, SSL_MODE_ENABLE_PARTIAL_WRITE);
SSL_CTX_set_session_cache_mode(rv, SSL_SESS_CACHE_OFF); // no cache/tickets/sessions
SSL_CTX_set_tmp_dh_callback(rv, ssl_dh_callback);
SSL_CTX_set_tmp_rsa_callback(rv, &ssl_rsa_callback);
return rv;
}
ssl_ctx_t* ssl_ctx_get(const char* certbuf, size_t certlen, const char* keybuf, size_t keylen) {
SSL_CTX* ctx = NULL;
X509* cert = NULL;
EVP_PKEY* key = NULL;
unless (mkcert(certbuf, certlen, keybuf, keylen, cert, key)) {
return NULL;
}
assert(cert && key);
ctx = ssl_ctx_get(true);
unless (ctx) {
goto err;
}
if (SSL_CTX_use_certificate(ctx, cert) != 1) {
ssl_log_err(notice);
goto err;
}
if (SSL_CTX_use_PrivateKey(ctx, key) != 1) {
ssl_log_err(notice);
goto err;
}
if (SSL_CTX_check_private_key(ctx) != 1) {
ssl_log_err(notice);
goto err;
}
X509_free(cert); // apparently not needed anymore and must be free'd??
EVP_PKEY_free(key);
return ctx;
err:;
if (ctx) SSL_CTX_free(ctx);
if (cert) X509_free(cert);
if (key) EVP_PKEY_free(key);
return NULL;
}
void ssl_ctx_del(ssl_ctx_t* ctx) {
SSL_CTX_free(ctx); // also calls the free()ing procedures for indirectly affected items, if applicable: the session cache, the list of ciphers, the list of Client CAs, the certificates and keys.
}
void ssl_ctx_up_ref(ssl_ctx_t* ctx) {
// http://openssl.6102.n7.nabble.com/OpenSSL-1-1-SSL-CTX-issues-td62592.html
#if 1
CRYPTO_add(&ctx->references, 1, CRYPTO_LOCK_SSL_CTX);
#else
SSL_CTX_up_ref(ctx);
#endif
}