[rs-commit] r428 - in /mod_cms_sign/trunk: mod_cms_sign.c test.sh

rs-commit at redwax.eu rs-commit at redwax.eu
Fri Aug 13 14:45:30 CEST 2021


Author: dirkx at redwax.eu
Date: Fri Aug 13 14:45:29 2021
New Revision: 428

Log:
Fix memory leak/workaround for missing EVP_PKEY_dup() prior to OpenSSL3, add test script, make it possible for JSON to be switched off too.

Added:
    mod_cms_sign/trunk/test.sh   (with props)
Modified:
    mod_cms_sign/trunk/mod_cms_sign.c

Modified: mod_cms_sign/trunk/mod_cms_sign.c
==============================================================================
--- mod_cms_sign/trunk/mod_cms_sign.c	(original)
+++ mod_cms_sign/trunk/mod_cms_sign.c	Fri Aug 13 14:45:29 2021
@@ -43,6 +43,7 @@
 
 #include <openssl/err.h>
 #include <openssl/pem.h>
+#include <openssl/evp.h>
 #include <openssl/cms.h>
 #include <openssl/obj_mac.h>
 
@@ -60,13 +61,14 @@
 module AP_MODULE_DECLARE_DATA cms_sign_module;
 
 typedef enum {
-    OF_DER = 0, OF_JSON
+    OF_UNSET =0, OF_DER, OF_JSON
 } sign_output_type;
 
 typedef struct {
+    int _openssl_needs_free; // Prior to openssl 3 there is no EVP_PKEY_dup();
     X509 *cert;
     EVP_PKEY *key;
-             STACK_OF(X509) * extra_certs;
+    STACK_OF(X509) * extra_certs;
     sign_output_type tp;
 } sign_config_rec;
 
@@ -86,7 +88,8 @@
 static apr_status_t sign_config_rec_cleanup(void *data)
 {
     sign_config_rec *rec = (sign_config_rec *) data;
-    EVP_PKEY_free(rec->key);
+    if (rec->_openssl_needs_free) 
+	    EVP_PKEY_free(rec->key);
     X509_free(rec->cert);
     sk_X509_pop_free(rec->extra_certs, X509_free);
     return APR_SUCCESS;
@@ -108,7 +111,7 @@
 static void *_create_dir_config(apr_pool_t * p, char *dir)
 {
     sign_config_rec *conf = apr_pcalloc(p, sizeof(sign_config_rec));
-    conf->tp = OF_DER;
+    conf->tp = OF_UNSET;
 
     if ((conf->extra_certs = sk_X509_new(NULL)) == NULL) {
 	ap_log_perror(APLOG_MARK, APLOG_ERR, 0, p, HANDLER ": out of memory");
@@ -131,8 +134,9 @@
     sign_config_rec *add = (sign_config_rec *) addv;
     sign_config_rec *base = (sign_config_rec *) basev;
 
-    new->key = (add->key == 0) ? base->key : add->key;
-    new->cert = (add->cert == 0) ? base->cert : add->cert;
+    new->tp= add->tp? add->tp: base->tp; 
+    new->key = add->key ? add->key : base->key;
+    new->cert = X509_dup(add->cert ? add->cert : base->cert);
 
     _merge_sk_X509(new->extra_certs, base->extra_certs);
     _merge_sk_X509(new->extra_certs, add->extra_certs);
@@ -170,17 +174,19 @@
 	    if (EVP_PKEY_cmp(key, conf->key) != 1)
 		return apr_psprintf(cmd->pool, "The cert file %s contains a key that is "
                    "not identical to the one specified with CMSSignKey. Aborting.", arg);
+
 	    ap_log_perror(APLOG_MARK, APLOG_WARNING, 0, cmd->pool, "The cert file %s "
                    "contains a key; which is ignored in lieu of the identical CMSSignKey "
                    "specified key.", arg);
-	}
+            EVP_PKEY_free(key);
+        } else {
+            conf->key = key;
+	    conf->_openssl_needs_free = 1;
+        };
     };
 
     if (conf->key && conf->cert && 1 != EVP_PKEY_cmp(conf->key, X509_get_pubkey(conf->cert)))
 	return apr_psprintf(cmd->pool, "The publci key in the cert in %s does not match the private key. Aborting.", arg);
