]> granicus.if.org Git - apache/blobdiff - modules/md/md_crypt.c
*) mod_md: bringing over v2.0.6 from github.
[apache] / modules / md / md_crypt.c
index 2a6dd24428c7f7046b73a9c0001845825bbccbc1..acb62d345683a58c52f0adae518c2c908a91bf6e 100644 (file)
@@ -1,18 +1,19 @@
-/* Copyright 2017 greenbytes GmbH (https://www.greenbytes.de)
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ *     http://www.apache.org/licenses/LICENSE-2.0
  *
- * http://www.apache.org/licenses/LICENSE-2.0
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 #include <assert.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <process.h>
 #endif
 
+#if defined(LIBRESSL_VERSION_NUMBER)
+/* Missing from LibreSSL */
+#define MD_USE_OPENSSL_PRE_1_1_API (LIBRESSL_VERSION_NUMBER < 0x2070000f)
+#else /* defined(LIBRESSL_VERSION_NUMBER) */
+#define MD_USE_OPENSSL_PRE_1_1_API (OPENSSL_VERSION_NUMBER < 0x10100000L)
+#endif
+
+#if defined(LIBRESSL_VERSION_NUMBER) || (OPENSSL_VERSION_NUMBER < 0x10100000L) 
+/* Missing from LibreSSL 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 {
@@ -134,17 +153,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 *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)
@@ -189,7 +204,7 @@ static int pem_passwd(char *buf, int size, int rwflag, void *baton)
  */
 static apr_time_t md_asn1_time_get(const ASN1_TIME* time)
 {
-#ifdef LIBRESSL_VERSION_NUMBER
+#if OPENSSL_VERSION_NUMBER < 0x10002000L || defined(LIBRESSL_VERSION_NUMBER)
     /* courtesy: https://stackoverflow.com/questions/10975542/asn1-time-to-time-t-conversion#11263731
      * all bugs are mine */
     apr_time_exp_t t;
@@ -369,7 +384,7 @@ 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 *buf, md_pkey_t *pkey, apr_pool_t *p,
                                    const char *pass, apr_size_t pass_len)
 {
     BIO *bio = BIO_new(BIO_s_mem());
@@ -408,10 +423,9 @@ static apr_status_t pkey_to_buffer(buffer_rec *buffer, md_pkey_t *pkey, 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->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;
     }
     BIO_free(bio);
     return APR_SUCCESS;
@@ -421,7 +435,7 @@ 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 buffer;
     apr_status_t rv;
     
     if (APR_SUCCESS == (rv = pkey_to_buffer(&buffer, pkey, p, pass_phrase, pass_len))) {
@@ -470,7 +484,8 @@ apr_status_t md_pkey_gen(md_pkey_t **ppkey, apr_pool_t *p, md_pkey_spec_t *spec)
     }
 }
 
-#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
+#if MD_USE_OPENSSL_PRE_1_1_API || (defined(LIBRESSL_VERSION_NUMBER) && \
+                                   LIBRESSL_VERSION_NUMBER < 0x2070000f)
 
 #ifndef NID_tlsfeature
 #define NID_tlsfeature          1020
@@ -566,56 +581,47 @@ 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 **pdigest, apr_pool_t *p, const md_data *buf)
 {
     EVP_MD_CTX *ctx = NULL;
-    unsigned char *buffer;
+    md_data *digest;
     apr_status_t rv = APR_ENOMEM;
-    unsigned int blen;
+    unsigned int dlen;
+
+    digest = apr_palloc(p, sizeof(*digest));
+    if (!digest) goto leave;
+    digest->data = apr_pcalloc(p, EVP_MAX_MD_SIZE);
+    if (!digest->data) goto leave;
     
-    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;
-                    }
+    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;
+leave:
+    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 *d)
 {
     const char *digest64 = NULL;
-    unsigned char *buffer;
-    size_t blen;
+    md_data *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->data, digest->len, p))) {
             rv = APR_EGENERAL;
         }
     }
@@ -623,47 +629,16 @@ 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 *data)
 {
-    char *dhex = NULL, *cp;
-    const char * x;
-    unsigned char *buffer;
-    size_t blen;
+    md_data *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;
 }
 
