diff options
Diffstat (limited to 'modules/md/md_crypt.c')
-rw-r--r-- | modules/md/md_crypt.c | 1334 |
1 files changed, 1085 insertions, 249 deletions
diff --git a/modules/md/md_crypt.c b/modules/md/md_crypt.c index e0aac3e..4b2af89 100644 --- a/modules/md/md_crypt.c +++ b/modules/md/md_crypt.c @@ -22,19 +22,26 @@ #include <apr_buckets.h> #include <apr_file_io.h> #include <apr_strings.h> +#include <httpd.h> +#include <http_core.h> #include <openssl/err.h> #include <openssl/evp.h> +#include <openssl/hmac.h> #include <openssl/pem.h> #include <openssl/rand.h> #include <openssl/rsa.h> #include <openssl/x509v3.h> +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#include <openssl/core_names.h> +#endif #include "md.h" #include "md_crypt.h" #include "md_json.h" #include "md_log.h" #include "md_http.h" +#include "md_time.h" #include "md_util.h" /* getpid for *NIX */ @@ -57,6 +64,17 @@ #define MD_USE_OPENSSL_PRE_1_1_API (OPENSSL_VERSION_NUMBER < 0x10100000L) #endif +#if (defined(LIBRESSL_VERSION_NUMBER) && (LIBRESSL_VERSION_NUMBER < 0x3050000fL)) || (OPENSSL_VERSION_NUMBER < 0x10100000L) +/* Missing from LibreSSL < 3.5.0 and only available since OpenSSL v1.1.x */ +#ifndef OPENSSL_NO_CT +#define OPENSSL_NO_CT +#endif +#endif + +#ifndef OPENSSL_NO_CT +#include <openssl/ct.h> +#endif + static int initialized; struct md_pkey_t { @@ -142,17 +160,13 @@ apr_status_t md_crypt_init(apr_pool_t *pool) return APR_SUCCESS; } -typedef struct { - char *data; - apr_size_t len; -} buffer_rec; - static apr_status_t fwrite_buffer(void *baton, apr_file_t *f, apr_pool_t *p) { - buffer_rec *buf = baton; + md_data_t *buf = baton; + apr_size_t wlen; (void)p; - return apr_file_write_full(f, buf->data, buf->len, &buf->len); + return apr_file_write_full(f, buf->data, buf->len, &wlen); } apr_status_t md_rand_bytes(unsigned char *buf, apr_size_t len, apr_pool_t *p) @@ -183,8 +197,10 @@ static int pem_passwd(char *buf, int size, int rwflag, void *baton) size = (int)ctx->pass_len; } memcpy(buf, ctx->pass_phrase, (size_t)size); + } else { + return 0; } - return ctx->pass_len; + return size; } /**************************************************************************************************/ @@ -197,7 +213,8 @@ static int pem_passwd(char *buf, int size, int rwflag, void *baton) */ static apr_time_t md_asn1_time_get(const ASN1_TIME* time) { -#if OPENSSL_VERSION_NUMBER < 0x10002000L || defined(LIBRESSL_VERSION_NUMBER) +#if OPENSSL_VERSION_NUMBER < 0x10002000L || (defined(LIBRESSL_VERSION_NUMBER) && \ + LIBRESSL_VERSION_NUMBER < 0x3060000fL) /* courtesy: https://stackoverflow.com/questions/10975542/asn1-time-to-time-t-conversion#11263731 * all bugs are mine */ apr_time_exp_t t; @@ -246,10 +263,98 @@ static apr_time_t md_asn1_time_get(const ASN1_TIME* time) #endif } +apr_time_t md_asn1_generalized_time_get(void *ASN1_GENERALIZEDTIME) +{ + return md_asn1_time_get(ASN1_GENERALIZEDTIME); +} + +/**************************************************************************************************/ +/* OID/NID things */ + +static int get_nid(const char *num, const char *sname, const char *lname) +{ + /* Funny API, an OID for a feature might be configured or + * maybe not. In the second case, we need to add it. But adding + * when it already is there is an error... */ + int nid = OBJ_txt2nid(num); + if (NID_undef == nid) { + nid = OBJ_create(num, sname, lname); + } + return nid; +} + +#define MD_GET_NID(x) get_nid(MD_OID_##x##_NUM, MD_OID_##x##_SNAME, MD_OID_##x##_LNAME) /**************************************************************************************************/ /* private keys */ +md_pkeys_spec_t *md_pkeys_spec_make(apr_pool_t *p) +{ + md_pkeys_spec_t *pks; + + pks = apr_pcalloc(p, sizeof(*pks)); + pks->p = p; + pks->specs = apr_array_make(p, 2, sizeof(md_pkey_spec_t*)); + return pks; +} + +void md_pkeys_spec_add(md_pkeys_spec_t *pks, md_pkey_spec_t *spec) +{ + APR_ARRAY_PUSH(pks->specs, md_pkey_spec_t*) = spec; +} + +void md_pkeys_spec_add_default(md_pkeys_spec_t *pks) +{ + md_pkey_spec_t *spec; + + spec = apr_pcalloc(pks->p, sizeof(*spec)); + spec->type = MD_PKEY_TYPE_DEFAULT; + md_pkeys_spec_add(pks, spec); +} + +int md_pkeys_spec_contains_rsa(md_pkeys_spec_t *pks) +{ + md_pkey_spec_t *spec; + int i; + for (i = 0; i < pks->specs->nelts; ++i) { + spec = APR_ARRAY_IDX(pks->specs, i, md_pkey_spec_t*); + if (MD_PKEY_TYPE_RSA == spec->type) return 1; + } + return 0; +} + +void md_pkeys_spec_add_rsa(md_pkeys_spec_t *pks, unsigned int bits) +{ + md_pkey_spec_t *spec; + + spec = apr_pcalloc(pks->p, sizeof(*spec)); + spec->type = MD_PKEY_TYPE_RSA; + spec->params.rsa.bits = bits; + md_pkeys_spec_add(pks, spec); +} + +int md_pkeys_spec_contains_ec(md_pkeys_spec_t *pks, const char *curve) +{ + md_pkey_spec_t *spec; + int i; + for (i = 0; i < pks->specs->nelts; ++i) { + spec = APR_ARRAY_IDX(pks->specs, i, md_pkey_spec_t*); + if (MD_PKEY_TYPE_EC == spec->type + && !apr_strnatcasecmp(curve, spec->params.ec.curve)) return 1; + } + return 0; +} + +void md_pkeys_spec_add_ec(md_pkeys_spec_t *pks, const char *curve) +{ + md_pkey_spec_t *spec; + + spec = apr_pcalloc(pks->p, sizeof(*spec)); + spec->type = MD_PKEY_TYPE_EC; + spec->params.ec.curve = apr_pstrdup(pks->p, curve); + md_pkeys_spec_add(pks, spec); +} + md_json_t *md_pkey_spec_to_json(const md_pkey_spec_t *spec, apr_pool_t *p) { md_json_t *json = md_json_create(p); @@ -264,6 +369,12 @@ md_json_t *md_pkey_spec_to_json(const md_pkey_spec_t *spec, apr_pool_t *p) md_json_setl((long)spec->params.rsa.bits, json, MD_KEY_BITS, NULL); } break; + case MD_PKEY_TYPE_EC: + md_json_sets("EC", json, MD_KEY_TYPE, NULL); + if (spec->params.ec.curve) { + md_json_sets(spec->params.ec.curve, json, MD_KEY_CURVE, NULL); + } + break; default: md_json_sets("Unsupported", json, MD_KEY_TYPE, NULL); break; @@ -272,6 +383,27 @@ md_json_t *md_pkey_spec_to_json(const md_pkey_spec_t *spec, apr_pool_t *p) return json; } +static apr_status_t spec_to_json(void *value, md_json_t *json, apr_pool_t *p, void *baton) +{ + md_json_t *jspec; + + (void)baton; + jspec = md_pkey_spec_to_json((md_pkey_spec_t*)value, p); + return md_json_setj(jspec, json, NULL); +} + +md_json_t *md_pkeys_spec_to_json(const md_pkeys_spec_t *pks, apr_pool_t *p) +{ + md_json_t *j; + + if (pks->specs->nelts == 1) { + return md_pkey_spec_to_json(md_pkeys_spec_get(pks, 0), p); + } + j = md_json_create(p); + md_json_seta(pks->specs, spec_to_json, (void*)pks, j, "specs", NULL); + return md_json_getj(j, "specs", NULL); +} + md_pkey_spec_t *md_pkey_spec_from_json(struct md_json_t *json, apr_pool_t *p) { md_pkey_spec_t *spec = apr_pcalloc(p, sizeof(*spec)); @@ -293,29 +425,161 @@ md_pkey_spec_t *md_pkey_spec_from_json(struct md_json_t *json, apr_pool_t *p) spec->params.rsa.bits = MD_PKEY_RSA_BITS_DEF; } } + else if (!apr_strnatcasecmp("EC", s)) { + spec->type = MD_PKEY_TYPE_EC; + s = md_json_gets(json, MD_KEY_CURVE, NULL); + if (s) { + spec->params.ec.curve = apr_pstrdup(p, s); + } + else { + spec->params.ec.curve = NULL; + } + } } return spec; } -int md_pkey_spec_eq(md_pkey_spec_t *spec1, md_pkey_spec_t *spec2) +static apr_status_t spec_from_json(void **pvalue, md_json_t *json, apr_pool_t *p, void *baton) +{ + (void)baton; + *pvalue = md_pkey_spec_from_json(json, p); + return APR_SUCCESS; +} + +md_pkeys_spec_t *md_pkeys_spec_from_json(struct md_json_t *json, apr_pool_t *p) +{ + md_pkeys_spec_t *pks; + md_pkey_spec_t *spec; + + pks = md_pkeys_spec_make(p); + if (md_json_is(MD_JSON_TYPE_ARRAY, json, NULL)) { + md_json_geta(pks->specs, spec_from_json, pks, json, NULL); + } + else { + spec = md_pkey_spec_from_json(json, p); + md_pkeys_spec_add(pks, spec); + } + return pks; +} + +static int pkey_spec_eq(md_pkey_spec_t *s1, md_pkey_spec_t *s2) { - if (spec1 == spec2) { + if (s1 == s2) { return 1; } - if (spec1 && spec2 && spec1->type == spec2->type) { - switch (spec1->type) { + if (s1 && s2 && s1->type == s2->type) { + switch (s1->type) { case MD_PKEY_TYPE_DEFAULT: return 1; case MD_PKEY_TYPE_RSA: - if (spec1->params.rsa.bits == spec2->params.rsa.bits) { + if (s1->params.rsa.bits == s2->params.rsa.bits) { return 1; } break; + case MD_PKEY_TYPE_EC: + if (s1->params.ec.curve == s2->params.ec.curve) { + return 1; + } + else if (!s1->params.ec.curve || !s2->params.ec.curve) { + return 0; + } + return !strcmp(s1->params.ec.curve, s2->params.ec.curve); } } return 0; } +int md_pkeys_spec_eq(md_pkeys_spec_t *pks1, md_pkeys_spec_t *pks2) +{ + int i; + + if (pks1 == pks2) { + return 1; + } + if (pks1 && pks2 && pks1->specs->nelts == pks2->specs->nelts) { + for(i = 0; i < pks1->specs->nelts; ++i) { + if (!pkey_spec_eq(APR_ARRAY_IDX(pks1->specs, i, md_pkey_spec_t *), + APR_ARRAY_IDX(pks2->specs, i, md_pkey_spec_t *))) { + return 0; + } + } + return 1; + } + return 0; +} + +static md_pkey_spec_t *pkey_spec_clone(apr_pool_t *p, md_pkey_spec_t *spec) +{ + md_pkey_spec_t *nspec; + + nspec = apr_pcalloc(p, sizeof(*nspec)); + nspec->type = spec->type; + switch (spec->type) { + case MD_PKEY_TYPE_DEFAULT: + break; + case MD_PKEY_TYPE_RSA: + nspec->params.rsa.bits = spec->params.rsa.bits; + break; + case MD_PKEY_TYPE_EC: + nspec->params.ec.curve = apr_pstrdup(p, spec->params.ec.curve); + break; + } + return nspec; +} + +const char *md_pkey_spec_name(const md_pkey_spec_t *spec) +{ + if (!spec) return "rsa"; + switch (spec->type) { + case MD_PKEY_TYPE_DEFAULT: + case MD_PKEY_TYPE_RSA: + return "rsa"; + case MD_PKEY_TYPE_EC: + return spec->params.ec.curve; + } + return "unknown"; +} + +int md_pkeys_spec_is_empty(const md_pkeys_spec_t *pks) +{ + return NULL == pks || 0 == pks->specs->nelts; +} + +md_pkeys_spec_t *md_pkeys_spec_clone(apr_pool_t *p, const md_pkeys_spec_t *pks) +{ + md_pkeys_spec_t *npks = NULL; + md_pkey_spec_t *spec; + int i; + + if (pks && pks->specs->nelts > 0) { + npks = apr_pcalloc(p, sizeof(*npks)); + npks->specs = apr_array_make(p, pks->specs->nelts, sizeof(md_pkey_spec_t*)); + for (i = 0; i < pks->specs->nelts; ++i) { + spec = APR_ARRAY_IDX(pks->specs, i, md_pkey_spec_t*); + APR_ARRAY_PUSH(npks->specs, md_pkey_spec_t*) = pkey_spec_clone(p, spec); + } + } + return npks; +} + +int md_pkeys_spec_count(const md_pkeys_spec_t *pks) +{ + return md_pkeys_spec_is_empty(pks)? 1 : pks->specs->nelts; +} + +static md_pkey_spec_t PkeySpecDef = { MD_PKEY_TYPE_DEFAULT, {{ 0 }} }; + +md_pkey_spec_t *md_pkeys_spec_get(const md_pkeys_spec_t *pks, int index) +{ + if (md_pkeys_spec_is_empty(pks)) { + return index == 1? &PkeySpecDef : NULL; + } + else if (pks && index >= 0 && index < pks->specs->nelts) { + return APR_ARRAY_IDX(pks->specs, index, md_pkey_spec_t*); + } + return NULL; +} + static md_pkey_t *make_pkey(apr_pool_t *p) { md_pkey_t *pkey = apr_pcalloc(p, sizeof(*pkey)); @@ -377,13 +641,14 @@ apr_status_t md_pkey_fload(md_pkey_t **ppkey, apr_pool_t *p, return rv; } -static apr_status_t pkey_to_buffer(buffer_rec *buffer, md_pkey_t *pkey, apr_pool_t *p, +static apr_status_t pkey_to_buffer(md_data_t *buf, md_pkey_t *pkey, apr_pool_t *p, const char *pass, apr_size_t pass_len) { BIO *bio = BIO_new(BIO_s_mem()); const EVP_CIPHER *cipher = NULL; pem_password_cb *cb = NULL; void *cb_baton = NULL; + apr_status_t rv = APR_SUCCESS; passwd_ctx ctx; unsigned long err; int i; @@ -392,7 +657,8 @@ static apr_status_t pkey_to_buffer(buffer_rec *buffer, md_pkey_t *pkey, apr_pool return APR_ENOMEM; } if (pass_len > INT_MAX) { - return APR_EINVAL; + rv = APR_EINVAL; + goto cleanup; } if (pass && pass_len > 0) { ctx.pass_phrase = pass; @@ -401,35 +667,42 @@ static apr_status_t pkey_to_buffer(buffer_rec *buffer, md_pkey_t *pkey, apr_pool cb_baton = &ctx; cipher = EVP_aes_256_cbc(); if (!cipher) { - return APR_ENOTIMPL; + rv = APR_ENOTIMPL; + goto cleanup; } } ERR_clear_error(); +#if 1 + if (!PEM_write_bio_PKCS8PrivateKey(bio, pkey->pkey, cipher, NULL, 0, cb, cb_baton)) { +#else if (!PEM_write_bio_PrivateKey(bio, pkey->pkey, cipher, NULL, 0, cb, cb_baton)) { - BIO_free(bio); +#endif err = ERR_get_error(); md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "PEM_write key: %ld %s", err, ERR_error_string(err, NULL)); - return APR_EINVAL; + rv = APR_EINVAL; + goto cleanup; } + md_data_null(buf); i = BIO_pending(bio); if (i > 0) { - buffer->data = apr_palloc(p, (apr_size_t)i + 1); - i = BIO_read(bio, buffer->data, i); - buffer->data[i] = '\0'; - buffer->len = (apr_size_t)i; + buf->data = apr_palloc(p, (apr_size_t)i); + i = BIO_read(bio, (char*)buf->data, i); + buf->len = (apr_size_t)i; } + +cleanup: BIO_free(bio); - return APR_SUCCESS; + return rv; } apr_status_t md_pkey_fsave(md_pkey_t *pkey, apr_pool_t *p, const char *pass_phrase, apr_size_t pass_len, const char *fname, apr_fileperms_t perms) { - buffer_rec buffer; + md_data_t buffer; apr_status_t rv; if (APR_SUCCESS == (rv = pkey_to_buffer(&buffer, pkey, p, pass_phrase, pass_len))) { @@ -440,6 +713,71 @@ apr_status_t md_pkey_fsave(md_pkey_t *pkey, apr_pool_t *p, return rv; } +apr_status_t md_pkey_read_http(md_pkey_t **ppkey, apr_pool_t *pool, + const struct md_http_response_t *res) +{ + apr_status_t rv; + apr_off_t data_len; + char *pem_data; + apr_size_t pem_len; + md_pkey_t *pkey; + BIO *bf; + passwd_ctx ctx; + + rv = apr_brigade_length(res->body, 1, &data_len); + if (APR_SUCCESS != rv) goto leave; + if (data_len > 1024*1024) { /* certs usually are <2k each */ + rv = APR_EINVAL; + goto leave; + } + rv = apr_brigade_pflatten(res->body, &pem_data, &pem_len, res->req->pool); + if (APR_SUCCESS != rv) goto leave; + + if (NULL == (bf = BIO_new_mem_buf(pem_data, (int)pem_len))) { + rv = APR_ENOMEM; + goto leave; + } + pkey = make_pkey(pool); + ctx.pass_phrase = NULL; + ctx.pass_len = 0; + ERR_clear_error(); + pkey->pkey = PEM_read_bio_PrivateKey(bf, NULL, NULL, &ctx); + BIO_free(bf); + + if (pkey->pkey == NULL) { + unsigned long err = ERR_get_error(); + rv = APR_EINVAL; + md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, pool, + "error loading pkey from http response: %s", + ERR_error_string(err, NULL)); + goto leave; + } + rv = APR_SUCCESS; + apr_pool_cleanup_register(pool, pkey, pkey_cleanup, apr_pool_cleanup_null); + +leave: + *ppkey = (APR_SUCCESS == rv)? pkey : NULL; + return rv; +} + +/* Determine the message digest used for signing with the given private key. + */ +static const EVP_MD *pkey_get_MD(md_pkey_t *pkey) +{ + switch (EVP_PKEY_id(pkey->pkey)) { +#ifdef NID_ED25519 + case NID_ED25519: + return NULL; +#endif +#ifdef NID_ED448 + case NID_ED448: + return NULL; +#endif + default: + return EVP_sha256(); + } +} + static apr_status_t gen_rsa(md_pkey_t **ppkey, apr_pool_t *p, unsigned int bits) { EVP_PKEY_CTX *ctx = NULL; @@ -465,6 +803,143 @@ static apr_status_t gen_rsa(md_pkey_t **ppkey, apr_pool_t *p, unsigned int bits) return rv; } +static apr_status_t check_EC_curve(int nid, apr_pool_t *p) { + EC_builtin_curve *curves = NULL; + size_t nc, i; + int rv = APR_ENOENT; + + nc = EC_get_builtin_curves(NULL, 0); + if (NULL == (curves = OPENSSL_malloc(sizeof(*curves) * nc)) || + nc != EC_get_builtin_curves(curves, nc)) { + rv = APR_EGENERAL; + md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, + "error looking up OpenSSL builtin EC curves"); + goto leave; + } + for (i = 0; i < nc; ++i) { + if (nid == curves[i].nid) { + rv = APR_SUCCESS; + break; + } + } +leave: + OPENSSL_free(curves); + return rv; +} + +static apr_status_t gen_ec(md_pkey_t **ppkey, apr_pool_t *p, const char *curve) +{ + EVP_PKEY_CTX *ctx = NULL; + apr_status_t rv; + int curve_nid = NID_undef; + + /* 1. Convert the cure into its registered identifier. Curves can be known under + * different names. + * 2. Determine, if the curve is supported by OpenSSL (or whatever is linked). + * 3. Generate the key, respecting the specific quirks some curves require. + */ + curve_nid = EC_curve_nist2nid(curve); + /* In case this fails, try some names from other standards, like SECG */ +#ifdef NID_secp384r1 + if (NID_undef == curve_nid && !apr_strnatcasecmp("secp384r1", curve)) { + curve_nid = NID_secp384r1; + curve = EC_curve_nid2nist(curve_nid); + } +#endif +#ifdef NID_X9_62_prime256v1 + if (NID_undef == curve_nid && !apr_strnatcasecmp("secp256r1", curve)) { + curve_nid = NID_X9_62_prime256v1; + curve = EC_curve_nid2nist(curve_nid); + } +#endif +#ifdef NID_X9_62_prime192v1 + if (NID_undef == curve_nid && !apr_strnatcasecmp("secp192r1", curve)) { + curve_nid = NID_X9_62_prime192v1; + curve = EC_curve_nid2nist(curve_nid); + } +#endif +#if defined(NID_X25519) && (!defined(LIBRESSL_VERSION_NUMBER) || \ + LIBRESSL_VERSION_NUMBER >= 0x3070000fL) + if (NID_undef == curve_nid && !apr_strnatcasecmp("X25519", curve)) { + curve_nid = NID_X25519; + curve = EC_curve_nid2nist(curve_nid); + } +#endif + if (NID_undef == curve_nid) { + /* OpenSSL object/curve names */ + curve_nid = OBJ_sn2nid(curve); + } + if (NID_undef == curve_nid) { + md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "ec curve unknown: %s", curve); + rv = APR_ENOTIMPL; goto leave; + } + + *ppkey = make_pkey(p); + switch (curve_nid) { + +#if defined(NID_X25519) && (!defined(LIBRESSL_VERSION_NUMBER) || \ + LIBRESSL_VERSION_NUMBER >= 0x3070000fL) + case NID_X25519: + /* no parameters */ + if (NULL == (ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_X25519, NULL)) + || EVP_PKEY_keygen_init(ctx) <= 0 + || EVP_PKEY_keygen(ctx, &(*ppkey)->pkey) <= 0) { + md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, p, + "error generate EC key for group: %s", curve); + rv = APR_EGENERAL; goto leave; + } + rv = APR_SUCCESS; + break; +#endif + +#if defined(NID_X448) && !defined(LIBRESSL_VERSION_NUMBER) + case NID_X448: + /* no parameters */ + if (NULL == (ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_X448, NULL)) + || EVP_PKEY_keygen_init(ctx) <= 0 + || EVP_PKEY_keygen(ctx, &(*ppkey)->pkey) <= 0) { + md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, p, + "error generate EC key for group: %s", curve); + rv = APR_EGENERAL; goto leave; + } + rv = APR_SUCCESS; + break; +#endif + + default: +#if OPENSSL_VERSION_NUMBER < 0x30000000L + if (APR_SUCCESS != (rv = check_EC_curve(curve_nid, p))) goto leave; + if (NULL == (ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL)) + || EVP_PKEY_paramgen_init(ctx) <= 0 + || EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, curve_nid) <= 0 + || EVP_PKEY_CTX_set_ec_param_enc(ctx, OPENSSL_EC_NAMED_CURVE) <= 0 + || EVP_PKEY_keygen_init(ctx) <= 0 + || EVP_PKEY_keygen(ctx, &(*ppkey)->pkey) <= 0) { + md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, p, + "error generate EC key for group: %s", curve); + rv = APR_EGENERAL; goto leave; + } +#else + if (APR_SUCCESS != (rv = check_EC_curve(curve_nid, p))) goto leave; + if (NULL == (ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL)) + || EVP_PKEY_keygen_init(ctx) <= 0 + || EVP_PKEY_CTX_ctrl_str(ctx, "ec_paramgen_curve", curve) <= 0 + || EVP_PKEY_keygen(ctx, &(*ppkey)->pkey) <= 0) { + md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, 0, p, + "error generate EC key for group: %s", curve); + rv = APR_EGENERAL; goto leave; + } +#endif + rv = APR_SUCCESS; + break; + } + +leave: + if (APR_SUCCESS != rv) *ppkey = NULL; + EVP_PKEY_CTX_free(ctx); + return rv; +} + apr_status_t md_pkey_gen(md_pkey_t **ppkey, apr_pool_t *p, md_pkey_spec_t *spec) { md_pkey_type_t ptype = spec? spec->type : MD_PKEY_TYPE_DEFAULT; @@ -473,6 +948,8 @@ apr_status_t md_pkey_gen(md_pkey_t **ppkey, apr_pool_t *p, md_pkey_spec_t *spec) return gen_rsa(ppkey, p, MD_PKEY_RSA_BITS_DEF); case MD_PKEY_TYPE_RSA: return gen_rsa(ppkey, p, spec->params.rsa.bits); + case MD_PKEY_TYPE_EC: + return gen_ec(ppkey, p, spec->params.ec.curve); default: return APR_ENOTIMPL; } @@ -501,59 +978,77 @@ static void RSA_get0_key(const RSA *r, static const char *bn64(const BIGNUM *b, apr_pool_t *p) { if (b) { - apr_size_t len = (apr_size_t)BN_num_bytes(b); - char *buffer = apr_pcalloc(p, len); - if (buffer) { - BN_bn2bin(b, (unsigned char *)buffer); - return md_util_base64url_encode(buffer, len, p); - } + md_data_t buffer; + + md_data_pinit(&buffer, (apr_size_t)BN_num_bytes(b), p); + if (buffer.data) { + BN_bn2bin(b, (unsigned char *)buffer.data); + return md_util_base64url_encode(&buffer, p); + } } return NULL; } const char *md_pkey_get_rsa_e64(md_pkey_t *pkey, apr_pool_t *p) { - const BIGNUM *e; - RSA *rsa = EVP_PKEY_get1_RSA(pkey->pkey); - - if (!rsa) { - return NULL; +#if OPENSSL_VERSION_NUMBER < 0x30000000L + const RSA *rsa = EVP_PKEY_get0_RSA(pkey->pkey); + if (rsa) { + const BIGNUM *e; + RSA_get0_key(rsa, NULL, &e, NULL); + return bn64(e, p); + } +#else + BIGNUM *e = NULL; + if (EVP_PKEY_get_bn_param(pkey->pkey, OSSL_PKEY_PARAM_RSA_E, &e)) { + const char *e64 = bn64(e, p); + BN_free(e); + return e64; } - RSA_get0_key(rsa, NULL, &e, NULL); - return bn64(e, p); +#endif + return NULL; } const char *md_pkey_get_rsa_n64(md_pkey_t *pkey, apr_pool_t *p) { - const BIGNUM *n; - RSA *rsa = EVP_PKEY_get1_RSA(pkey->pkey); - - if (!rsa) { - return NULL; +#if OPENSSL_VERSION_NUMBER < 0x30000000L + const RSA *rsa = EVP_PKEY_get0_RSA(pkey->pkey); + if (rsa) { + const BIGNUM *n; + RSA_get0_key(rsa, &n, NULL, NULL); + return bn64(n, p); + } +#else + BIGNUM *n = NULL; + if (EVP_PKEY_get_bn_param(pkey->pkey, OSSL_PKEY_PARAM_RSA_N, &n)) { + const char *n64 = bn64(n, p); + BN_free(n); + return n64; } - RSA_get0_key(rsa, &n, NULL, NULL); - return bn64(n, p); +#endif + return NULL; } apr_status_t md_crypt_sign64(const char **psign64, md_pkey_t *pkey, apr_pool_t *p, const char *d, size_t dlen) { EVP_MD_CTX *ctx = NULL; - char *buffer; + md_data_t buffer; unsigned int blen; const char *sign64 = NULL; apr_status_t rv = APR_ENOMEM; - - buffer = apr_pcalloc(p, (apr_size_t)EVP_PKEY_size(pkey->pkey)); - if (buffer) { + + md_data_pinit(&buffer, (apr_size_t)EVP_PKEY_size(pkey->pkey), p); + if (buffer.data) { ctx = EVP_MD_CTX_create(); if (ctx) { rv = APR_ENOTIMPL; if (EVP_SignInit_ex(ctx, EVP_sha256(), NULL)) { rv = APR_EGENERAL; if (EVP_SignUpdate(ctx, d, dlen)) { - if (EVP_SignFinal(ctx, (unsigned char*)buffer, &blen, pkey->pkey)) { - sign64 = md_util_base64url_encode(buffer, blen, p); + if (EVP_SignFinal(ctx, (unsigned char*)buffer.data, &blen, pkey->pkey)) { + buffer.len = blen; + sign64 = md_util_base64url_encode(&buffer, p); if (sign64) { rv = APR_SUCCESS; } @@ -575,56 +1070,42 @@ apr_status_t md_crypt_sign64(const char **psign64, md_pkey_t *pkey, apr_pool_t * return rv; } -static apr_status_t sha256_digest(unsigned char **pdigest, size_t *pdigest_len, - apr_pool_t *p, const char *d, size_t dlen) +static apr_status_t sha256_digest(md_data_t **pdigest, apr_pool_t *p, const md_data_t *buf) { EVP_MD_CTX *ctx = NULL; - unsigned char *buffer; + md_data_t *digest; apr_status_t rv = APR_ENOMEM; - unsigned int blen; - - buffer = apr_pcalloc(p, EVP_MAX_MD_SIZE); - if (buffer) { - ctx = EVP_MD_CTX_create(); - if (ctx) { - rv = APR_ENOTIMPL; - if (EVP_DigestInit_ex(ctx, EVP_sha256(), NULL)) { - rv = APR_EGENERAL; - if (EVP_DigestUpdate(ctx, d, dlen)) { - if (EVP_DigestFinal(ctx, buffer, &blen)) { - rv = APR_SUCCESS; - } + unsigned int dlen; + + digest = md_data_pmake(EVP_MAX_MD_SIZE, p); + ctx = EVP_MD_CTX_create(); + if (ctx) { + rv = APR_ENOTIMPL; + if (EVP_DigestInit_ex(ctx, EVP_sha256(), NULL)) { + rv = APR_EGENERAL; + if (EVP_DigestUpdate(ctx, (unsigned char*)buf->data, buf->len)) { + if (EVP_DigestFinal(ctx, (unsigned char*)digest->data, &dlen)) { + digest->len = dlen; + rv = APR_SUCCESS; } } } - - if (ctx) { - EVP_MD_CTX_destroy(ctx); - } } - - if (APR_SUCCESS == rv) { - *pdigest = buffer; - *pdigest_len = blen; - } - else { - md_log_perror(MD_LOG_MARK, MD_LOG_WARNING, rv, p, "digest"); - *pdigest = NULL; - *pdigest_len = 0; + if (ctx) { + EVP_MD_CTX_destroy(ctx); } + *pdigest = (APR_SUCCESS == rv)? digest : NULL; return rv; } -apr_status_t md_crypt_sha256_digest64(const char **pdigest64, apr_pool_t *p, - const char *d, size_t dlen) +apr_status_t md_crypt_sha256_digest64(const char **pdigest64, apr_pool_t *p, const md_data_t *d) { const char *digest64 = NULL; - unsigned char *buffer; - size_t blen; + md_data_t *digest; apr_status_t rv; - if (APR_SUCCESS == (rv = sha256_digest(&buffer, &blen, p, d, dlen))) { - if (NULL == (digest64 = md_util_base64url_encode((const char*)buffer, blen, p))) { + if (APR_SUCCESS == (rv = sha256_digest(&digest, p, d))) { + if (NULL == (digest64 = md_util_base64url_encode(digest, p))) { rv = APR_EGENERAL; } } @@ -632,47 +1113,41 @@ apr_status_t md_crypt_sha256_digest64(const char **pdigest64, apr_pool_t *p, return rv; } -static const char * const hex_const[] = { - "00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "0a", "0b", "0c", "0d", "0e", "0f", - "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "1a", "1b", "1c", "1d", "1e", "1f", - "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "2a", "2b", "2c", "2d", "2e", "2f", - "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "3a", "3b", "3c", "3d", "3e", "3f", - "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "4a", "4b", "4c", "4d", "4e", "4f", - "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "5a", "5b", "5c", "5d", "5e", "5f", - "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "6a", "6b", "6c", "6d", "6e", "6f", - "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "7a", "7b", "7c", "7d", "7e", "7f", - "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "8a", "8b", "8c", "8d", "8e", "8f", - "90", "91", "92", "93", "94", "95", "96", "97", "98", "99", "9a", "9b", "9c", "9d", "9e", "9f", - "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "aa", "ab", "ac", "ad", "ae", "af", - "b0", "b1", "b2", "b3", "b4", "b5", "b6", "b7", "b8", "b9", "ba", "bb", "bc", "bd", "be", "bf", - "c0", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9", "ca", "cb", "cc", "cd", "ce", "cf", - "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9", "da", "db", "dc", "dd", "de", "df", - "e0", "e1", "e2", "e3", "e4", "e5", "e6", "e7", "e8", "e9", "ea", "eb", "ec", "ed", "ee", "ef", - "f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "fa", "fb", "fc", "fd", "fe", "ff", -}; - apr_status_t md_crypt_sha256_digest_hex(const char **pdigesthex, apr_pool_t *p, - const char *d, size_t dlen) + const md_data_t *data) { - char *dhex = NULL, *cp; - const char * x; - unsigned char *buffer; - size_t blen; + md_data_t *digest; apr_status_t rv; - unsigned int i; - if (APR_SUCCESS == (rv = sha256_digest(&buffer, &blen, p, d, dlen))) { - cp = dhex = apr_pcalloc(p, 2 * blen + 1); - if (!dhex) { - rv = APR_EGENERAL; - } - for (i = 0; i < blen; ++i, cp += 2) { - x = hex_const[buffer[i]]; - cp[0] = x[0]; - cp[1] = x[1]; - } + if (APR_SUCCESS == (rv = sha256_digest(&digest, p, data))) { + return md_data_to_hex(pdigesthex, 0, p, digest); } - *pdigesthex = dhex; + *pdigesthex = NULL; + return rv; +} + +apr_status_t md_crypt_hmac64(const char **pmac64, const md_data_t *hmac_key, + apr_pool_t *p, const char *d, size_t dlen) +{ + const char *mac64 = NULL; + unsigned char *s; + unsigned int digest_len = 0; + md_data_t *digest; + apr_status_t rv = APR_SUCCESS; + + digest = md_data_pmake(EVP_MAX_MD_SIZE, p); + s = HMAC(EVP_sha256(), (const unsigned char*)hmac_key->data, (int)hmac_key->len, + (const unsigned char*)d, (size_t)dlen, + (unsigned char*)digest->data, &digest_len); + if (!s) { + rv = APR_EINVAL; + goto cleanup; + } + digest->len = digest_len; + mac64 = md_util_base64url_encode(digest, p); + +cleanup: + *pmac64 = (APR_SUCCESS == rv)? mac64 : NULL; return rv; } @@ -695,26 +1170,47 @@ static apr_status_t cert_cleanup(void *data) return APR_SUCCESS; } -static md_cert_t *make_cert(apr_pool_t *p, X509 *x509) +md_cert_t *md_cert_wrap(apr_pool_t *p, void *x509) { md_cert_t *cert = apr_pcalloc(p, sizeof(*cert)); cert->pool = p; cert->x509 = x509; - apr_pool_cleanup_register(p, cert, cert_cleanup, apr_pool_cleanup_null); - return cert; } -void md_cert_free(md_cert_t *cert) +md_cert_t *md_cert_make(apr_pool_t *p, void *x509) { - cert_cleanup(cert); + md_cert_t *cert = md_cert_wrap(p, x509); + apr_pool_cleanup_register(p, cert, cert_cleanup, apr_pool_cleanup_null); + return cert; } -void *md_cert_get_X509(struct md_cert_t *cert) +void *md_cert_get_X509(const md_cert_t *cert) { return cert->x509; } +const char *md_cert_get_serial_number(const md_cert_t *cert, apr_pool_t *p) +{ + const char *s = ""; + BIGNUM *bn; + const char *serial; + const ASN1_INTEGER *ai = X509_get_serialNumber(cert->x509); + if (ai) { + bn = ASN1_INTEGER_to_BN(ai, NULL); + serial = BN_bn2hex(bn); + s = apr_pstrdup(p, serial); + OPENSSL_free((void*)serial); + OPENSSL_free((void*)bn); + } + return s; +} + +int md_certs_are_equal(const md_cert_t *a, const md_cert_t *b) +{ + return X509_cmp(a->x509, b->x509) == 0; +} + int md_cert_is_valid_now(const md_cert_t *cert) { return ((X509_cmp_current_time(X509_get_notBefore(cert->x509)) < 0) @@ -726,23 +1222,31 @@ int md_cert_has_expired(const md_cert_t *cert) return (X509_cmp_current_time(X509_get_notAfter(cert->x509)) <= 0); } -apr_time_t md_cert_get_not_after(md_cert_t *cert) +apr_time_t md_cert_get_not_after(const md_cert_t *cert) { return md_asn1_time_get(X509_get_notAfter(cert->x509)); } -apr_time_t md_cert_get_not_before(md_cert_t *cert) +apr_time_t md_cert_get_not_before(const md_cert_t *cert) { return md_asn1_time_get(X509_get_notBefore(cert->x509)); } +md_timeperiod_t md_cert_get_valid(const md_cert_t *cert) +{ + md_timeperiod_t p; + p.start = md_cert_get_not_before(cert); + p.end = md_cert_get_not_after(cert); + return p; +} + int md_cert_covers_domain(md_cert_t *cert, const char *domain_name) { - if (!cert->alt_names) { - md_cert_get_alt_names(&cert->alt_names, cert, cert->pool); - } - if (cert->alt_names) { - return md_array_str_index(cert->alt_names, domain_name, 0, 0) >= 0; + apr_array_header_t *alt_names; + + md_cert_get_alt_names(&alt_names, cert, cert->pool); + if (alt_names) { + return md_array_str_index(alt_names, domain_name, 0, 0) >= 0; } return 0; } @@ -760,7 +1264,7 @@ int md_cert_covers_md(md_cert_t *cert, const md_t *md) cert->alt_names->nelts); for (i = 0; i < md->domains->nelts; ++i) { name = APR_ARRAY_IDX(md->domains, i, const char *); - if (md_array_str_index(cert->alt_names, name, 0, 0) < 0) { + if (!md_dns_domains_match(cert->alt_names, name)) { md_log_perror(MD_LOG_MARK, MD_LOG_TRACE1, 0, cert->pool, "md domain %s not covered by cert", name); return 0; @@ -774,7 +1278,7 @@ int md_cert_covers_md(md_cert_t *cert, const md_t *md) return 0; } -apr_status_t md_cert_get_issuers_uri(const char **puri, md_cert_t *cert, apr_pool_t *p) +apr_status_t md_cert_get_issuers_uri(const char **puri, const md_cert_t *cert, apr_pool_t *p) { apr_status_t rv = APR_ENOENT; STACK_OF(ACCESS_DESCRIPTION) *xinfos; @@ -801,17 +1305,19 @@ apr_status_t md_cert_get_issuers_uri(const char **puri, md_cert_t *cert, apr_poo return rv; } -apr_status_t md_cert_get_alt_names(apr_array_header_t **pnames, md_cert_t *cert, apr_pool_t *p) +apr_status_t md_cert_get_alt_names(apr_array_header_t **pnames, const md_cert_t *cert, apr_pool_t *p) { apr_array_header_t *names; apr_status_t rv = APR_ENOENT; STACK_OF(GENERAL_NAME) *xalt_names; unsigned char *buf; int i; - + xalt_names = X509_get_ext_d2i(cert->x509, NID_subject_alt_name, NULL, NULL); if (xalt_names) { GENERAL_NAME *cval; + const unsigned char *ip; + int len; names = apr_array_make(p, sk_GENERAL_NAME_num(xalt_names), sizeof(char *)); for (i = 0; i < sk_GENERAL_NAME_num(xalt_names); ++i) { @@ -819,11 +1325,33 @@ apr_status_t md_cert_get_alt_names(apr_array_header_t **pnames, md_cert_t *cert, switch (cval->type) { case GEN_DNS: case GEN_URI: - case GEN_IPADD: ASN1_STRING_to_UTF8(&buf, cval->d.ia5); APR_ARRAY_PUSH(names, const char *) = apr_pstrdup(p, (char*)buf); OPENSSL_free(buf); break; + case GEN_IPADD: + len = ASN1_STRING_length(cval->d.iPAddress); +#if OPENSSL_VERSION_NUMBER < 0x10100000L + ip = ASN1_STRING_data(cval->d.iPAddress); +#else + ip = ASN1_STRING_get0_data(cval->d.iPAddress); +#endif + if (len == 4) /* IPv4 address */ + APR_ARRAY_PUSH(names, const char *) = apr_psprintf(p, "%u.%u.%u.%u", + ip[0], ip[1], ip[2], ip[3]); + else if (len == 16) /* IPv6 address */ + APR_ARRAY_PUSH(names, const char *) = apr_psprintf(p, "%02x%02x%02x%02x:" + "%02x%02x%02x%02x:" + "%02x%02x%02x%02x:" + "%02x%02x%02x%02x", + ip[0], ip[1], ip[2], ip[3], + ip[4], ip[5], ip[6], ip[7], + ip[8], ip[9], ip[10], ip[11], + ip[12], ip[13], ip[14], ip[15]); + else { + ; /* Unknown address type - Log? Assert? */ + } + break; default: break; } @@ -848,7 +1376,7 @@ apr_status_t md_cert_fload(md_cert_t **pcert, apr_pool_t *p, const char *fname) x509 = PEM_read_X509(f, NULL, NULL, NULL); rv = fclose(f); if (x509 != NULL) { - cert = make_cert(p, x509); + cert = md_cert_make(p, x509); } else { rv = APR_EINVAL; @@ -859,7 +1387,7 @@ apr_status_t md_cert_fload(md_cert_t **pcert, apr_pool_t *p, const char *fname) return rv; } -static apr_status_t cert_to_buffer(buffer_rec *buffer, md_cert_t *cert, apr_pool_t *p) +static apr_status_t cert_to_buffer(md_data_t *buffer, const md_cert_t *cert, apr_pool_t *p) { BIO *bio = BIO_new(BIO_s_mem()); int i; @@ -877,9 +1405,8 @@ static apr_status_t cert_to_buffer(buffer_rec *buffer, md_cert_t *cert, apr_pool i = BIO_pending(bio); if (i > 0) { - buffer->data = apr_palloc(p, (apr_size_t)i + 1); - i = BIO_read(bio, buffer->data, i); - buffer->data[i] = '\0'; + buffer->data = apr_palloc(p, (apr_size_t)i); + i = BIO_read(bio, (char*)buffer->data, i); buffer->len = (apr_size_t)i; } BIO_free(bio); @@ -889,64 +1416,194 @@ static apr_status_t cert_to_buffer(buffer_rec *buffer, md_cert_t *cert, apr_pool apr_status_t md_cert_fsave(md_cert_t *cert, apr_pool_t *p, const char *fname, apr_fileperms_t perms) { - buffer_rec buffer; + md_data_t buffer; apr_status_t rv; - + + md_data_null(&buffer); if (APR_SUCCESS == (rv = cert_to_buffer(&buffer, cert, p))) { return md_util_freplace(fname, perms, p, fwrite_buffer, &buffer); } return rv; } -apr_status_t md_cert_to_base64url(const char **ps64, md_cert_t *cert, apr_pool_t *p) +apr_status_t md_cert_to_base64url(const char **ps64, const md_cert_t *cert, apr_pool_t *p) { - buffer_rec buffer; + md_data_t buffer; apr_status_t rv; - + + md_data_null(&buffer); if (APR_SUCCESS == (rv = cert_to_buffer(&buffer, cert, p))) { - *ps64 = md_util_base64url_encode(buffer.data, buffer.len, p); + *ps64 = md_util_base64url_encode(&buffer, p); return APR_SUCCESS; } *ps64 = NULL; return rv; } +apr_status_t md_cert_to_sha256_digest(md_data_t **pdigest, const md_cert_t *cert, apr_pool_t *p) +{ + md_data_t *digest; + unsigned int dlen; + + digest = md_data_pmake(EVP_MAX_MD_SIZE, p); + X509_digest(cert->x509, EVP_sha256(), (unsigned char*)digest->data, &dlen); + digest->len = dlen; + + *pdigest = digest; + return APR_SUCCESS; +} + +apr_status_t md_cert_to_sha256_fingerprint(const char **pfinger, const md_cert_t *cert, apr_pool_t *p) +{ + md_data_t *digest; + apr_status_t rv; + + rv = md_cert_to_sha256_digest(&digest, cert, p); + if (APR_SUCCESS == rv) { + return md_data_to_hex(pfinger, 0, p, digest); + } + *pfinger = NULL; + return rv; +} + +static int md_cert_read_pem(BIO *bf, apr_pool_t *p, md_cert_t **pcert) +{ + md_cert_t *cert; + X509 *x509; + apr_status_t rv = APR_ENOENT; + + ERR_clear_error(); + x509 = PEM_read_bio_X509(bf, NULL, NULL, NULL); + if (x509 == NULL) goto cleanup; + cert = md_cert_make(p, x509); + rv = APR_SUCCESS; +cleanup: + *pcert = (APR_SUCCESS == rv)? cert : NULL; + return rv; +} + +apr_status_t md_cert_read_chain(apr_array_header_t *chain, apr_pool_t *p, + const char *pem, apr_size_t pem_len) +{ + BIO *bf = NULL; + apr_status_t rv = APR_SUCCESS; + md_cert_t *cert; + int added = 0; + + if (NULL == (bf = BIO_new_mem_buf(pem, (int)pem_len))) { + rv = APR_ENOMEM; + goto cleanup; + } + while (APR_SUCCESS == (rv = md_cert_read_pem(bf, chain->pool, &cert))) { + APR_ARRAY_PUSH(chain, md_cert_t *) = cert; + added = 1; + } + if (APR_ENOENT == rv && added) { + rv = APR_SUCCESS; + } + +cleanup: + md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, rv, p, "read chain with %d certs", chain->nelts); + if (bf) BIO_free(bf); + return rv; +} + apr_status_t md_cert_read_http(md_cert_t **pcert, apr_pool_t *p, const md_http_response_t *res) { const char *ct; apr_off_t data_len; + char *der; apr_size_t der_len; + md_cert_t *cert = NULL; apr_status_t rv; ct = apr_table_get(res->headers, "Content-Type"); - if (!res->body || !ct || strcmp("application/pkix-cert", ct)) { - return APR_ENOENT; + ct = md_util_parse_ct(res->req->pool, ct); + if (!res->body || !ct || strcmp("application/pkix-cert", ct)) { + rv = APR_ENOENT; + goto out; } if (APR_SUCCESS == (rv = apr_brigade_length(res->body, 1, &data_len))) { - char *der; if (data_len > 1024*1024) { /* certs usually are <2k each */ return APR_EINVAL; } - if (APR_SUCCESS == (rv = apr_brigade_pflatten(res->body, &der, &der_len, p))) { + if (APR_SUCCESS == (rv = apr_brigade_pflatten(res->body, &der, &der_len, res->req->pool))) { const unsigned char *bf = (const unsigned char*)der; X509 *x509; if (NULL == (x509 = d2i_X509(NULL, &bf, (long)der_len))) { rv = APR_EINVAL; + goto out; } else { - *pcert = make_cert(p, x509); + cert = md_cert_make(p, x509); rv = APR_SUCCESS; + md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, rv, p, + "parsing cert from content-type=%s, content-length=%ld", ct, (long)data_len); } } - md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, rv, p, "cert parsed"); } +out: + *pcert = (APR_SUCCESS == rv)? cert : NULL; return rv; } -md_cert_state_t md_cert_state_get(md_cert_t *cert) +apr_status_t md_cert_chain_read_http(struct apr_array_header_t *chain, + apr_pool_t *p, const struct md_http_response_t *res) +{ + const char *ct = NULL; + apr_off_t blen; + apr_size_t data_len = 0; + char *data; + md_cert_t *cert; + apr_status_t rv = APR_ENOENT; + + md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, p, + "chain_read, processing %d response", res->status); + if (APR_SUCCESS != (rv = apr_brigade_length(res->body, 1, &blen))) goto cleanup; + if (blen > 1024*1024) { /* certs usually are <2k each */ + rv = APR_EINVAL; + goto cleanup; + } + + data_len = (apr_size_t)blen; + ct = apr_table_get(res->headers, "Content-Type"); + if (!res->body || !ct) goto cleanup; + ct = md_util_parse_ct(res->req->pool, ct); + if (!strcmp("application/pkix-cert", ct)) { + rv = md_cert_read_http(&cert, p, res); + if (APR_SUCCESS != rv) goto cleanup; + APR_ARRAY_PUSH(chain, md_cert_t *) = cert; + } + else if (!strcmp("application/pem-certificate-chain", ct) + || !strncmp("text/plain", ct, sizeof("text/plain")-1)) { + /* Some servers seem to think 'text/plain' is sufficient, see #232 */ + rv = apr_brigade_pflatten(res->body, &data, &data_len, res->req->pool); + if (APR_SUCCESS != rv) goto cleanup; + rv = md_cert_read_chain(chain, res->req->pool, data, data_len); + } + else { + md_log_perror(MD_LOG_MARK, MD_LOG_DEBUG, 0, p, + "attempting to parse certificates from unrecognized content-type: %s", ct); + rv = apr_brigade_pflatten(res->body, &data, &data_len, res->req->pool); + if (APR_SUCCESS != rv) goto cleanup; + rv = md_cert_read_chain(chain, res->req->pool, data, data_len); + if (APR_SUCCESS == rv && chain->nelts == 0) { + md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, + "certificate chain response did not contain any certificates " + "(suspicious content-type: %s)", ct); + rv = APR_ENOENT; + } + } +cleanup: + md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, rv, p, + "parsed certs from content-type=%s, content-length=%ld", ct, (long)data_len); + return rv; +} + +md_cert_state_t md_cert_state_get(const md_cert_t *cert) { if (cert->x509) { return md_cert_is_valid_now(cert)? MD_CERT_VALID : MD_CERT_EXPIRED; @@ -966,7 +1623,7 @@ apr_status_t md_chain_fappend(struct apr_array_header_t *certs, apr_pool_t *p, c if (rv == APR_SUCCESS) { ERR_clear_error(); while (NULL != (x509 = PEM_read_X509(f, NULL, NULL, NULL))) { - cert = make_cert(p, x509); + cert = md_cert_make(p, x509); APR_ARRAY_PUSH(certs, md_cert_t *) = cert; } fclose(f); @@ -1064,18 +1721,22 @@ static apr_status_t add_ext(X509 *x, int nid, const char *value, apr_pool_t *p) X509V3_CTX ctx; apr_status_t rv; + ERR_clear_error(); X509V3_set_ctx_nodb(&ctx); X509V3_set_ctx(&ctx, x, x, NULL, NULL, 0); if (NULL == (ext = X509V3_EXT_conf_nid(NULL, &ctx, nid, (char*)value))) { + unsigned long err = ERR_get_error(); + md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "add_ext, create, nid=%d value='%s' " + "(lib=%d, reason=%d)", nid, value, ERR_GET_LIB(err), ERR_GET_REASON(err)); return APR_EGENERAL; } ERR_clear_error(); rv = X509_add_ext(x, ext, -1)? APR_SUCCESS : APR_EINVAL; if (APR_SUCCESS != rv) { - md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "add_ext nid=%dd value='%s'", - nid, value); - + unsigned long err = ERR_get_error(); + md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "add_ext, add, nid=%d value='%s' " + "(lib=%d, reason=%d)", nid, value, ERR_GET_LIB(err), ERR_GET_REASON(err)); } X509_EXTENSION_free(ext); return rv; @@ -1100,116 +1761,114 @@ static apr_status_t sk_add_alt_names(STACK_OF(X509_EXTENSION) *exts, #define MD_OID_MUST_STAPLE_SNAME "tlsfeature" #define MD_OID_MUST_STAPLE_LNAME "TLS Feature" -static int get_must_staple_nid(void) -{ - /* Funny API, the OID for must staple might be configured or - * might be not. In the second case, we need to add it. But adding - * when it already is there is an error... */ - int nid = OBJ_txt2nid(MD_OID_MUST_STAPLE_NUM); - if (NID_undef == nid) { - nid = OBJ_create(MD_OID_MUST_STAPLE_NUM, - MD_OID_MUST_STAPLE_SNAME, MD_OID_MUST_STAPLE_LNAME); - } - return nid; -} - -int md_cert_must_staple(md_cert_t *cert) +int md_cert_must_staple(const md_cert_t *cert) { /* In case we do not get the NID for it, we treat this as not set. */ - int nid = get_must_staple_nid(); + int nid = MD_GET_NID(MUST_STAPLE); return ((NID_undef != nid)) && X509_get_ext_by_NID(cert->x509, nid, -1) >= 0; } -static apr_status_t add_must_staple(STACK_OF(X509_EXTENSION) *exts, const md_t *md, apr_pool_t *p) +static apr_status_t add_must_staple(STACK_OF(X509_EXTENSION) *exts, const char *name, apr_pool_t *p) { + X509_EXTENSION *x; + int nid; - if (md->must_staple) { - X509_EXTENSION *x; - int nid; - - nid = get_must_staple_nid(); - if (NID_undef == nid) { - md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, - "%s: unable to get NID for v3 must-staple TLS feature", md->name); - return APR_ENOTIMPL; - } - x = X509V3_EXT_conf_nid(NULL, NULL, nid, (char*)"DER:30:03:02:01:05"); - if (NULL == x) { - md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, - "%s: unable to create x509 extension for must-staple", md->name); - return APR_EGENERAL; - } - sk_X509_EXTENSION_push(exts, x); + nid = MD_GET_NID(MUST_STAPLE); + if (NID_undef == nid) { + md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, + "%s: unable to get NID for v3 must-staple TLS feature", name); + return APR_ENOTIMPL; + } + x = X509V3_EXT_conf_nid(NULL, NULL, nid, (char*)"DER:30:03:02:01:05"); + if (NULL == x) { + md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, + "%s: unable to create x509 extension for must-staple", name); + return APR_EGENERAL; } + sk_X509_EXTENSION_push(exts, x); return APR_SUCCESS; } -apr_status_t md_cert_req_create(const char **pcsr_der_64, const md_t *md, +apr_status_t md_cert_req_create(const char **pcsr_der_64, const char *name, + apr_array_header_t *domains, int must_staple, md_pkey_t *pkey, apr_pool_t *p) { - const char *s, *csr_der, *csr_der_64 = NULL; + const char *s, *csr_der_64 = NULL; const unsigned char *domain; X509_REQ *csr; X509_NAME *n = NULL; STACK_OF(X509_EXTENSION) *exts = NULL; apr_status_t rv; + md_data_t csr_der; int csr_der_len; - assert(md->domains->nelts > 0); - + assert(domains->nelts > 0); + md_data_null(&csr_der); + if (NULL == (csr = X509_REQ_new()) || NULL == (exts = sk_X509_EXTENSION_new_null()) || NULL == (n = X509_NAME_new())) { rv = APR_ENOMEM; - md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: openssl alloc X509 things", md->name); + md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: openssl alloc X509 things", name); goto out; } /* subject name == first domain */ - domain = APR_ARRAY_IDX(md->domains, 0, const unsigned char *); - if (!X509_NAME_add_entry_by_txt(n, "CN", MBSTRING_ASC, domain, -1, -1, 0) - || !X509_REQ_set_subject_name(csr, n)) { - md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "%s: REQ name add entry", md->name); + domain = APR_ARRAY_IDX(domains, 0, const unsigned char *); + /* Do not set the domain in the CN if it is longer than 64 octets. + * Instead, let the CA choose a 'proper' name. At the moment (2021-01), LE will + * inspect all SAN names and use one < 64 chars if it can be found. It will fail + * otherwise. + * The reason we do not check this beforehand is that the restrictions on CNs + * are in flux. They used to be authoritative, now browsers no longer do that, but + * no one wants to hand out a cert with "google.com" as CN either. So, we leave + * it for the CA to decide if and how it hands out a cert for this or fails. + * This solves issue where the name is too long, see #227 */ + if (strlen((const char*)domain) < 64 + && (!X509_NAME_add_entry_by_txt(n, "CN", MBSTRING_ASC, domain, -1, -1, 0) + || !X509_REQ_set_subject_name(csr, n))) { + md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "%s: REQ name add entry", name); rv = APR_EGENERAL; goto out; } /* collect extensions, such as alt names and must staple */ - if (APR_SUCCESS != (rv = sk_add_alt_names(exts, md->domains, p))) { - md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: collecting alt names", md->name); + if (APR_SUCCESS != (rv = sk_add_alt_names(exts, domains, p))) { + md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: collecting alt names", name); rv = APR_EGENERAL; goto out; } - if (APR_SUCCESS != (rv = add_must_staple(exts, md, p))) { + if (must_staple && APR_SUCCESS != (rv = add_must_staple(exts, name, p))) { md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: you requested that a certificate " "is created with the 'must-staple' extension, however the SSL library was " "unable to initialized that extension. Please file a bug report on which platform " "and with which library this happens. To continue before this problem is resolved, " - "configure 'MDMustStaple off' for your domains", md->name); + "configure 'MDMustStaple off' for your domains", name); rv = APR_EGENERAL; goto out; } /* add extensions to csr */ if (sk_X509_EXTENSION_num(exts) > 0 && !X509_REQ_add_extensions(csr, exts)) { - md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: adding exts", md->name); + md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: adding exts", name); rv = APR_EGENERAL; goto out; } /* add our key */ if (!X509_REQ_set_pubkey(csr, pkey->pkey)) { - md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: set pkey in csr", md->name); + md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: set pkey in csr", name); rv = APR_EGENERAL; goto out; } /* sign, der encode and base64url encode */ - if (!X509_REQ_sign(csr, pkey->pkey, EVP_sha256())) { - md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: sign csr", md->name); + if (!X509_REQ_sign(csr, pkey->pkey, pkey_get_MD(pkey))) { + md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: sign csr", name); rv = APR_EGENERAL; goto out; } if ((csr_der_len = i2d_X509_REQ(csr, NULL)) < 0) { - md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: der length", md->name); + md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: der length", name); rv = APR_EGENERAL; goto out; } - s = csr_der = apr_pcalloc(p, (apr_size_t)csr_der_len + 1); + csr_der.len = (apr_size_t)csr_der_len; + s = csr_der.data = apr_pcalloc(p, csr_der.len + 1); if (i2d_X509_REQ(csr, (unsigned char**)&s) != csr_der_len) { - md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: csr der enc", md->name); + md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: csr der enc", name); rv = APR_EGENERAL; goto out; } - csr_der_64 = md_util_base64url_encode(csr_der, (apr_size_t)csr_der_len, p); + csr_der_64 = md_util_base64url_encode(&csr_der, p); rv = APR_SUCCESS; out: @@ -1226,20 +1885,16 @@ out: return rv; } -apr_status_t md_cert_self_sign(md_cert_t **pcert, const char *cn, - apr_array_header_t *domains, md_pkey_t *pkey, - apr_interval_time_t valid_for, apr_pool_t *p) +static apr_status_t mk_x509(X509 **px, md_pkey_t *pkey, const char *cn, + apr_interval_time_t valid_for, apr_pool_t *p) { - X509 *x; + X509 *x = NULL; X509_NAME *n = NULL; - md_cert_t *cert = NULL; - apr_status_t rv; - int days; BIGNUM *big_rnd = NULL; ASN1_INTEGER *asn1_rnd = NULL; unsigned char rnd[20]; - - assert(domains); + int days; + apr_status_t rv; if (NULL == (x = X509_new()) || NULL == (n = X509_NAME_new())) { @@ -1247,24 +1902,22 @@ apr_status_t md_cert_self_sign(md_cert_t **pcert, const char *cn, md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "%s: openssl alloc X509 things", cn); goto out; } - + if (APR_SUCCESS != (rv = md_rand_bytes(rnd, sizeof(rnd), p)) || !(big_rnd = BN_bin2bn(rnd, sizeof(rnd), NULL)) || !(asn1_rnd = BN_to_ASN1_INTEGER(big_rnd, NULL))) { md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "%s: setup random serial", cn); rv = APR_EGENERAL; goto out; } - - if (1 != X509_set_version(x, 2L)) { - md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "%s: setting x.509v3", cn); - rv = APR_EGENERAL; goto out; - } - if (!X509_set_serialNumber(x, asn1_rnd)) { md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "%s: set serial number", cn); rv = APR_EGENERAL; goto out; } - /* set common name and issue */ + if (1 != X509_set_version(x, 2L)) { + md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, "%s: setting x.509v3", cn); + rv = APR_EGENERAL; goto out; + } + /* set common name and issuer */ if (!X509_NAME_add_entry_by_txt(n, "CN", MBSTRING_ASC, (const unsigned char*)cn, -1, -1, 0) || !X509_set_subject_name(x, n) || !X509_set_issuer_name(x, n)) { @@ -1272,21 +1925,16 @@ apr_status_t md_cert_self_sign(md_cert_t **pcert, const char *cn, rv = APR_EGENERAL; goto out; } /* cert are unconstrained (but not very trustworthy) */ - if (APR_SUCCESS != (rv = add_ext(x, NID_basic_constraints, "CA:FALSE, pathlen:0", p))) { + if (APR_SUCCESS != (rv = add_ext(x, NID_basic_constraints, "critical,CA:FALSE", p))) { md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: set basic constraints ext", cn); goto out; } - /* add the domain as alt name */ - if (APR_SUCCESS != (rv = add_ext(x, NID_subject_alt_name, alt_names(domains, p), p))) { - md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: set alt_name ext", cn); - goto out; - } /* add our key */ if (!X509_set_pubkey(x, pkey->pkey)) { md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: set pkey in x509", cn); rv = APR_EGENERAL; goto out; } - + /* validity */ days = (int)((apr_time_sec(valid_for) + MD_SECS_PER_DAY - 1)/ MD_SECS_PER_DAY); if (!X509_set_notBefore(x, ASN1_TIME_set(NULL, time(NULL)))) { rv = APR_EGENERAL; goto out; @@ -1295,29 +1943,217 @@ apr_status_t md_cert_self_sign(md_cert_t **pcert, const char *cn, rv = APR_EGENERAL; goto out; } +out: + *px = (APR_SUCCESS == rv)? x : NULL; + if (APR_SUCCESS != rv && x) X509_free(x); + if (big_rnd) BN_free(big_rnd); + if (asn1_rnd) ASN1_INTEGER_free(asn1_rnd); + if (n) X509_NAME_free(n); + return rv; +} + +apr_status_t md_cert_self_sign(md_cert_t **pcert, const char *cn, + apr_array_header_t *domains, md_pkey_t *pkey, + apr_interval_time_t valid_for, apr_pool_t *p) +{ + X509 *x; + md_cert_t *cert = NULL; + apr_status_t rv; + + assert(domains); + + if (APR_SUCCESS != (rv = mk_x509(&x, pkey, cn, valid_for, p))) goto out; + + /* add the domain as alt name */ + if (APR_SUCCESS != (rv = add_ext(x, NID_subject_alt_name, alt_names(domains, p), p))) { + md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: set alt_name ext", cn); + goto out; + } + + /* keyUsage, ExtendedKeyUsage */ + + if (APR_SUCCESS != (rv = add_ext(x, NID_key_usage, "critical,digitalSignature", p))) { + md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: set keyUsage", cn); + goto out; + } + if (APR_SUCCESS != (rv = add_ext(x, NID_ext_key_usage, "serverAuth", p))) { + md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: set extKeyUsage", cn); + goto out; + } + /* sign with same key */ - if (!X509_sign(x, pkey->pkey, EVP_sha256())) { + if (!X509_sign(x, pkey->pkey, pkey_get_MD(pkey))) { md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: sign x509", cn); rv = APR_EGENERAL; goto out; } - cert = make_cert(p, x); + cert = md_cert_make(p, x); + rv = APR_SUCCESS; + +out: + *pcert = (APR_SUCCESS == rv)? cert : NULL; + if (!cert && x) X509_free(x); + return rv; +} + +#define MD_OID_ACME_VALIDATION_NUM "1.3.6.1.5.5.7.1.31" +#define MD_OID_ACME_VALIDATION_SNAME "pe-acmeIdentifier" +#define MD_OID_ACME_VALIDATION_LNAME "ACME Identifier" + +static int get_acme_validation_nid(void) +{ + int nid = OBJ_txt2nid(MD_OID_ACME_VALIDATION_NUM); + if (NID_undef == nid) { + nid = OBJ_create(MD_OID_ACME_VALIDATION_NUM, + MD_OID_ACME_VALIDATION_SNAME, MD_OID_ACME_VALIDATION_LNAME); + } + return nid; +} + +apr_status_t md_cert_make_tls_alpn_01(md_cert_t **pcert, const char *domain, + const char *acme_id, md_pkey_t *pkey, + apr_interval_time_t valid_for, apr_pool_t *p) +{ + X509 *x; + md_cert_t *cert = NULL; + const char *alts; + apr_status_t rv; + + if (APR_SUCCESS != (rv = mk_x509(&x, pkey, "tls-alpn-01-challenge", valid_for, p))) goto out; + + /* add the domain as alt name */ + alts = apr_psprintf(p, "DNS:%s", domain); + if (APR_SUCCESS != (rv = add_ext(x, NID_subject_alt_name, alts, p))) { + md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: set alt_name ext", domain); + goto out; + } + + if (APR_SUCCESS != (rv = add_ext(x, get_acme_validation_nid(), acme_id, p))) { + md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: set pe-acmeIdentifier", domain); + goto out; + } + + /* sign with same key */ + if (!X509_sign(x, pkey->pkey, pkey_get_MD(pkey))) { + md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: sign x509", domain); + rv = APR_EGENERAL; goto out; + } + + cert = md_cert_make(p, x); rv = APR_SUCCESS; out: if (!cert && x) { X509_free(x); } - if (n) { - X509_NAME_free(n); + *pcert = (APR_SUCCESS == rv)? cert : NULL; + return rv; +} + +#define MD_OID_CT_SCTS_NUM "1.3.6.1.4.1.11129.2.4.2" +#define MD_OID_CT_SCTS_SNAME "CT-SCTs" +#define MD_OID_CT_SCTS_LNAME "CT Certificate SCTs" + +#ifndef OPENSSL_NO_CT +static int get_ct_scts_nid(void) +{ + int nid = OBJ_txt2nid(MD_OID_CT_SCTS_NUM); + if (NID_undef == nid) { + nid = OBJ_create(MD_OID_CT_SCTS_NUM, + MD_OID_CT_SCTS_SNAME, MD_OID_CT_SCTS_LNAME); } - if (big_rnd) { - BN_free(big_rnd); + return nid; +} +#endif + +const char *md_nid_get_sname(int nid) +{ + return OBJ_nid2sn(nid); +} + +const char *md_nid_get_lname(int nid) +{ + return OBJ_nid2ln(nid); +} + +apr_status_t md_cert_get_ct_scts(apr_array_header_t *scts, apr_pool_t *p, const md_cert_t *cert) +{ +#ifndef OPENSSL_NO_CT + int nid, i, idx, critical; + STACK_OF(SCT) *sct_list; + SCT *sct_handle; + md_sct *sct; + size_t len; + const char *data; + + nid = get_ct_scts_nid(); + if (NID_undef == nid) return APR_ENOTIMPL; + + idx = -1; + while (1) { + sct_list = X509_get_ext_d2i(cert->x509, nid, &critical, &idx); + if (sct_list) { + for (i = 0; i < sk_SCT_num(sct_list); i++) { + sct_handle = sk_SCT_value(sct_list, i); + if (sct_handle) { + sct = apr_pcalloc(p, sizeof(*sct)); + sct->version = SCT_get_version(sct_handle); + sct->timestamp = apr_time_from_msec(SCT_get_timestamp(sct_handle)); + len = SCT_get0_log_id(sct_handle, (unsigned char**)&data); + sct->logid = md_data_make_pcopy(p, data, len); + sct->signature_type_nid = SCT_get_signature_nid(sct_handle); + len = SCT_get0_signature(sct_handle, (unsigned char**)&data); + sct->signature = md_data_make_pcopy(p, data, len); + + APR_ARRAY_PUSH(scts, md_sct*) = sct; + } + } + } + if (idx < 0) break; } - if (asn1_rnd) { - ASN1_INTEGER_free(asn1_rnd); + md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, p, "ct_sct, found %d SCT extensions", scts->nelts); + return APR_SUCCESS; +#else + (void)scts; + (void)p; + (void)cert; + return APR_ENOTIMPL; +#endif +} + +apr_status_t md_cert_get_ocsp_responder_url(const char **purl, apr_pool_t *p, const md_cert_t *cert) +{ + STACK_OF(OPENSSL_STRING) *ssk; + apr_status_t rv = APR_SUCCESS; + const char *url = NULL; + + ssk = X509_get1_ocsp(md_cert_get_X509(cert)); + if (!ssk) { + rv = APR_ENOENT; + goto cleanup; } - *pcert = (APR_SUCCESS == rv)? cert : NULL; + url = apr_pstrdup(p, sk_OPENSSL_STRING_value(ssk, 0)); + md_log_perror(MD_LOG_MARK, MD_LOG_TRACE2, 0, p, "ocsp responder found '%s'", url); + +cleanup: + if (ssk) X509_email_free(ssk); + *purl = url; return rv; } +apr_status_t md_check_cert_and_pkey(struct apr_array_header_t *certs, md_pkey_t *pkey) +{ + const md_cert_t *cert; + + if (certs->nelts == 0) { + return APR_ENOENT; + } + + cert = APR_ARRAY_IDX(certs, 0, const md_cert_t*); + + if (1 != X509_check_private_key(cert->x509, pkey->pkey)) { + return APR_EGENERAL; + } + + return APR_SUCCESS; +} |