-
-    EVP_PKEY_free(conf->key);
-    conf->key = key;
 
     BIO_free(in);
     return NULL;
@@ -213,10 +219,11 @@
 	if (EVP_PKEY_cmp(key, conf->key) != 1)
 	    return apr_psprintf(cmd->pool, "The key file %s contains a key that is not identical to the one specified with CMSSignCert . Aborting.", arg);
 	ap_log_perror(APLOG_MARK, APLOG_WARNING, 0, cmd->pool, "The cert file %s contains a key; which is ignored in lieu of the CMSSignKey specified key.", arg);
-    };
-
-    EVP_PKEY_free(conf->key);
+
+        EVP_PKEY_free(conf->key);
+    };
     conf->key = key;
+    conf->_openssl_needs_free = 1;
 
     if (conf->key && conf->cert && 1 != EVP_PKEY_cmp(conf->key, X509_get_pubkey(conf->cert)))
 	return apr_psprintf(cmd->pool, "The private key file %s does not match the public one specified with CMSSignCert . Aborting.", arg);
@@ -255,6 +262,7 @@
 {
     sign_config_rec *conf = dconf;
     sign_output_type tp;
+
     if (strcasecmp("DER", arg) == 0)
 	tp = OF_DER;
     else if (strcasecmp("JSON", arg) == 0)
@@ -311,14 +319,17 @@
     apr_bucket *e;
     apr_status_t rv;
 
+    X509 * cert = conf->cert;
+    EVP_PKEY * key = conf->key;
+
     if (APR_BRIGADE_EMPTY(bb)) {
 	return APR_SUCCESS;
     }
 
-    if (!conf || !conf->cert || !conf->key || !conf->extra_certs) {
+    if (!conf || !cert || !key || !conf->extra_certs) {
 	ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, HANDLER ": not configured%s%s",
-		      conf->cert ? "" : ", no CMSSignCert",
-		      conf->key ? "" : ", no CMSSignKey");
+		      cert ? "" : ", no CMSSignCert",
+		      key ? "" : ", no CMSSignKey");
 	return HTTP_INTERNAL_SERVER_ERROR;
     }
 
@@ -345,14 +356,14 @@
 			  ERR_reason_error_string(ERR_get_error()));
 	    return HTTP_INTERNAL_SERVER_ERROR;
 	};
-	if (!(CMS_add1_signer(state->ci, conf->cert, conf->key, EVP_get_digestbynid(DEFAULT_MD), state->flags))) {
+	if (!(CMS_add1_signer(state->ci, cert, key, EVP_get_digestbynid(DEFAULT_MD), state->flags))) {
 	    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, HANDLER ": add signer failed: %s",
 			  ERR_reason_error_string(ERR_get_error()));
 	    return HTTP_INTERNAL_SERVER_ERROR;
 	}
 
 	apr_table_unset(f->r->headers_out, "Content-Length");
-	f->r->content_type = conf->tp == OF_DER ? "application/cms" : "application/json";
+	f->r->content_type = conf->tp != OF_JSON ? "application/cms" : "application/json";
 
 	if (conf->tp == OF_JSON) {
 	    apr_bucket_brigade *pass_bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
@@ -455,16 +466,15 @@
 		return HTTP_INTERNAL_SERVER_ERROR;
 	    }
 
-	    if (conf->tp == OF_DER) {
-		rv = apr_brigade_write(pass_bb, NULL, NULL, data, length);
-	    }
-	    else {
+	    if (conf->tp == OF_JSON) {
 		rv = _brigade_base64_write(pass_bb, data, length, state->b64, state->mem);
 		if (rv == APR_SUCCESS)
 		    rv = _brigade_base64_final(pass_bb, state->b64, state->mem);
 		if (rv == APR_SUCCESS)
 		    rv = apr_brigade_puts(pass_bb, NULL, NULL, "\"}");
-	    };
+	    } else {
+		rv = apr_brigade_write(pass_bb, NULL, NULL, data, length);
+	    }
 
 	    if (rv != APR_SUCCESS) {
 		ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, HANDLER ": write error.");