@@ -701,11 +676,28 @@ void md_cert_free(md_cert_t *cert)
     cert_cleanup(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 = "";
+    const ASN1_INTEGER *ai = X509_get_serialNumber(cert->x509);
+    if (ai) {
+        BIGNUM *bn; 
+        const char *hex;
+        
+        bn = ASN1_INTEGER_to_BN(ai, NULL);
+        hex = BN_bn2hex(bn);
+        s = apr_pstrdup(p, hex);
+        OPENSSL_free((void*)bn);
+        OPENSSL_free((void*)hex);
+    }
+    return s;
+}
+
 int md_cert_is_valid_now(const md_cert_t *cert)
 {
     return ((X509_cmp_current_time(X509_get_notBefore(cert->x509)) < 0)
@@ -717,23 +709,23 @@ 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));
 }
 
 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;
 }
@@ -751,7 +743,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;
@@ -765,38 +757,34 @@ 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)
 {
-    int i, ext_idx, nid = NID_info_access;
-    X509_EXTENSION *ext;
-    X509V3_EXT_METHOD *ext_cls;
-    void *ext_data;
-    const char *uri = NULL;
     apr_status_t rv = APR_ENOENT;
-    
-    /* Waddle through x509  API history to get someone that may be able
-     * to hand us the issuer url for the cert chain */
-    ext_idx = X509_get_ext_by_NID(cert->x509, nid, -1);
-    ext = (ext_idx >= 0)? X509_get_ext(cert->x509, ext_idx) : NULL;
-    ext_cls = ext? (X509V3_EXT_METHOD*)X509V3_EXT_get(ext) : NULL;
-    if (ext_cls && (ext_data = X509_get_ext_d2i(cert->x509, nid, 0, 0))) {
-        CONF_VALUE *cval;
-        STACK_OF(CONF_VALUE) *ext_vals = ext_cls->i2v(ext_cls, ext_data, 0);
-        
-        for (i = 0; i < sk_CONF_VALUE_num(ext_vals); ++i) {
-            cval = sk_CONF_VALUE_value(ext_vals, i);
-            if (!strcmp("CA Issuers - URI", cval->name)) {
-                uri = apr_pstrdup(p, cval->value);
+    STACK_OF(ACCESS_DESCRIPTION) *xinfos;
+    const char *uri = NULL;
+    unsigned char *buf;
+    int i;
+
+    xinfos = X509_get_ext_d2i(cert->x509, NID_info_access, NULL, NULL);
+    if (xinfos) {
+        for (i = 0; i < sk_ACCESS_DESCRIPTION_num(xinfos); i++) {
+            ACCESS_DESCRIPTION *val = sk_ACCESS_DESCRIPTION_value(xinfos, i);
+            if (OBJ_obj2nid(val->method) == NID_ad_ca_issuers
+                    && val->location && val->location->type == GEN_URI) {
+                ASN1_STRING_to_UTF8(&buf, val->location->d.uniformResourceIdentifier);
+                uri = apr_pstrdup(p, (char *)buf);
+                OPENSSL_free(buf);
                 rv = APR_SUCCESS;
                 break;
             }
         }
+        sk_ACCESS_DESCRIPTION_pop_free(xinfos, ACCESS_DESCRIPTION_free);
     } 
     *puri = (APR_SUCCESS == rv)? uri : NULL;
     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;
@@ -804,7 +792,7 @@ apr_status_t md_cert_get_alt_names(apr_array_header_t **pnames, md_cert_t *cert,
     unsigned char *buf;
     int i;
     
-    xalt_names = (GENERAL_NAMES*)X509_get_ext_d2i(cert->x509, NID_subject_alt_name, NULL, NULL);
+    xalt_names = X509_get_ext_d2i(cert->x509, NID_subject_alt_name, NULL, NULL);
     if (xalt_names) {
         GENERAL_NAME *cval;
         
@@ -823,6 +811,7 @@ apr_status_t md_cert_get_alt_names(apr_array_header_t **pnames, md_cert_t *cert,
                     break;
             }
         }
+        sk_GENERAL_NAME_pop_free(xalt_names, GENERAL_NAME_free);
         rv = APR_SUCCESS;
     }
     *pnames = (APR_SUCCESS == rv)? names : NULL;
@@ -853,7 +842,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 *buffer, const md_cert_t *cert, apr_pool_t *p)
 {
     BIO *bio = BIO_new(BIO_s_mem());
     int i;
@@ -871,9 +860,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);
@@ -883,7 +871,7 @@ 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 buffer;
     apr_status_t rv;
     
     if (APR_SUCCESS == (rv = cert_to_buffer(&buffer, cert, p))) {
@@ -892,9 +880,9 @@ apr_status_t md_cert_fsave(md_cert_t *cert, apr_pool_t *p,
     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 buffer;
     apr_status_t rv;
     
     if (APR_SUCCESS == (rv = cert_to_buffer(&buffer, cert, p))) {
@@ -905,42 +893,159 @@ apr_status_t md_cert_to_base64url(const char **ps64, md_cert_t *cert, apr_pool_t
     return rv;
 }
 
+apr_status_t md_cert_to_sha256_digest(md_data **pdigest, const md_cert_t *cert, apr_pool_t *p)
+{
+    md_data *digest;
+    unsigned int dlen;
+    apr_status_t rv = APR_ENOMEM;
+    
+    digest = apr_palloc(p, sizeof(*digest));
+    if (!digest) goto leave;
+    digest->data = apr_pcalloc(p, EVP_MAX_MD_SIZE);
+    if (!digest->data) goto leave;
+
+    X509_digest(cert->x509, EVP_sha256(), (unsigned char*)digest->data, &dlen);
+    digest->len = dlen;
+    rv = APR_SUCCESS;
+leave:
+    *pdigest = (APR_SUCCESS == rv)? digest : NULL;
+    return rv;
+}
+
+apr_status_t md_cert_to_sha256_fingerprint(const char **pfinger, const md_cert_t *cert, apr_pool_t *p)
+{
+    md_data *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;
+    
+    ERR_clear_error();
+    x509 = PEM_read_bio_X509(bf, NULL, NULL, NULL);
+    if (x509 == NULL) {
+        rv = APR_ENOENT;
+        goto out;
+    }
+    cert = make_cert(p, x509);
+    rv = APR_SUCCESS;
+    
+out:
+    *pcert = (APR_SUCCESS == rv)? cert : NULL;
+    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;
+    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 = make_cert(p, x509);
+                rv = APR_SUCCESS;
+                md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, rv, p, "cert parsed");
+            }
+        }
+    }
+out:
+    *pcert = (APR_SUCCESS == rv)? cert : NULL;
+    return rv;
+}
+
+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;
+    apr_off_t blen;
+    apr_size_t data_len;
+    char *data;
+    BIO *bf = NULL;
+    apr_status_t rv;
+    
+    if (APR_SUCCESS != (rv = apr_brigade_length(res->body, 1, &blen))) goto out;
+    if (blen > 1024*1024) { /* certs usually are <2k each */
+        rv = APR_EINVAL;
+        goto out;
+    }
+    
+    data_len = (apr_size_t)blen;
+    ct = apr_table_get(res->headers, "Content-Type");
+    if (!res->body || !ct) {
+        rv = APR_ENOENT;
+        goto out;
+    }
+    else if (!strcmp("application/pem-certificate-chain", ct)) {
+        if (APR_SUCCESS == (rv = apr_brigade_pflatten(res->body, &data, &data_len, res->req->pool))) {
+            int added = 0;
+            md_cert_t *cert;
+            
+            if (NULL == (bf = BIO_new_mem_buf(data, (int)data_len))) {
+                rv = APR_ENOMEM;
+                goto out;
+            }
+            
+            while (APR_SUCCESS == (rv = md_cert_read_pem(bf, p, &cert))) {
+                APR_ARRAY_PUSH(chain, md_cert_t *) = cert;
+                added = 1;
+            }
+            if (APR_ENOENT == rv && added) {
                 rv = APR_SUCCESS;
             }
         }
         md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, rv, p, "cert parsed");
     }
+    else if (!strcmp("application/pkix-cert", ct)) {
+        md_cert_t *cert;
+        
+        rv = md_cert_read_http(&cert, p, res);
+        if (APR_SUCCESS == rv) {
+            APR_ARRAY_PUSH(chain, md_cert_t *) = cert;
+        }
+    }
+    else {
+        /* unrecongized content type */
+        rv = APR_ENOENT;
+        goto out;
+    }
+out:
+    if (bf) BIO_free(bf);
     return rv;
 }
 
