| /* 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)) { |
| arg += 5; |
| |
| if (!*arg) { |
| return apr_pstrcat(cmd->pool, "No filename specified", NULL); |
| } |
| |
| const char *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 |