[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