-md_cert_state_t md_cert_state_get(md_cert_t *cert)
+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;
@@ -1058,18 +1163,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;
@@ -1090,31 +1199,53 @@ static apr_status_t sk_add_alt_names(STACK_OF(X509_EXTENSION) *exts,
     return APR_SUCCESS;
 }
 
-static apr_status_t add_must_staple(STACK_OF(X509_EXTENSION) *exts, const md_t *md, apr_pool_t *p)
+#define MD_OID_MUST_STAPLE_NUM          "1.3.6.1.5.5.7.1.24"
+#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(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();
+    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 char *name, apr_pool_t *p)
+{
+    X509_EXTENSION *x;
+    int nid;
     
-    if (md->must_staple) {
-        X509_EXTENSION *x;
-        int nid;
-        
-        nid = OBJ_create("1.3.6.1.5.5.7.1.24", "OCSPReq", "OCSP Request");
-        if (NID_undef == nid) {
-            md_log_perror(MD_LOG_MARK, MD_LOG_ERR, 0, p, 
-                          "%s: unable to get NID for must-staple", md->name);
-            return APR_EGENERAL;
-        }
-        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 get x509 extension for must-staple", md->name);
-            return APR_EGENERAL;
-        }
-        sk_X509_EXTENSION_push(exts, x);
+    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", 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;
@@ -1125,54 +1256,58 @@ apr_status_t md_cert_req_create(const char **pcsr_der_64, const md_t *md,
     apr_status_t rv;
     int csr_der_len;
     
-    assert(md->domains->nelts > 0);
+    assert(domains->nelts > 0);
     
     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 *);
+    domain = APR_ARRAY_IDX(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);
+        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))) {
-        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: must staple", md->name);
+    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", 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);
+        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);
     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);
