1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements. See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 #include "mod_session.h"
18 #include "apu_version.h"
19 #include "apr_base64.h" /* for apr_base64_decode et al */
21 #include "apr_strings.h"
23 #include "http_core.h"
25 #if APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION < 4
27 #error session_crypto_module requires APU v1.4.0 or later
29 #elif APU_HAVE_CRYPTO == 0
31 #error Crypto support must be enabled in APR
35 #if APR_MAJOR_VERSION < 2
36 #define CRYPTO_VERSION 104
38 #define CRYPTO_VERSION 200
41 #include "apr_crypto.h" /* for apr_*_crypt et al */
43 #define LOG_PREFIX "mod_session_crypto: "
44 #define DRIVER_KEY "session_crypto_driver"
46 module AP_MODULE_DECLARE_DATA session_crypto_module;
49 * Structure to carry the per-dir session config.
52 const char *passphrase;
53 apr_array_header_t *params;
55 apr_crypto_block_key_type_e cipher;
57 }session_crypto_dir_conf;
60 * Structure to carry the server wide session config.
64 apr_array_header_t *params;
70 AP_DECLARE(int) ap_session_crypto_encode(request_rec * r, session_rec * z);
71 AP_DECLARE(int) ap_session_crypto_decode(request_rec * r, session_rec * z);
72 AP_DECLARE(int) ap_session_crypto_init(apr_pool_t *p, apr_pool_t *plog,
73 apr_pool_t *ptemp, server_rec *s);
76 * Initialise the encryption as per the current config.
78 * Returns APR_SUCCESS if successful.
80 static apr_status_t crypt_init(request_rec * r,
81 const apr_crypto_driver_t *driver,
82 apr_crypto_t **f, apr_crypto_key_t **key,
83 apr_uuid_t *salt, apr_size_t *ivSize,
84 session_crypto_dir_conf * dconf)
89 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, LOG_PREFIX
90 "encryption driver not configured, "
91 "no SessionCryptoDriver set");
95 if (!dconf->passphrase_set) {
96 ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, LOG_PREFIX
97 "encryption not configured, "
103 res = apr_crypto_make(driver, r->pool, dconf->params, f);
104 if (APR_ENOTIMPL == res) {
105 ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, LOG_PREFIX
106 "generic symmetrical encryption is not supported by this "
107 "version of APR. session encryption not possible");
110 if (APR_SUCCESS == res) {
111 #if CRYPTO_VERSION < 200
112 res = apr_crypto_passphrase(driver, r->pool, *f, dconf->passphrase,
114 res = apr_crypto_passphrase(r->pool, *f, dconf->passphrase,
116 strlen(dconf->passphrase),
117 (unsigned char *) salt, salt ? sizeof(apr_uuid_t) : 0,
118 dconf->cipher, APR_MODE_CBC, 1, 4096, key, ivSize);
121 if (APR_STATUS_IS_ENOKEY(res)) {
122 ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, LOG_PREFIX
123 "the passphrase '%s' was empty", dconf->passphrase);
125 if (APR_STATUS_IS_EPADDING(res)) {
126 ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, LOG_PREFIX
127 "padding is not supported for cipher");
129 if (APR_STATUS_IS_EKEYTYPE(res)) {
130 ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, LOG_PREFIX
131 "the key type is not known");
133 if (APR_SUCCESS != res) {
134 ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, LOG_PREFIX
135 "encryption could not be configured. Please check the "
136 "certificates and/or passphrase as appropriate");
144 * Encrypt the string given as per the current config.
146 * Returns APR_SUCCESS if successful.
148 static apr_status_t encrypt_string(request_rec * r,
149 const apr_crypto_driver_t *driver,
150 session_crypto_dir_conf *dconf,
151 const char *in, char **out)
154 apr_crypto_t *f = NULL;
155 apr_crypto_key_t *key = NULL;
156 apr_size_t ivSize = 0;
157 apr_crypto_block_t *block = NULL;
158 unsigned char *encrypt = NULL;
159 unsigned char *combined = NULL;
160 apr_size_t encryptlen, tlen;
162 apr_size_t blockSize = 0;
163 const unsigned char *iv = NULL;
166 /* by default, return an empty string */
169 /* don't attempt to encrypt an empty string, trying to do so causes a segfault */
174 /* use a uuid as a salt value, and prepend it to our result */
176 res = crypt_init(r, driver, &f, &key, &salt, &ivSize, dconf);
177 if (res != APR_SUCCESS) {
181 #if CRYPTO_VERSION < 200
182 res = apr_crypto_block_encrypt_init(driver, r->pool, f, key, &iv, &block,
184 res = apr_crypto_block_encrypt_init(r->pool, f, key, &iv, &block,
187 if (APR_SUCCESS != res) {
188 ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, LOG_PREFIX
189 "apr_crypto_block_encrypt_init failed");
193 /* encrypt the given string */
194 #if CRYPTO_VERSION < 200
195 res = apr_crypto_block_encrypt(driver, block, &encrypt,
197 res = apr_crypto_block_encrypt(f, block, &encrypt,
199 &encryptlen, (unsigned char *)in, strlen(in));
200 if (APR_SUCCESS != res) {
201 ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, LOG_PREFIX
202 "apr_crypto_block_encrypt failed");
205 #if CRYPTO_VERSION < 200
206 res = apr_crypto_block_encrypt_finish(driver, block, encrypt + encryptlen,
208 res = apr_crypto_block_encrypt_finish(f, block, encrypt + encryptlen,
211 if (APR_SUCCESS != res) {
212 ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, LOG_PREFIX
213 "apr_crypto_block_encrypt_finish failed");
218 /* prepend the salt and the iv to the result */
219 combined = apr_palloc(r->pool, ivSize + encryptlen + sizeof(apr_uuid_t));
220 memcpy(combined, &salt, sizeof(apr_uuid_t));
221 memcpy(combined + sizeof(apr_uuid_t), iv, ivSize);
222 memcpy(combined + sizeof(apr_uuid_t) + ivSize, encrypt, encryptlen);
224 /* base64 encode the result */
225 base64 = apr_palloc(r->pool, apr_base64_encode_len(ivSize + encryptlen +
226 sizeof(apr_uuid_t) + 1)
228 apr_base64_encode(base64, (const char *) combined,
229 ivSize + encryptlen + sizeof(apr_uuid_t));
237 * Decrypt the string given as per the current config.
239 * Returns APR_SUCCESS if successful.
241 static apr_status_t decrypt_string(request_rec * r,
242 const apr_crypto_driver_t *driver,
243 session_crypto_dir_conf *dconf,
244 const char *in, char **out)
247 apr_crypto_t *f = NULL;
248 apr_crypto_key_t *key = NULL;
249 apr_size_t ivSize = 0;
250 apr_crypto_block_t *block = NULL;
251 unsigned char *decrypted = NULL;
252 apr_size_t decryptedlen, tlen;
253 apr_size_t decodedlen;
255 apr_size_t blockSize = 0;
257 /* strip base64 from the string */
258 decoded = apr_palloc(r->pool, apr_base64_decode_len(in));
259 decodedlen = apr_base64_decode(decoded, in);
260 decoded[decodedlen] = '\0';
262 res = crypt_init(r, driver, &f, &key, (apr_uuid_t *)decoded, &ivSize, dconf);
263 if (res != APR_SUCCESS) {
267 /* sanity check - decoded too short? */
268 if (decodedlen < (sizeof(apr_uuid_t) + ivSize)) {
269 ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_SUCCESS, r, LOG_PREFIX
270 "too short to decrypt, skipping");
274 /* bypass the salt at the start of the decoded block */
275 decoded += sizeof(apr_uuid_t);
276 decodedlen -= sizeof(apr_uuid_t);
278 #if CRYPTO_VERSION < 200
279 res = apr_crypto_block_decrypt_init(driver, r->pool, f, key, (unsigned char *)decoded, &block,
281 res = apr_crypto_block_decrypt_init(r->pool, f, key, (unsigned char *)decoded, &block,
284 if (APR_SUCCESS != res) {
285 ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, LOG_PREFIX
286 "apr_crypto_block_decrypt_init failed");
290 /* bypass the iv at the start of the decoded block */
292 decodedlen -= ivSize;
294 /* decrypt the given string */
295 #if CRYPTO_VERSION < 200
296 res = apr_crypto_block_decrypt(driver, block, &decrypted,
298 res = apr_crypto_block_decrypt(f, block, &decrypted,
300 &decryptedlen, (unsigned char *)decoded, decodedlen);
302 ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, LOG_PREFIX
303 "apr_crypto_block_decrypt failed");
306 *out = (char *) decrypted;
308 #if CRYPTO_VERSION < 200
309 res = apr_crypto_block_decrypt_finish(driver, block, decrypted + decryptedlen,
311 res = apr_crypto_block_decrypt_finish(f, block, decrypted + decryptedlen,
314 if (APR_SUCCESS != res) {
315 ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, LOG_PREFIX
316 "apr_crypto_block_decrypt_finish failed");
319 decryptedlen += tlen;
320 decrypted[decryptedlen] = 0;
327 * Crypto encoding for the session.
329 * @param r The request pointer.
330 * @param z A pointer to where the session will be written.
332 AP_DECLARE(int) ap_session_crypto_encode(request_rec * r, session_rec * z)
335 char *encoded = NULL;
337 const apr_crypto_driver_t *driver = NULL;
338 session_crypto_dir_conf *dconf = ap_get_module_config(r->per_dir_config,
339 &session_crypto_module);
341 if (dconf->passphrase_set && z->encoded && *z->encoded) {
342 apr_pool_userdata_get((void **)&driver, DRIVER_KEY, r->server->process->pconf);
343 res = encrypt_string(r, driver, dconf, z->encoded, &encoded);
345 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, res, r, LOG_PREFIX
346 "encrypt session failed");
349 z->encoded = encoded;
357 * Crypto decoding for the session.
359 * @param r The request pointer.
360 * @param z A pointer to where the session will be written.
362 AP_DECLARE(int) ap_session_crypto_decode(request_rec * r, session_rec * z)
365 char *encoded = NULL;
367 const apr_crypto_driver_t *driver = NULL;
368 session_crypto_dir_conf *dconf = ap_get_module_config(r->per_dir_config,
369 &session_crypto_module);
371 if ((dconf->passphrase_set) && z->encoded && *z->encoded) {
372 apr_pool_userdata_get((void **)&driver, DRIVER_KEY,
373 r->server->process->pconf);
374 res = decrypt_string(r, driver, dconf, z->encoded, &encoded);
375 if (res != APR_SUCCESS) {
376 ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, LOG_PREFIX
377 "decrypt session failed, wrong passphrase?");
380 z->encoded = encoded;
388 * Initialise the SSL in the post_config hook.
390 AP_DECLARE(int) ap_session_crypto_init(apr_pool_t *p, apr_pool_t *plog,
391 apr_pool_t *ptemp, server_rec *s)
393 const apr_crypto_driver_t *driver = NULL;
395 session_crypto_conf *conf = ap_get_module_config(s->module_config,
396 &session_crypto_module);
398 /* session_crypto_init() will be called twice. Don't bother
399 * going through all of the initialization on the first call
400 * because it will just be thrown away.*/
401 if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG)
406 const apu_err_t *err = NULL;
409 rv = apr_crypto_init(p, NULL);
410 if (APR_SUCCESS != rv) {
411 ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, LOG_PREFIX
412 "APR crypto could not be initialised");
416 rv = apr_crypto_get_driver(p, conf->library, &driver, conf->params, &err);
417 if (APR_EREINIT == rv) {
419 ap_log_error(APLOG_MARK, APLOG_WARNING, rv, s, LOG_PREFIX
420 "warning: crypto for '%s' was already initialised, "
421 "using existing configuration", conf->library);
427 ap_log_error(APLOG_MARK, APLOG_WARNING, rv, s, LOG_PREFIX
428 "warning: crypto for '%s' was not previously initialised "
429 "when it was expected to be, initialised instead by "
430 "mod_session_crypto", conf->library);
433 if (APR_SUCCESS != rv && err) {
434 ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, LOG_PREFIX
438 if (APR_ENOTIMPL == rv) {
439 ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, LOG_PREFIX
440 "The crypto library '%s' could not be found",
444 if (APR_SUCCESS != rv || !driver) {
445 ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, LOG_PREFIX
446 "The crypto library '%s' could not be loaded",
451 ap_log_error(APLOG_MARK, APLOG_INFO, rv, s, LOG_PREFIX
452 "The crypto library '%s' was loaded successfully",
455 apr_pool_userdata_set((const void *)driver, DRIVER_KEY,
456 apr_pool_cleanup_null, s->process->pconf);
463 static void *create_session_crypto_config(apr_pool_t * p, server_rec *s)
465 session_crypto_conf *new =
466 (session_crypto_conf *) apr_pcalloc(p, sizeof(session_crypto_conf));
468 /* if no library has been configured, set the recommended library
469 * as a sensible default.
471 #ifdef APU_CRYPTO_RECOMMENDED_DRIVER
472 new->library = APU_CRYPTO_RECOMMENDED_DRIVER;
478 static void *create_session_crypto_dir_config(apr_pool_t * p, char *dummy)
480 session_crypto_dir_conf *new =
481 (session_crypto_dir_conf *) apr_pcalloc(p, sizeof(session_crypto_dir_conf));
483 /* default cipher AES256-SHA */
484 new->cipher = APR_KEY_AES_256;
489 static void *merge_session_crypto_dir_config(apr_pool_t * p, void *basev, void *addv)
491 session_crypto_dir_conf *new = (session_crypto_dir_conf *) apr_pcalloc(p, sizeof(session_crypto_dir_conf));
492 session_crypto_dir_conf *add = (session_crypto_dir_conf *) addv;
493 session_crypto_dir_conf *base = (session_crypto_dir_conf *) basev;
495 new->passphrase = (add->passphrase_set == 0) ? base->passphrase : add->passphrase;
496 new->params = (add->passphrase_set == 0) ? base->params : add->params;
497 new->passphrase_set = add->passphrase_set || base->passphrase_set;
498 new->cipher = (add->cipher_set == 0) ? base->cipher : add->cipher;
499 new->cipher_set = add->cipher_set || base->cipher_set;
504 static const char *set_crypto_driver(cmd_parms * cmd, void *config, const char *arg)
508 session_crypto_conf *conf =
509 (session_crypto_conf *)ap_get_module_config(cmd->server->module_config,
510 &session_crypto_module);
511 apr_crypto_param_t *param;
513 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
519 conf->params = apr_array_make(cmd->pool, 10, sizeof(apr_crypto_param_t));
522 word = ap_getword_conf(cmd->pool, &arg);
523 val = strchr(word, '=');
525 if (!strcasecmp(word, "noinit")) {
527 conf->noinit_set = 1;
529 else if (!library_set) {
530 conf->library = word;
531 conf->library_set = 1;
535 return "Invalid SessionCryptoDriver parameter. Parameter must "
536 "be in the form 'key=value'.";
541 if (!strcasecmp(word, "dir")) {
542 param = apr_array_push(conf->params);
543 param->type = APR_CRYPTO_CA_TYPE_DIR;
546 else if (!strcasecmp(word, "key3")) {
547 param = apr_array_push(conf->params);
548 param->type = APR_CRYPTO_CERT_TYPE_KEY3_DB;
551 else if (!strcasecmp(word, "cert7")) {
552 param = apr_array_push(conf->params);
553 param->type = APR_CRYPTO_CA_TYPE_CERT7_DB;
556 else if (!strcasecmp(word, "secmod")) {
557 param = apr_array_push(conf->params);
558 param->type = APR_CRYPTO_CA_TYPE_SECMOD;
567 static const char *set_crypto_passphrase(cmd_parms * cmd, void *config, const char *arg)
570 int passphrase_set = 0;
571 session_crypto_dir_conf *dconf = (session_crypto_dir_conf *) config;
572 apr_crypto_param_t *param;
573 dconf->params = apr_array_make(cmd->pool, 10, sizeof(apr_crypto_param_t));
576 word = ap_getword_conf(cmd->pool, &arg);
577 val = strchr(word, '=');
579 if (!passphrase_set) {
580 dconf->passphrase = word;
581 dconf->passphrase_set = 1;
585 return "Invalid SessionCryptoPassphrase parameter. Parameter must "
586 "be in the form 'key=value'.";
591 if (!strcasecmp(word, "engine")) {
592 param = apr_array_push(dconf->params);
593 param->type = APR_CRYPTO_ENGINE;
596 else if (!strcasecmp(word, "cipher")) {
597 if (!strcasecmp(val, "3des192")) {
598 dconf->cipher = APR_KEY_3DES_192;
599 dconf->cipher_set = 1;
601 else if (!strcasecmp(val, "aes256")) {
602 dconf->cipher = APR_KEY_AES_256;
603 dconf->cipher_set = 1;
606 return "Invalid SessionCryptoPassphrase parameter. Cipher must "
607 "be '3des192' or 'aes256'.";
611 return "Invalid SessionCryptoPassphrase parameter. Parameters must "
612 "be 'engine' or 'cipher'.";
620 static const command_rec session_crypto_cmds[] =
622 AP_INIT_RAW_ARGS("SessionCryptoPassphrase", set_crypto_passphrase, NULL, RSRC_CONF|OR_AUTHCFG,
623 "The passphrase used to encrypt the session"),
624 AP_INIT_RAW_ARGS("SessionCryptoDriver", set_crypto_driver, NULL, RSRC_CONF,
625 "The underlying crypto library driver to use"),
629 static void register_hooks(apr_pool_t * p)
631 ap_hook_session_encode(ap_session_crypto_encode, NULL, NULL, APR_HOOK_LAST);
632 ap_hook_session_decode(ap_session_crypto_decode, NULL, NULL, APR_HOOK_FIRST);
633 ap_hook_post_config(ap_session_crypto_init, NULL, NULL, APR_HOOK_FIRST);
636 AP_DECLARE_MODULE(session_crypto) =
638 STANDARD20_MODULE_STUFF,
639 create_session_crypto_dir_config, /* dir config creater */
640 merge_session_crypto_dir_config, /* dir merger --- default is to
642 create_session_crypto_config, /* server config */
643 NULL, /* merge server config */
644 session_crypto_cmds, /* command apr_table_t */
645 register_hooks /* register hooks */