[rs-commit] r419 - in /mod_cms_sign/trunk: README mod_cms_sign.c
rs-commit at redwax.eu
rs-commit at redwax.eu
Thu Aug 5 00:59:36 CEST 2021
Author: dirkx at redwax.eu
Date: Thu Aug 5 00:59:35 2021
New Revision: 419
Log:
Add JSON variation as well - not quite streaming; as there does not seem to be an interface to pass (just) the digest.
Modified:
mod_cms_sign/trunk/README
mod_cms_sign/trunk/mod_cms_sign.c
Modified: mod_cms_sign/trunk/README
==============================================================================
--- mod_cms_sign/trunk/README (original)
+++ mod_cms_sign/trunk/README Thu Aug 5 00:59:35 2021
@@ -3,15 +3,28 @@
Typical config:
-<Location /signed_files>
- # SetOutputFilter cmssign
- AddOutputFilter cmssign .txt
+ CMSSigningCertificate /etc/pki/certs/sign.cert
- CMSSigningCertificate /etc/pki/certs/sign.cert
- CMSSigningKey /etc/pki/certs/sign.key
- CMSAddCerts /etc/pki/certs/chain.pem
-</Location>
-</IfModule>
+ # Optional - e.g. if above sign.cert already contains the key too.
+ #
+ # CMSSigningKey /etc/pki/certs/sign.key
+
+ # Optional - normally contains the chain up to, but not including the CA.
+ #
+ # CMSAddCerts /etc/pki/certs/chain.pem
+
+ <Location /signed_files>
+ SetOutputFilter cmssign
+ </Locatin>
+
+ # Or limit the siging to certain files
+ # AddOutputFilter cmssign .txt
+
+
+ <Location /somebackend>
+ SetOutputFilter cmssign
+ ProxyPass http:/.....
+ </Location>
# And check this with something like
@@ -19,4 +32,16 @@
or
curl --silent http://localhost:8080/ | openssl pkcs7 -inform DER -noout -print
+In addition one can also set the flag:
+ CMSSigningOutputFormat JSON
+
+in which case a simple JSON consisting of
+
+ {
+ "payload":".. base64 ..",
+ "signature":"... base64 .."
+ }
+
+will be output. The signature is a detached signature this time; of the decoded base64 of the payload. The signature is
+again a DER formatted CMS/PKCS#7; but detached this time.
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 Thu Aug 5 00:59:35 2021
@@ -59,16 +59,22 @@
module AP_MODULE_DECLARE_DATA cms_sign_module;
+typedef enum {
+ OF_DER = 0, OF_JSON
+} sign_output_type;
+
typedef struct {
X509 *cert;
EVP_PKEY *key;
STACK_OF(X509) * extra_certs;
-} sign_config_rec;
+ sign_output_type tp;
+} sign_config_rec;
typedef struct {
CMS_ContentInfo *ci;
- BIO *in, *out;
-} cms_filter_state;
+ BIO *in, *out, *b64, *mem;
+ unsigned int flags;
+} cms_filter_state;
static apr_status_t _cleanup(void *data)
{
@@ -92,6 +98,8 @@
if (state) {
BIO_free(state->out);
BIO_free(state->in);
+ BIO_free(state->b64);
+ BIO_free(state->mem);
CMS_ContentInfo_free(state->ci);
};
return APR_SUCCESS;
@@ -105,7 +113,8 @@
static void *_create_dir_config(apr_pool_t * p, char *dir)
{
- sign_config_rec *conf = apr_pcalloc(p, sizeof(sign_config_rec)); /* null key ptrs. */
+ sign_config_rec *conf = apr_pcalloc(p, sizeof(sign_config_rec));
+ conf->tp = OF_DER;
if ((conf->extra_certs = sk_X509_new(NULL)) == NULL) {
ap_log_perror(APLOG_MARK, APLOG_ERR, 0, p, HANDLER ": out of memory");
@@ -163,8 +172,11 @@
if (key) {
if (conf->key) {
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);
+ 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);
}
};
@@ -216,7 +228,6 @@
return NULL;
}
-
static const char *add_cms_certs(cmd_parms *cmd, void *dconf,
const char *arg)
{
@@ -241,6 +252,58 @@
};
return NULL;
+}
+
+static const char *set_cms_output_format(cmd_parms *cmd, void *dconf,
+ const char *arg)
+{
+ sign_config_rec *conf = dconf;
+ sign_output_type tp;
+ if (strcasecmp("DER", arg) == 0)
+ tp = OF_DER;
+ else if (strcasecmp("JSON", arg) == 0)
+ tp = OF_JSON;
+ else
+ return "Output format can only be DER (default) or JSON.";
+
+ conf->tp = tp;
+ return NULL;
+}
+
+static apr_status_t _brigade_base64_write(apr_bucket_brigade * bb, const char *data, apr_size_t length, BIO * b64, BIO * mem)
+{
+ apr_status_t rv = APR_SUCCESS;
+ while (length) {
+ char buff[1024 * 64];
+ #define MAXIN (sizeof(buff)/3*4-2)
+ rv = HTTP_INTERNAL_SERVER_ERROR;
+
+ int blen = BIO_write(b64, (void *)data, length > MAXIN ? MAXIN : length);
+ if (blen <= 0)
+ break;
+
+ data += blen;
+ length -= blen;
+
+ blen = BIO_read(mem, buff, sizeof(buff));
+ if (blen <= -1)
+ break;
+ if (blen <= 0) {
+ rv = APR_SUCCESS;
+ continue;
+ };
+ if ((rv = apr_brigade_write(bb, NULL, NULL, buff, blen)) != APR_SUCCESS)
+ break;
+ };
+ return rv;
+}
+
+static apr_status_t _brigade_base64_final(apr_bucket_brigade * bb, BIO * b64, BIO * mem)
+{
+ BIO_flush(b64);
+ const char *ptr;
+ long len = BIO_get_mem_data(mem, &ptr);
+ return (len > 0) ? apr_brigade_write(bb, NULL, NULL, ptr, len) : APR_SUCCESS;
}
static apr_status_t _out_filter(ap_filter_t * f, apr_bucket_brigade * bb)
@@ -250,13 +313,14 @@
apr_read_type_e mode = APR_NONBLOCK_READ;
cms_filter_state *state = f->ctx;
apr_bucket *e;
+ apr_status_t rv;
if (APR_BRIGADE_EMPTY(bb)) {
return APR_SUCCESS;
}
if (!conf || !conf->cert || !conf->key || !conf->extra_certs) {
- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, HANDLER ": not configured%s%s",
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, HANDLER ": not configured%s%s",
conf->cert ? "" : ", no CMSSignCert",
conf->key ? "" : ", no CMSSignKey");
return HTTP_INTERNAL_SERVER_ERROR;
@@ -268,21 +332,46 @@
state->in = BIO_new(BIO_s_mem());
state->out = BIO_new(BIO_s_mem());
- state->ci = CMS_sign(NULL, NULL, conf->extra_certs, state->in, CMS_BINARY | CMS_STREAM);
-
- if (!state->in || !state->ci || !state->out) {
- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, HANDLER ": state init failed: %s",
+ state->mem = BIO_new(BIO_s_mem());
+ state->b64 = BIO_new(BIO_f_base64());
+
+ BIO_push(state->b64, state->mem);
+ BIO_set_flags(state->b64, BIO_FLAGS_BASE64_NO_NL);
+
+ state->flags = CMS_BINARY | CMS_STREAM | CMS_NOSMIMECAP;
+ if (conf->tp == OF_JSON)
+ state->flags |= CMS_DETACHED;
+
+ state->ci = CMS_sign(NULL, NULL, conf->extra_certs, state->in, state->flags | CMS_PARTIAL);
+
+ if (!state->in || !state->ci || !state->out || !state->b64 || !state->mem) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, HANDLER ": state init failed: %s",
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), 0))) {
- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, HANDLER ": add signer failed: %s",
+ if (!(CMS_add1_signer(state->ci, conf->cert, conf->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_setn(r->headers_out, "Content-Encoding", "application/cms");
-
+
+ apr_table_unset(f->r->headers_out, "Content-Length");
+ f->r->content_type = conf->tp == OF_DER ? "application/cms" : "application/json";
+
+ if (conf->tp == OF_JSON) {
+ apr_bucket_brigade *pass_bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+ if ((rv = apr_brigade_puts(pass_bb, NULL, NULL, "{\"payload\":\"")) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, HANDLER ": write error.");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ rv = ap_pass_brigade(f->next, pass_bb);
+ if (rv != APR_SUCCESS) {
+ apr_brigade_destroy(pass_bb);
+ return rv;
+ }
+ }
f->ctx = state;
+ //force recalc of lenght
};
if (state->in == NULL)
@@ -297,34 +386,63 @@
for (apr_bucket * e = APR_BRIGADE_FIRST(bb); e != APR_BRIGADE_SENTINEL(bb); e = APR_BUCKET_NEXT(e)) {
const char *data;
apr_size_t length = 0;
- apr_status_t rv;
if (!APR_BUCKET_IS_METADATA(e))
if ((rv = apr_bucket_read(e, &data, &length, APR_BLOCK_READ)) != APR_SUCCESS) {
- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, HANDLER ": read error.");
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, HANDLER ": read error.");
return (rv);
};
+
if (state->in == NULL)
continue;
if (state->in && length > 0) {
apr_size_t written = BIO_write(state->in, data, length);
if (length != written) {
- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, HANDLER ": could not outbuff: %s %" APR_SIZE_T_FMT "/%" APR_SIZE_T_FMT,
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, HANDLER ": could not outbuff: %s %" APR_SIZE_T_FMT "/%" APR_SIZE_T_FMT,
ERR_reason_error_string(ERR_get_error()), written, length);
return HTTP_INTERNAL_SERVER_ERROR;
};
+ if (conf->tp == OF_JSON) {
+ apr_bucket_brigade *pass_bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+ if ((rv = _brigade_base64_write(pass_bb, data, length, state->b64, state->mem)) != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, HANDLER ": write error.");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ rv = ap_pass_brigade(f->next, pass_bb);
+ if (rv != APR_SUCCESS) {
+ apr_brigade_destroy(pass_bb);
+ return rv;
+ };
+ };
};
if (APR_BUCKET_IS_EOS(e)) {
- if (1 != CMS_final(state->ci, state->in, NULL, CMS_PARTIAL)) {
- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, HANDLER ": could not finalize struct: %s",
+ apr_bucket_brigade *pass_bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+ if (conf->tp == OF_JSON) {
+ if (((rv = _brigade_base64_final(pass_bb, state->b64, state->mem)) != APR_SUCCESS)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, HANDLER ": write error.");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ if (((rv = apr_brigade_puts(pass_bb, NULL, NULL, "\",\"signature\":\"")) != APR_SUCCESS)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, HANDLER ": write error.");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ BIO_free(state->mem);
+ BIO_free(state->b64);
+ state->mem = BIO_new(BIO_s_mem());
+ state->b64 = BIO_new(BIO_f_base64());
+
+ BIO_push(state->b64, state->mem);
+ BIO_set_flags(state->b64, BIO_FLAGS_BASE64_NO_NL);
+ };
+ if (1 != CMS_final(state->ci, state->in, NULL, CMS_BINARY | CMS_NOSMIMECAP /* state->flags */)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, HANDLER ": could not finalize struct: %s",
ERR_reason_error_string(ERR_get_error()));
return HTTP_INTERNAL_SERVER_ERROR;
};
-
- if (1 != i2d_CMS_bio_stream(state->out, state->ci, state->in, CMS_BINARY)) {
- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, HANDLER ": could not serialize: %s",
+ if (1 != i2d_CMS_bio_stream(state->out, state->ci, state->in, CMS_BINARY | CMS_NOSMIMECAP )) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, HANDLER ": could not serialize: %s",
ERR_reason_error_string(ERR_get_error()));
return HTTP_INTERNAL_SERVER_ERROR;
}
@@ -332,32 +450,45 @@
state->in = NULL;
//ignore anyting after EOS
- apr_bucket_brigade * pass_bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
- length = BIO_get_mem_data(state->out, &data);
-
- if ((rv = apr_brigade_write(pass_bb, NULL, NULL, data, length)) != APR_SUCCESS) {
- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, HANDLER ": write error.");
+ length = BIO_get_mem_data(state->out, &data);
+ if (length <= 0) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, HANDLER ": no CMS data returned");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (conf->tp == OF_DER) {
+ rv = apr_brigade_write(pass_bb, NULL, NULL, data, length);
+ }
+ else {
+ 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, "\"}");
+ };
+
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, HANDLER ": write error.");
return rv;
- };
+ }
APR_BRIGADE_INSERT_TAIL(pass_bb, apr_bucket_eos_create(r->connection->bucket_alloc));
-
- BIO_free(state->out);
- state->out = NULL;
-
rv = ap_pass_brigade(f->next, pass_bb);
if (rv != APR_SUCCESS) {
apr_brigade_destroy(pass_bb);
return rv;
}
+
+ BIO_free(state->out);
+ state->out = NULL;
}
}
return APR_SUCCESS;
}
static int _pre_config(apr_pool_t * pconf, apr_pool_t * plog,
- apr_pool_t * ptemp)
+ apr_pool_t * ptemp)
{
OpenSSL_add_all_algorithms();
ERR_load_crypto_strings();
@@ -370,7 +501,7 @@
static void _register_hooks(apr_pool_t * p)
{
ap_hook_pre_config(_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
- ap_register_output_filter(HANDLER, _out_filter, NULL, AP_FTYPE_PROTOCOL - 1);
+ ap_register_output_filter(HANDLER, _out_filter, NULL, AP_FTYPE_RESOURCE);
}
static const command_rec _cmds[] =
@@ -381,9 +512,12 @@
AP_INIT_TAKE1("CMSSigningKey",
set_cms_sign_key, NULL, RSRC_CONF | ACCESS_CONF,
"Set the CMS signing key. PEM(openssl) format"),
- AP_INIT_TAKE1("CMSAddCerts",
- add_cms_certs, NULL, RSRC_CONF | ACCESS_CONF,
- "Add additional certs (siging cert is always added), PEM format, contain multiple entries, may repeat"),
+ AP_INIT_ITERATE("CMSAddCerts",
+ add_cms_certs, NULL, RSRC_CONF | ACCESS_CONF,
+ "Add additional certs (siging cert is always added), PEM format, contain multiple entries, may repeat"),
+ AP_INIT_TAKE1("CMSSigningOutputFormat",
+ set_cms_output_format, NULL, RSRC_CONF | ACCESS_CONF,
+ "Set CMS output format; DER or JSON (default is DER)"),
{NULL}
};
More information about the rs-commit
mailing list