@@ -1192,20 +1327,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())) {
@@ -1213,24 +1344,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)) {
@@ -1242,17 +1371,12 @@ apr_status_t md_cert_self_sign(md_cert_t **pcert, const char *cn,
         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;
@@ -1261,6 +1385,33 @@ 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;
+    }
+
     /* sign with same key */
     if (!X509_sign(x, pkey->pkey, EVP_sha256())) {
         md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: sign x509", cn);
@@ -1271,19 +1422,127 @@ apr_status_t md_cert_self_sign(md_cert_t **pcert, const char *cn,
     rv = APR_SUCCESS;
     
 out:
-    if (!cert && x) {
-        X509_free(x);
+    *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);
     }
-    if (n) {
-        X509_NAME_free(n);
+    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, domain, 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 (big_rnd) {
-        BN_free(big_rnd);
+
+    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, EVP_sha256())) {
+        md_log_perror(MD_LOG_MARK, MD_LOG_ERR, rv, p, "%s: sign x509", domain);
+        rv = APR_EGENERAL; goto out;
     }
-    if (asn1_rnd) {
-        ASN1_INTEGER_free(asn1_rnd);
+
+    cert = make_cert(p, x);
+    rv = APR_SUCCESS;
+    
+out:
+    if (!cert && x) {
+        X509_free(x);
     }
     *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" 
+
+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);
+    }
+    return nid;
+}
+
+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_create(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_create(p, data, len);
+                    
+                    APR_ARRAY_PUSH(scts, md_sct*) = sct;
+                }
+            }
+        }
+        if (idx < 0) break;
+    }
+    md_log_perror(MD_LOG_MARK, MD_LOG_TRACE3, 0, p, "ct_sct, found %d SCT extensions", scts->nelts);
+    return APR_SUCCESS;
+#else
+    return APR_ENOTIMPL;
+#endif
+}