[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