Added: mod_cms_sign/trunk/test.sh
==============================================================================
--- mod_cms_sign/trunk/test.sh	(added)
+++ mod_cms_sign/trunk/test.sh	Fri Aug 13 14:45:29 2021
@@ -0,0 +1,162 @@
+#!/bin/sh
+#
+# Create a signing certificate issued by a subordinate CA; and the 2 levels of CA bove that.
+# Start up a webserver with a trival config.
+# Test a signed response from both a proxy and a local file.
+#
+set -e
+
+TMPDIR=${TMPDIR:-/tmp}
+CA_PREFIX=${CA_PREFIX:-${TMPDIR}/ca}
+CERT_PREFIX=${CERT_PREFIX:-${TMPDIR}/cert}
+CHAIN_PREFIX=${CHAIN_PREFIX:-${TMPDIR}/chain}
+EXTFILE=${EXTFILE:-${TMPDIR}/ext.cnf}
+OPENSSL=${OPENSSL:-openssl}
+HTTPD_EXTRA_CONF=${HTTPD_EXTRA_CONF:-${TMPDIR}/httpd-extra.conf}
+HTTPD=${HTTPD:-httpd}
+
+test -f "${EXTFILE}" || \
+cat > ${EXTFILE} <<EOM
+[ subca ]
+keyUsage = cRLSign, keyCertSign
+subjectKeyIdentifier=hash
+authorityKeyIdentifier=keyid:always,issuer
+basicConstraints = CA:TRUE
+
+[ leaf ]
+nsComment = For testing only and no this is not the real thing. Duh.
+keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+subjectKeyIdentifier=hash
+authorityKeyIdentifier=keyid:always,issuer
+basicConstraints = CA:FALSE
+EOM
+
+test -f "${CA_PREFIX}.key" || \
+	$OPENSSL req -x509 -new -nodes -extensions v3_ca \
+		-subj "/CN=CA $$" \
+		-out "${CA_PREFIX}.pem" \
+		-keyout "${CA_PREFIX}.key" 
+
+test -f "${CA_PREFIX}-sub.key" || \
+	$OPENSSL req -new -nodes \
+		-keyout "${CA_PREFIX}-sub.key" \
+		-subj '/CN=Sub under CA' |\
+	$OPENSSL x509 -req \
+		-extfile "${EXTFILE}" -extensions subca \
+		-set_serial 1000 -out "${CA_PREFIX}-sub.pem" \
+		-CAkey "${CA_PREFIX}.key" -CA "${CA_PREFIX}.pem"  
+
+test -f "${CA_PREFIX}-sub-sub.key" || \
+	$OPENSSL req -new -nodes \
+		-keyout "${CA_PREFIX}-sub-sub.key" \
+		-subj '/CN=Sub under CA Sub' |\
+	$OPENSSL x509 -req \
+		-extfile "${EXTFILE}" -extensions subca \
+		-set_serial 2000 \
+		-CAkey "${CA_PREFIX}-sub.key" -CA "${CA_PREFIX}-sub.pem"  \
+		-out "${CA_PREFIX}-sub-sub.pem"
+
+test -f "${CERT_PREFIX}.key" || \
+	$OPENSSL req -new -nodes \
+		-keyout "${CERT_PREFIX}.key" \
+		-subj "/CN=Signing Party" | \
+	$OPENSSL x509 -req \
+		-extfile "${EXTFILE}" -extensions leaf \
+		-set_serial 3001 \
+		-CAkey "${CA_PREFIX}-sub-sub.key" -CA "${CA_PREFIX}-sub-sub.pem"  \
+		-out "${CERT_PREFIX}.pem"
+
+# Remove the keys we do not need for this test.
+#
+rm "${CA_PREFIX}.key" "${CA_PREFIX}-sub.key" "${CA_PREFIX}-sub-sub.key" 
+
+# Combine the key/cert as is common in the webserver world.
+#
+cat "${CERT_PREFIX}.key" "${CERT_PREFIX}.pem" > "${CERT_PREFIX}.crt"
+rm  "${CERT_PREFIX}.key" "${CERT_PREFIX}.pem"
+
+# Create the _sub_ chain to include with teh signed payload.
+cat "${CA_PREFIX}-sub.pem" "${CA_PREFIX}-sub-sub.pem" > "${CA_PREFIX}-chain.pem"
+
+cat > "${HTTPD_EXTRA_CONF}" <<EOM
+CMSSigningCertificate  "${CERT_PREFIX}.crt"
+CMSAddCerts            "${CA_PREFIX}-chain.pem"
+
+LoadModule cms_sign_module "${PWD}/.libs/mod_cms_sign.so"
+
+<IfModule !proxy_module>
+LoadModule proxy_module lib/apache2/modules/mod_proxy.so
+</IfModule>
+
+<IfModule !proxy_http_module>
+LoadModule proxy_http_module lib/apache2/modules/mod_proxy_http.so
+</IfModule>
+
+LogLevel Debug
+ErrorLog  "${TMPDIR}/error_log"
+
+<Location /signed_site>
+     SetOutputFilter cmssign
+     ProxyPass http://neverssl.com/
+</Location>
+
+<Location /signed_json>
+     SetOutputFilter cmssign
+     CMSSigningOutputFormat  JSON
+     ProxyPass http://neverssl.com/
+</Location>
+
+# Simply sign the main file
+<Location /index.html>
+     SetOutputFilter cmssign
+</Location>
+EOM
+
+# Check config
+#
+"$HTTPD" -t -c "Include \"${HTTPD_EXTRA_CONF}\""
+
+# Start minimal server based on defaults.
+#
+"$HTTPD" -X -c "Include \"${HTTPD_EXTRA_CONF}\"" &
+HTTP_PID=$!
+echo Waiting for webserver to start.
+sleep 1
+
+tail -F "$TMPDIR/error_log" &
+TAIL_PID=$!
+
+echo
+echo 
+echo Starting tests:
+
+L=$(curl --silent http://neverssl.com/ | ${OPENSSL} sha256 )
+M=$(curl --silent http://localhost/signed_site | ${OPENSSL} cms -verify -inform DER -CAfile $TMPDIR/ca.pem |  $OPENSSL sha256)
+test "$L" == "$M"
+echo Proxy fetch OK
+
+curl --silent http://localhost/signed_json | jq -r .payload  |base64 -d > "$TMPDIR/payload.raw"
+N=$(cat  "$TMPDIR/payload.raw" | ${OPENSSL} sha256 )
+test "$N" == "$L"
+
+curl --silent http://localhost/signed_json | jq -r .signature| base64 -d > "$TMPDIR/payload.sig"
+O=$(${OPENSSL}  cms -verify -inform DER -content "$TMPDIR/payload.raw" -CAfile "$TMPDIR/ca.pem" -in "$TMPDIR/payload.sig" -binary | openssl sha256)
+test "$O" == "$L"
+
+echo JSON ok.
+
+curl --silent  http://localhost/index.html | ${OPENSSL}  cms -verify -inform DER -CAfile $TMPDIR/ca.pem > /dev/null
+echo Static content ok.
+
+echo
+echo
+echo Clenaup.
+kill ${HTTP_PID} 
+sleep 1
+
+kill ${TAIL_PID}
+
+rm "${CA_PREFIX}-chain.pem"  "${HTTPD_EXTRA_CONF}" "${CA_PREFIX}.pem" "${CA_PREFIX}-sub.pem" "${CA_PREFIX}-sub-sub.pem" "${EXTFILE}" "${CERT_PREFIX}.crt" "$TMPDIR/payload.sig"  "$TMPDIR/payload.raw" "${TMPDIR}/error_log" 
+
+echo All was well.
+

Propchange: mod_cms_sign/trunk/test.sh
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: mod_cms_sign/trunk/test.sh
------------------------------------------------------------------------------
    svn:executable = *

Propchange: mod_cms_sign/trunk/test.sh
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision



More information about the rs-commit mailing list