/* 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 * * 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. */ /* * mod_crypto.c --- Encrypt / decrypt data in the input and output filter * stacks. */ #include "mod_crypto.h" #include "apr_lib.h" #include "apr_strings.h" #include "apr_crypto.h" #include "apr_base64.h" #include "apr_escape.h" #include "apr_version.h" #if !APR_VERSION_AT_LEAST(2,0,0) #include "apu_version.h" #endif #include "util_filter.h" #include "http_log.h" #include "http_request.h" #include "http_protocol.h" #include "ap_expr.h" #if APR_VERSION_AT_LEAST(2,0,0) || \ (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 6) APR_HOOK_STRUCT(APR_HOOK_LINK(crypto_key) APR_HOOK_LINK(crypto_iv)) APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(ap, CRYPTO, apr_status_t, crypto_key, (request_rec *r, apr_crypto_block_key_type_t * cipher, apr_crypto_block_key_mode_t * mode, int pad, const apr_crypto_key_rec_t ** rec), (r, cipher, mode, pad, rec), DECLINED) APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(ap, CRYPTO, apr_status_t, crypto_iv, (request_rec *r, apr_crypto_block_key_type_t * cipher, const unsigned char **iv), (r, cipher, iv), DECLINED) module AP_MODULE_DECLARE_DATA crypto_module; #define DEFAULT_BUFFER_SIZE 128*1024 #define DEFAULT_CIPHER "aes256" #define DEFAULT_MODE "cbc" #define CRYPTO_KEY "crypto_context" typedef struct pass_conf { const char *scheme; const ap_expr_info_t *expr; unsigned char *raw; apr_size_t size; } pass_conf; /** * Structure to carry the server wide session config. */ typedef struct { const char *library; const char *params; apr_crypto_t **crypto; int library_set; } crypto_conf; typedef struct crypto_dir_conf { apr_off_t size; /* size of the buffer */ int size_set; /* has the size been set */ const char *cipher; const char *mode; int cipher_set; pass_conf *key; int key_set; pass_conf *iv; int iv_set; } crypto_dir_conf; typedef struct crypto_ctx { apr_bucket_brigade *bb; apr_bucket_brigade *tmp; crypto_dir_conf *conf; unsigned char *out; apr_crypto_key_t *key; apr_crypto_block_key_type_t *cipher; apr_crypto_block_key_mode_t *mode; apr_crypto_block_t *block; const unsigned char *iv; apr_off_t remaining; apr_off_t written; apr_size_t osize; int seen_eos:1; int encrypt:1; int clength:1; } crypto_ctx; static const char *parse_pass_conf_binary(cmd_parms *cmd, pass_conf * pass, const char *arg) { apr_status_t rv; char ps = *arg; if ('f' == ps && !strncmp(arg, "file:", 5)) { const char *name; arg += 5; if (!*arg) { return apr_pstrcat(cmd->pool, "No filename specified", NULL); } name = ap_server_root_relative(cmd->temp_pool, arg); if (name) { apr_file_t *file; rv = apr_file_open(&file, name, APR_FOPEN_READ, APR_FPROT_OS_DEFAULT, cmd->temp_pool); if (APR_SUCCESS == rv) { apr_finfo_t finfo; rv = apr_file_info_get(&finfo, APR_FINFO_SIZE, file); if (rv == APR_SUCCESS) { apr_size_t size; pass->scheme = "file"; pass->raw = apr_palloc(cmd->pool, finfo.size); pass->size = finfo.size; apr_crypto_clear(cmd->pool, pass->raw, pass->size); rv = apr_file_read_full(file, pass->raw, pass->size, &size); if (APR_SUCCESS == rv && size != pass->size) { rv = APR_EGENERAL; } } } if (APR_SUCCESS != rv) { char buf[120]; return apr_pstrcat(cmd->pool, "Unable to load from file '", arg, "': ", apr_strerror(rv, buf, sizeof(buf)), NULL); } } else { return apr_pstrcat(cmd->pool, "Unable to locate file from name ", arg, NULL); } } else if ('h' == ps && (!strncmp(arg, "hex:", 4))) { const char *expr_err = NULL; arg += 4; if (!*arg) { return apr_pstrcat(cmd->temp_pool, "Cannot parse expression, it is blank", NULL); } pass->scheme = "hex"; pass->expr = ap_expr_parse_cmd(cmd, arg, AP_EXPR_FLAG_STRING_RESULT, &expr_err, NULL); if (expr_err) { return apr_pstrcat(cmd->temp_pool, "Cannot parse ", pass->scheme, " expression '", arg, "' in: ", expr_err, NULL); } } else if ('b' == ps && !strncmp(arg, "base64:", 7)) { const char *expr_err = NULL; arg += 7; if (!*arg) { return apr_pstrcat(cmd->temp_pool, "Cannot parse expression, it is blank", NULL); } pass->scheme = "base64"; pass->expr = ap_expr_parse_cmd(cmd, arg, AP_EXPR_FLAG_STRING_RESULT, &expr_err, NULL); if (expr_err) { return apr_pstrcat(cmd->temp_pool, "Cannot parse ", pass->scheme, " expression '", arg, "' in: ", expr_err, NULL); } } else if ('d' == ps && !strncmp(arg, "decimal:", 8)) { const char *expr_err = NULL; arg += 8; if (!*arg) { return apr_pstrcat(cmd->temp_pool, "Cannot parse expression, it is blank", NULL); } pass->scheme = "decimal"; pass->expr = ap_expr_parse_cmd(cmd, arg, AP_EXPR_FLAG_STRING_RESULT, &expr_err, NULL); if (expr_err) { return apr_pstrcat(cmd->temp_pool, "Cannot parse ", pass->scheme, " expression '", arg, "' in: ", expr_err, NULL); } } else if ('n' == ps && !strcmp(arg, "none")) { pass->scheme = arg; } else { return apr_pstrcat(cmd->pool, "Scheme must be 'file:', 'hex:', 'base64:', 'decimal:' or 'none': ", arg, NULL); } return NULL; } static apr_status_t exec_pass_conf_binary(request_rec *r, pass_conf * pass, const char *description, apr_size_t size, const unsigned char **k) { if (pass) { if (pass->raw) { *k = pass->raw; if (size != pass->size) { ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, r, APLOGNO(03409) "%s has wrong size (was %" APR_SIZE_T_FMT ", must be %" APR_SIZE_T_FMT ")", description, pass->size, size); return APR_EGENERAL; } return APR_SUCCESS; } else if (pass->expr) { char ps = *pass->scheme; const char *err = NULL; const char *arg = ap_expr_str_exec(r, pass->expr, &err); if (err) { ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, r, APLOGNO(03410) "%s could not be parsed: %s", description, err); return APR_EGENERAL; } /* hex */ if ('h' == ps) { apr_size_t len; unsigned char *b; apr_unescape_hex(NULL, arg, strlen(arg), 1, &len); if (len < size) { b = apr_palloc(r->pool, size); memset(b, 0, size - len); apr_unescape_hex(b + size - len, arg, strlen(arg), 1, &len); } else { b = apr_palloc(r->pool, len); apr_unescape_hex(b, arg, strlen(arg), 1, NULL); b += len - size; } *k = b; } /* base64 */ else if ('b' == ps) { apr_size_t len; unsigned char *b; len = apr_base64_decode_len(arg); if (len < size) { b = apr_palloc(r->pool, size); memset(b, 0, size - len); apr_base64_decode_binary(b + size - len, arg); } else { b = apr_palloc(r->pool, len); apr_base64_decode_binary(b, arg); b += len - size; } *k = b; } /* decimal */ else if ('d' == ps) { apr_size_t len; unsigned char *b; char n[8]; apr_uint64_t t; int i; t = (apr_uint64_t) apr_atoi64(arg); for (i = 7; i >= 0; i--) { n[i] = t & 0xFF; t = t >> 8; } len = sizeof(n); if (len < size) { b = apr_palloc(r->pool, size); memset(b, 0, size - len); memcpy(b + size - len, n, len); } else { b = apr_palloc(r->pool, len); memcpy(b, n, len); b += len - size; } *k = b; } } } return DECLINED; } static apr_status_t init_cipher(request_rec *r, apr_crypto_block_key_type_t ** cipher, apr_crypto_block_key_mode_t ** mode) { apr_status_t rv; apr_hash_t *ciphers; apr_hash_t *modes; crypto_conf *conf = ap_get_module_config(r->server->module_config, &crypto_module); crypto_dir_conf *dconf = ap_get_module_config(r->per_dir_config, &crypto_module); if (cipher) { rv = apr_crypto_get_block_key_types(&ciphers, *conf->crypto); if (APR_SUCCESS != rv) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(03411) "no ciphers returned by APR"); return rv; } *cipher = apr_hash_get(ciphers, dconf->cipher, APR_HASH_KEY_STRING); if (!*cipher) { apr_hash_index_t *hi; const void *key; apr_ssize_t klen; int sum = 0; int offset = 0; char *options = NULL; for (hi = apr_hash_first(r->pool, ciphers); hi; hi = apr_hash_next(hi)) { apr_hash_this(hi, NULL, &klen, NULL); sum += klen + 2; } for (hi = apr_hash_first(r->pool, ciphers); hi; hi = apr_hash_next(hi)) { apr_hash_this(hi, &key, &klen, NULL); if (!options) { options = apr_palloc(r->pool, sum + 1); } else { options[offset++] = ','; options[offset++] = ' '; } strncpy(options + offset, key, klen); offset += klen; } options[offset] = 0; ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(03428) "cipher '%s' not recognised by crypto driver. " "Options: %s", dconf->cipher, options); return rv; } } if (mode) { rv = apr_crypto_get_block_key_modes(&modes, *conf->crypto); if (APR_SUCCESS != rv) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(03412) "no cipher modes returned by APR"); return rv; } *mode = apr_hash_get(modes, dconf->mode, APR_HASH_KEY_STRING); if (!*mode) { apr_hash_index_t *hi; const void *key; apr_ssize_t klen; int sum = 0; int offset = 0; char *options = NULL; for (hi = apr_hash_first(r->pool, modes); hi; hi = apr_hash_next(hi)) { apr_hash_this(hi, NULL, &klen, NULL); sum += klen + 2; } for (hi = apr_hash_first(r->pool, modes); hi; hi = apr_hash_next(hi)) { apr_hash_this(hi, &key, &klen, NULL); if (!options) { options = apr_palloc(r->pool, sum + 1); } else { options[offset++] = ','; options[offset++] = ' '; } strncpy(options + offset, key, klen); offset += klen; } options[offset] = 0; ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(03429) "cipher mode '%s' not recognised by crypto driver. " "Options: %s", dconf->mode, options); return rv; } } return APR_SUCCESS; } static apr_status_t init_crypt(ap_filter_t * f) { apr_status_t rv; crypto_ctx *ctx = f->ctx; const apr_crypto_key_rec_t *rec; crypto_conf *conf = ap_get_module_config(f->r->server->module_config, &crypto_module); crypto_dir_conf *dconf = ap_get_module_config(f->r->per_dir_config, &crypto_module); /* sanity check - has crypto been switched on? */ if (!conf->crypto || !*conf->crypto) { ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, f->r, APLOGNO(03430) "crypto driver has not been enabled for this server"); return APR_EGENERAL; } /* initial setup of the context */ ctx->bb = apr_brigade_create(f->r->pool, f->c->bucket_alloc); ctx->conf = dconf; ctx->remaining = ctx->conf->size; ctx->written = 0; ctx->osize = ctx->conf->size; /* fetch the cipher for this location */ rv = init_cipher(f->r, &ctx->cipher, &ctx->mode); if (APR_SUCCESS != rv) { return rv; } /* sanity check - buffer size multiple of block size? */ if (ctx->conf->size % ctx->cipher->blocksize) { ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, f->r, APLOGNO(03413) "Buffer size %" APR_OFF_T_FMT " is not a multiple of the block size %d of cipher '%s'", ctx->conf->size, ctx->cipher->blocksize, dconf->cipher); return APR_EGENERAL; } /* fetch the key we'll be using for decryption */ rv = ap_run_crypto_key(f->r, ctx->cipher, ctx->mode, 1, &rec); if (DECLINED == rv) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, f->r, APLOGNO(03414) "no key specified for this URL"); return APR_ENOKEY; } if (APR_SUCCESS != rv || !rec) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r, APLOGNO(03415) "key could not be retrieved"); return APR_ENOKEY; } if (rec->ktype != APR_CRYPTO_KTYPE_SECRET) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r, APLOGNO(03416) "key is not a symmetrical key"); return APR_ENOKEY; } /* attempt to import the key */ rv = apr_crypto_key(&ctx->key, rec, *conf->crypto, f->r->pool); if (APR_STATUS_IS_ENOKEY(rv)) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r, APLOGNO(03417) "key could not be loaded"); } if (APR_STATUS_IS_EPADDING(rv)) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r, APLOGNO(03418) "padding is not supported for cipher"); } if (APR_STATUS_IS_EKEYTYPE(rv)) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r, APLOGNO(03419) "the key type is not known"); } if (APR_SUCCESS != rv) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r, APLOGNO(03420) "encryption could not be configured."); return rv; } /* fetch the optional iv */ rv = ap_run_crypto_iv(f->r, ctx->cipher, &ctx->iv); if (DECLINED != rv && APR_SUCCESS != rv) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r, APLOGNO(03431) "initialisation vector could not be retrieved"); return rv; } return APR_SUCCESS; } static int init_encrypt(ap_filter_t * f) { apr_status_t rv; crypto_ctx *ctx; ctx = f->ctx = apr_pcalloc(f->r->pool, sizeof(*ctx)); ctx->encrypt = 1; rv = init_crypt(f); if (APR_SUCCESS != rv) { return HTTP_INTERNAL_SERVER_ERROR; } return OK; } static int init_decrypt(ap_filter_t * f) { apr_status_t rv; crypto_ctx *ctx; ctx = f->ctx = apr_pcalloc(f->r->pool, sizeof(*ctx)); ctx->encrypt = 0; rv = init_crypt(f); if (APR_SUCCESS != rv) { return HTTP_INTERNAL_SERVER_ERROR; } return OK; } /** * Run the crypto algorithm, write to ctx->out */ static apr_status_t do_crypto(ap_filter_t * f, unsigned char *in, apr_off_t size, int finish) { apr_status_t rv; crypto_ctx *ctx = f->ctx; apr_off_t extra = 0; apr_size_t blockSize = 0; int need_iv = (ctx->iv == NULL); unsigned char *out; apr_size_t written; /* encrypt the given buffer */ if (ctx->encrypt) { if (!ctx->block) { rv = apr_crypto_block_encrypt_init(&ctx->block, &ctx->iv, ctx->key, &blockSize, f->r->pool); if (APR_SUCCESS != rv) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r, APLOGNO(03421) "could not initialise encryption"); return rv; } } if (!ctx->out) { if (need_iv && ctx->iv) { ctx->osize += blockSize; } out = ctx->out = apr_palloc(f->r->pool, ctx->osize + ctx->cipher->blocksize); apr_crypto_clear(f->r->pool, ctx->out, ctx->osize + ctx->cipher->blocksize); /* no precomputed iv? write the generated iv as the first block of the stream */ if (need_iv && ctx->iv) { memcpy(out, ctx->iv, blockSize); ctx->remaining += blockSize; out += blockSize; extra = blockSize; } } else { out = ctx->out + (ctx->osize - ctx->remaining); } if (!finish) { rv = apr_crypto_block_encrypt(&out, &written, in, size, ctx->block); if (APR_SUCCESS != rv) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r, APLOGNO(03422) "crypto: attempt to encrypt failed"); return rv; } } else { rv = apr_crypto_block_encrypt_finish(out, &written, ctx->block); if (APR_SUCCESS != rv) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r, APLOGNO(03432) "crypto: attempt to finish encrypt failed"); return rv; } } } /* decrypt the given buffer */ else { if (!ctx->out) { out = ctx->out = apr_palloc(f->r->pool, ctx->osize + ctx->cipher->blocksize); apr_crypto_clear(f->r->pool, ctx->out, ctx->osize + ctx->cipher->blocksize); } else { out = ctx->out + (ctx->osize - ctx->remaining); } /* no precomputed iv? assume the first block in the stream is the iv */ if (need_iv) { apr_off_t isize = ctx->cipher->blocksize - (ctx->osize - ctx->remaining); if (size < isize) { memcpy(out, in, size); ctx->remaining -= size; return APR_SUCCESS; } else { memcpy(out, in, isize); ctx->remaining -= isize; out += isize; ctx->iv = ctx->out; } } if (!ctx->block) { rv = apr_crypto_block_decrypt_init(&ctx->block, &blockSize, ctx->iv, ctx->key, f->r->pool); if (APR_SUCCESS != rv) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r, APLOGNO(03423) "could not initialise decryption"); return rv; } } if (!finish) { rv = apr_crypto_block_decrypt(&out, &written, in, size, ctx->block); if (APR_SUCCESS != rv) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r, APLOGNO(03433) "crypto: attempt to decrypt failed (key/iv incorrect?)"); return rv; } } else { rv = apr_crypto_block_decrypt_finish(out, &written, ctx->block); if (APR_SUCCESS != rv) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, f->r, APLOGNO(03434) "crypto: attempt to finish decrypt failed (key/iv incorrect?)"); return rv; } } } ctx->remaining -= written; ctx->written += written; ctx->written += extra; return rv; } /** * Encrypt/decrypt buckets being written to the output filter stack. */ static apr_status_t crypto_out_filter(ap_filter_t * f, apr_bucket_brigade * bb) { apr_bucket *e, *after; crypto_ctx *ctx = f->ctx; apr_status_t rv = APR_SUCCESS; /* Do nothing if asked to filter nothing. */ if (APR_BRIGADE_EMPTY(bb)) { return ap_pass_brigade(f->next, bb); } /* clear the content length */ if (!ctx->clength) { ctx->clength = 1; apr_table_unset(f->r->headers_out, "Content-Length"); } /* make sure we fit in the buffer snugly */ if (APR_BRIGADE_EMPTY(ctx->bb)) { apr_brigade_partition(bb, ctx->remaining, &after); } while (APR_SUCCESS == rv && !APR_BRIGADE_EMPTY(bb)) { const char *data; apr_size_t size; e = APR_BRIGADE_FIRST(bb); /* EOS means we are done. */ if (APR_BUCKET_IS_EOS(e)) { /* handle any leftovers */ do_crypto(f, NULL, 0, 1); apr_brigade_write(ctx->bb, NULL, NULL, (const char *) ctx->out, ctx->conf->size - ctx->remaining); ctx->remaining = ctx->osize; ctx->written = 0; apr_brigade_partition(bb, ctx->remaining, &after); /* pass the EOS across */ APR_BUCKET_REMOVE(e); APR_BRIGADE_INSERT_TAIL(ctx->bb, e); /* pass what we have down the chain */ rv = ap_pass_brigade(f->next, ctx->bb); ap_remove_output_filter(f); continue; } /* handle flush */ if (APR_BUCKET_IS_FLUSH(e)) { /* we cannot change the laws of physics: crypto can only happen * on a block boundary. As a result, just pass the flush bucket * through as is, we'll send the rest of the block when it * arrives in full. */ /* pass the flush bucket across */ APR_BUCKET_REMOVE(e); APR_BRIGADE_INSERT_TAIL(ctx->bb, e); /* pass what we have down the chain */ rv = ap_pass_brigade(f->next, ctx->bb); continue; } /* metadata buckets are preserved as is */ if (APR_BUCKET_IS_METADATA(e)) { /* * Remove meta data bucket from old brigade and insert into the * new. */ APR_BUCKET_REMOVE(e); APR_BRIGADE_INSERT_TAIL(ctx->bb, e); continue; } if (APR_SUCCESS == (rv = apr_bucket_read(e, &data, &size, APR_BLOCK_READ))) { do_crypto(f, (unsigned char *) data, size, 0); apr_bucket_delete(e); if (!ctx->remaining) { apr_brigade_write(ctx->bb, NULL, NULL, (const char *) ctx->out, ctx->written); ctx->remaining = ctx->osize; ctx->written = 0; apr_brigade_partition(bb, ctx->remaining, &after); rv = ap_pass_brigade(f->next, ctx->bb); } } } return rv; } /** * Decrypt/encrypt buckets being read from the input filter stack. */ static apr_status_t crypto_in_filter(ap_filter_t * f, apr_bucket_brigade * bb, ap_input_mode_t mode, apr_read_type_e block, apr_off_t readbytes) { apr_bucket *e, *after; apr_status_t rv = APR_SUCCESS; crypto_ctx *ctx = f->ctx; if (!ctx->tmp) { ctx->tmp = apr_brigade_create(f->r->pool, f->c->bucket_alloc); } /* just get out of the way of things we don't want. */ if (mode != AP_MODE_READBYTES) { return ap_get_brigade(f->next, bb, mode, block, readbytes); } /* if our buffer is empty, read off the network until the buffer is full */ if (APR_BRIGADE_EMPTY(ctx->bb)) { ctx->remaining = ctx->osize; ctx->written = 0; while (!ctx->seen_eos && ctx->remaining > 0) { const char *data; apr_size_t size = 0; if (APR_BRIGADE_EMPTY(ctx->tmp)) { rv = ap_get_brigade(f->next, ctx->tmp, mode, block, ctx->remaining); } /* if an error was received, bail out now. If the error is * EAGAIN and we have not yet seen an EOS, we will definitely * be called again, at which point we will send our buffered * data. Instead of sending EAGAIN, some filters return an * empty brigade instead when data is not yet available. In * this case, we drop through and pass buffered data, if any. */ if (APR_STATUS_IS_EAGAIN(rv) || (rv == APR_SUCCESS && block == APR_NONBLOCK_READ && APR_BRIGADE_EMPTY(ctx->tmp))) { if (APR_BRIGADE_EMPTY(ctx->bb)) { return rv; } break; } if (APR_SUCCESS != rv) { return rv; } while (!APR_BRIGADE_EMPTY(ctx->tmp)) { e = APR_BRIGADE_FIRST(ctx->tmp); /* if we see an EOS, we are done */ if (APR_BUCKET_IS_EOS(e)) { /* handle any leftovers */ do_crypto(f, NULL, 0, 1); apr_brigade_write(ctx->bb, NULL, NULL, (const char *) ctx->out, ctx->written); APR_BUCKET_REMOVE(e); APR_BRIGADE_INSERT_TAIL(ctx->bb, e); ctx->seen_eos = 1; break; } /* flush buckets clear the buffer */ if (APR_BUCKET_IS_FLUSH(e)) { APR_BUCKET_REMOVE(e); APR_BRIGADE_INSERT_TAIL(ctx->bb, e); break; } /* pass metadata buckets through */ if (APR_BUCKET_IS_METADATA(e)) { APR_BUCKET_REMOVE(e); APR_BRIGADE_INSERT_TAIL(ctx->bb, e); continue; } /* read the bucket in, pack it into the buffer */ rv = apr_bucket_read(e, &data, &size, block); if (APR_STATUS_IS_EAGAIN(rv)) { if (APR_BRIGADE_EMPTY(ctx->bb)) { return rv; } break; } if (APR_SUCCESS != rv) { return rv; } do_crypto(f, (unsigned char *) data, size, 0); if (!ctx->remaining || APR_STATUS_IS_EAGAIN(rv)) { apr_brigade_write(ctx->bb, NULL, NULL, (const char *) ctx->out, ctx->written); } apr_bucket_delete(e); } } } /* give the caller the data they asked for from the buffer */ apr_brigade_partition(ctx->bb, readbytes, &after); e = APR_BRIGADE_FIRST(ctx->bb); while (e != after) { if (APR_BUCKET_IS_EOS(e)) { /* last bucket read, step out of the way */ ap_remove_input_filter(f); } APR_BUCKET_REMOVE(e); APR_BRIGADE_INSERT_TAIL(bb, e); e = APR_BRIGADE_FIRST(ctx->bb); } /* clear the content length */ if (!ctx->clength) { ctx->clength = 1; apr_table_unset(f->r->headers_in, "Content-Length"); } return APR_SUCCESS; } static int crypto_handler(request_rec *r) { crypto_conf *conf; crypto_dir_conf *dconf; apr_status_t rv; if (*r->handler != 'c' || strcmp(r->handler, "crypto-key")) { return DECLINED; } conf = ap_get_module_config(r->server->module_config, &crypto_module); dconf = ap_get_module_config(r->per_dir_config, &crypto_module); /* sanity check - has crypto been switched on? */ if (!conf->crypto || !*conf->crypto) { ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, r, APLOGNO(03435) "crypto driver has not been enabled for this server"); return APR_EGENERAL; } if (dconf->key_set) { const apr_crypto_key_rec_t *rec; apr_crypto_block_key_type_t *cipher; apr_crypto_block_key_mode_t *mode; /* fetch the cipher for this location */ rv = init_cipher(r, &cipher, &mode); if (APR_SUCCESS != rv) { return HTTP_INTERNAL_SERVER_ERROR; } /* fetch the key we'll be using for encryption / decryption */ rv = ap_run_crypto_key(r, cipher, mode, 1, &rec); if (DECLINED == rv) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(03424) "no key specified for this URL"); return HTTP_INTERNAL_SERVER_ERROR; } if (APR_SUCCESS != rv || !rec) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(03425) "key could not be retrieved"); return HTTP_INTERNAL_SERVER_ERROR; } if (rec->ktype != APR_CRYPTO_KTYPE_SECRET) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(03426) "key is not a symmetrical key"); return HTTP_INTERNAL_SERVER_ERROR; } ap_set_content_type(r, "application/octet-stream"); ap_set_content_length(r, rec->k.secret.secretLen); ap_rwrite(rec->k.secret.secret, rec->k.secret.secretLen, r); return OK; } else { return HTTP_NOT_FOUND; } } static void *create_crypto_config(apr_pool_t * p, server_rec *s) { crypto_conf *new = (crypto_conf *) apr_pcalloc(p, sizeof(crypto_conf)); /* if no library has been configured, set the recommended library * as a sensible default. */ #ifdef APU_CRYPTO_RECOMMENDED_DRIVER new->library = APU_CRYPTO_RECOMMENDED_DRIVER; #endif new->crypto = apr_pcalloc(p, sizeof(apr_crypto_t *)); return (void *) new; } static void *merge_crypto_config(apr_pool_t * p, void *basev, void *addv) { crypto_conf *new = (crypto_conf *) apr_pcalloc(p, sizeof(crypto_conf)); crypto_conf *add = (crypto_conf *) addv; crypto_conf *base = (crypto_conf *) basev; new->library = (add->library_set == 0) ? base->library : add->library; new->params = (add->library_set == 0) ? base->params : add->params; new->library_set = add->library_set || base->library_set; new->crypto = base->crypto; return (void *) new; } static void *create_crypto_dir_config(apr_pool_t * p, char *dummy) { crypto_dir_conf *new = (crypto_dir_conf *) apr_pcalloc(p, sizeof(crypto_dir_conf)); new->size = DEFAULT_BUFFER_SIZE; /* default size */ new->cipher = DEFAULT_CIPHER; new->mode = DEFAULT_MODE; return (void *) new; } static void *merge_crypto_dir_config(apr_pool_t * p, void *basev, void *addv) { crypto_dir_conf *new = (crypto_dir_conf *) apr_pcalloc(p, sizeof(crypto_dir_conf)); crypto_dir_conf *add = (crypto_dir_conf *) addv; crypto_dir_conf *base = (crypto_dir_conf *) basev; new->size = (add->size_set == 0) ? base->size : add->size; new->size_set = add->size_set || base->size_set; new->cipher = (add->cipher_set == 0) ? base->cipher : add->cipher; new->mode = (add->cipher_set == 0) ? base->mode : add->mode; new->cipher_set = add->cipher_set || base->cipher_set; new->key = (add->key_set == 0) ? base->key : add->key; new->key_set = add->key_set || base->key_set; new->iv = (add->iv_set == 0) ? base->iv : add->iv; new->iv_set = add->iv_set || base->iv_set; return new; } static const char *set_crypto_size(cmd_parms *cmd, void *dconf, const char *arg) { crypto_dir_conf *conf = dconf; if (APR_SUCCESS != apr_strtoff(&(conf->size), arg, NULL, 10) || conf->size <= 0) { return "CryptoSize must be a size in bytes, and greater than zero"; } conf->size_set = 1; return NULL; } static const char *set_crypto_driver(cmd_parms *cmd, void *config, const char *arg) { crypto_conf *conf = (crypto_conf *) ap_get_module_config(cmd->server->module_config, &crypto_module); const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY); if (err != NULL) { return err; } conf->library = ap_getword_conf(cmd->pool, &arg); conf->params = arg; conf->crypto = apr_pcalloc(cmd->pool, sizeof(apr_crypto_t *)); conf->library_set = 1; return NULL; } static const char *set_crypto_cipher(cmd_parms *cmd, void *config, const char *cipher, const char *mode) { crypto_dir_conf *dconf = (crypto_dir_conf *) config; dconf->cipher = cipher; dconf->mode = mode ? mode : DEFAULT_MODE; dconf->cipher_set = 1; return NULL; } static const char *set_crypto_key(cmd_parms *cmd, void *config, const char *arg) { crypto_dir_conf *dconf = (crypto_dir_conf *) config; pass_conf *key = dconf->key = apr_pcalloc(cmd->pool, sizeof(pass_conf)); dconf->key_set = 1; return parse_pass_conf_binary(cmd, key, arg); } static const char *set_crypto_iv(cmd_parms *cmd, void *config, const char *arg) { crypto_dir_conf *dconf = (crypto_dir_conf *) config; pass_conf *iv = dconf->iv = apr_pcalloc(cmd->pool, sizeof(pass_conf)); dconf->iv_set = 1; return parse_pass_conf_binary(cmd, iv, arg); } static const command_rec crypto_cmds[] = { AP_INIT_TAKE1("CryptoSize", set_crypto_size, NULL, ACCESS_CONF, "Maximum size of the buffer used by the crypto filters"), AP_INIT_RAW_ARGS("CryptoDriver", set_crypto_driver, NULL, RSRC_CONF, "The underlying crypto library driver to use"), AP_INIT_TAKE12("CryptoCipher", set_crypto_cipher, NULL, RSRC_CONF | OR_AUTHCFG, "The underlying crypto cipher and mode to use. If unspecified, the mode defaults to 'cbc'"), AP_INIT_TAKE1("CryptoKey", set_crypto_key, NULL, RSRC_CONF | OR_AUTHCFG, "The crypto key scheme and value to use. Scheme is one of 'none', 'file:', 'hex:', 'base64:' or 'decimal:'"), AP_INIT_TAKE1("CryptoIV", set_crypto_iv, NULL, RSRC_CONF | OR_AUTHCFG, "The crypto IV scheme and value to use. Scheme is one of 'none', 'file:', 'hex:', 'base64:' or 'decimal:'"), {NULL} }; /** * Initialise the SSL in the post_config hook. */ static int crypto_init(apr_pool_t * p, apr_pool_t * plog, apr_pool_t * ptemp, server_rec *s) { const apr_crypto_driver_t *driver = NULL; while (s) { crypto_conf *conf = ap_get_module_config(s->module_config, &crypto_module); if (conf->library_set && !*conf->crypto) { const apu_err_t *err = NULL; apr_status_t rv; rv = apr_crypto_init(p); if (APR_SUCCESS != rv) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(03427) "APR crypto could not be initialised"); return rv; } rv = apr_crypto_get_driver(&driver, conf->library, conf->params, &err, p); if (APR_EREINIT == rv) { ap_log_error(APLOG_MARK, APLOG_WARNING, rv, s, APLOGNO(03436) "warning: crypto for '%s' was already initialised, " "using existing configuration", conf->library); rv = APR_SUCCESS; } if (APR_SUCCESS != rv && err) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(03437) "The crypto library '%s' could not be loaded: %s (%s: %d)", conf->library, err->msg, err->reason, err->rc); return rv; } if (APR_ENOTIMPL == rv) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(03438) "The crypto library '%s' could not be found", conf->library); return rv; } if (APR_SUCCESS != rv || !driver) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(03439) "The crypto library '%s' could not be loaded", conf->library); return rv; } rv = apr_crypto_make(conf->crypto, driver, conf->params, p); if (APR_SUCCESS != rv) { ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(03440) "The crypto library '%s' could not be initialised", conf->library); return rv; } ap_log_error(APLOG_MARK, APLOG_INFO, rv, s, APLOGNO(03441) "The crypto library '%s' was loaded successfully", conf->library); } s = s->next; } return OK; } static apr_status_t crypto_key(request_rec *r, apr_crypto_block_key_type_t * cipher, apr_crypto_block_key_mode_t * mode, int pad, const apr_crypto_key_rec_t ** recptr) { apr_crypto_key_rec_t *rec; crypto_dir_conf *conf = ap_get_module_config(r->per_dir_config, &crypto_module); pass_conf *key = conf->key; *recptr = rec = apr_palloc(r->pool, sizeof(apr_crypto_key_rec_t)); rec->ktype = APR_CRYPTO_KTYPE_SECRET; rec->type = cipher->type; rec->mode = mode->mode; rec->pad = pad; rec->k.secret.secretLen = cipher->keysize; return exec_pass_conf_binary(r, key, "key", cipher->keysize, &(rec->k.secret.secret)); } static apr_status_t crypto_iv(request_rec *r, apr_crypto_block_key_type_t * cipher, const unsigned char **v) { crypto_dir_conf *conf = ap_get_module_config(r->per_dir_config, &crypto_module); pass_conf *iv = conf->iv; return exec_pass_conf_binary(r, iv, "iv", cipher->ivsize, v); } static void register_hooks(apr_pool_t * p) { ap_hook_crypto_key(crypto_key, NULL, NULL, APR_HOOK_REALLY_LAST); ap_hook_crypto_iv(crypto_iv, NULL, NULL, APR_HOOK_REALLY_LAST); ap_hook_post_config(crypto_init, NULL, NULL, APR_HOOK_LAST); ap_hook_handler(crypto_handler, NULL, NULL, APR_HOOK_MIDDLE); ap_register_output_filter("ENCRYPT", crypto_out_filter, init_encrypt, AP_FTYPE_RESOURCE); ap_register_input_filter("ENCRYPT", crypto_in_filter, init_encrypt, AP_FTYPE_RESOURCE); ap_register_output_filter("DECRYPT", crypto_out_filter, init_decrypt, AP_FTYPE_RESOURCE); ap_register_input_filter("DECRYPT", crypto_in_filter, init_decrypt, AP_FTYPE_RESOURCE); } AP_DECLARE_MODULE(crypto) = { STANDARD20_MODULE_STUFF, create_crypto_dir_config, /* create per-directory config structure */ merge_crypto_dir_config, /* merge per-directory config structures */ create_crypto_config, /* create per-server config structure */ merge_crypto_config, /* merge per-server config structures */ crypto_cmds, /* command apr_table_t */ register_hooks /* register hooks */ }; #else #error This module requires at least v1.6.0 of apr-util. #endif