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
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
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.
15 */
16
17#include "mod_session.h"
18#include "apu_version.h"
19#include "apr_base64.h"                /* for apr_base64_decode et al */
20#include "apr_lib.h"
21#include "apr_strings.h"
22#include "http_log.h"
23#include "http_core.h"
24
25#if APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION < 4
26
27#error session_crypto_module requires APU v1.4.0 or later
28
29#elif APU_HAVE_CRYPTO == 0
30
31#error Crypto support must be enabled in APR
32
33#else
34
35#include "apr_crypto.h"                /* for apr_*_crypt et al */
36
37#define CRYPTO_KEY "session_crypto_context"
38
39module AP_MODULE_DECLARE_DATA session_crypto_module;
40
41/**
42 * Structure to carry the per-dir session config.
43 */
44typedef struct {
45    apr_array_header_t *passphrases;
46    int passphrases_set;
47    const char *cipher;
48    int cipher_set;
49} session_crypto_dir_conf;
50
51/**
52 * Structure to carry the server wide session config.
53 */
54typedef struct {
55    const char *library;
56    const char *params;
57    int library_set;
58} session_crypto_conf;
59
60/**
61 * Initialise the encryption as per the current config.
62 *
63 * Returns APR_SUCCESS if successful.
64 */
65static apr_status_t crypt_init(request_rec *r,
66        const apr_crypto_t *f, apr_crypto_block_key_type_e **cipher,
67        session_crypto_dir_conf * dconf)
68{
69    apr_status_t res;
70    apr_hash_t *ciphers;
71
72    res = apr_crypto_get_block_key_types(&ciphers, f);
73    if (APR_SUCCESS != res) {
74        ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, APLOGNO(01823)
75                "no ciphers returned by APR. "
76                "session encryption not possible");
77        return res;
78    }
79
80    *cipher = apr_hash_get(ciphers, dconf->cipher, APR_HASH_KEY_STRING);
81    if (!(*cipher)) {
82        apr_hash_index_t *hi;
83        const void *key;
84        apr_ssize_t klen;
85        int sum = 0;
86        int offset = 0;
87        char *options = NULL;
88
89        for (hi = apr_hash_first(r->pool, ciphers); hi; hi = apr_hash_next(hi)) {
90            apr_hash_this(hi, NULL, &klen, NULL);
91            sum += klen + 2;
92        }
93        for (hi = apr_hash_first(r->pool, ciphers); hi; hi = apr_hash_next(hi)) {
94            apr_hash_this(hi, &key, &klen, NULL);
95            if (!options) {
96                options = apr_palloc(r->pool, sum + 1);
97            }
98            else {
99                options[offset++] = ',';
100                options[offset++] = ' ';
101            }
102            strncpy(options + offset, key, klen);
103            offset += klen;
104        }
105        options[offset] = 0;
106
107        ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, APLOGNO(01824)
108                "cipher '%s' not recognised by crypto driver. "
109                "session encryption not possible, options: %s", dconf->cipher, options);
110
111        return APR_EGENERAL;
112    }
113
114    return APR_SUCCESS;
115}
116
117/**
118 * Encrypt the string given as per the current config.
119 *
120 * Returns APR_SUCCESS if successful.
121 */
122static apr_status_t encrypt_string(request_rec * r, const apr_crypto_t *f,
123        session_crypto_dir_conf *dconf, const char *in, char **out)
124{
125    apr_status_t res;
126    apr_crypto_key_t *key = NULL;
127    apr_size_t ivSize = 0;
128    apr_crypto_block_t *block = NULL;
129    unsigned char *encrypt = NULL;
130    unsigned char *combined = NULL;
131    apr_size_t encryptlen, tlen;
132    char *base64;
133    apr_size_t blockSize = 0;
134    const unsigned char *iv = NULL;
135    apr_uuid_t salt;
136    apr_crypto_block_key_type_e *cipher;
137    const char *passphrase;
138
139    /* by default, return an empty string */
140    *out = "";
141
142    /* don't attempt to encrypt an empty string, trying to do so causes a segfault */
143    if (!in || !*in) {
144        return APR_SUCCESS;
145    }
146
147    /* use a uuid as a salt value, and prepend it to our result */
148    apr_uuid_get(&salt);
149    res = crypt_init(r, f, &cipher, dconf);
150    if (res != APR_SUCCESS) {
151        return res;
152    }
153
154    /* encrypt using the first passphrase in the list */
155    passphrase = APR_ARRAY_IDX(dconf->passphrases, 0, char *);
156    res = apr_crypto_passphrase(&key, &ivSize, passphrase,
157            strlen(passphrase),
158            (unsigned char *) (&salt), sizeof(apr_uuid_t),
159            *cipher, APR_MODE_CBC, 1, 4096, f, r->pool);
160    if (APR_STATUS_IS_ENOKEY(res)) {
161        ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, APLOGNO(01825)
162                "the passphrase '%s' was empty", passphrase);
163    }
164    if (APR_STATUS_IS_EPADDING(res)) {
165        ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, APLOGNO(01826)
166                "padding is not supported for cipher");
167    }
168    if (APR_STATUS_IS_EKEYTYPE(res)) {
169        ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, APLOGNO(01827)
170                "the key type is not known");
171    }
172    if (APR_SUCCESS != res) {
173        ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, APLOGNO(01828)
174                "encryption could not be configured.");
175        return res;
176    }
177
178    res = apr_crypto_block_encrypt_init(&block, &iv, key, &blockSize, r->pool);
179    if (APR_SUCCESS != res) {
180        ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, APLOGNO(01829)
181                "apr_crypto_block_encrypt_init failed");
182        return res;
183    }
184
185    /* encrypt the given string */
186    res = apr_crypto_block_encrypt(&encrypt, &encryptlen, (unsigned char *)in,
187            strlen(in), block);
188    if (APR_SUCCESS != res) {
189        ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, APLOGNO(01830)
190                "apr_crypto_block_encrypt failed");
191        return res;
192    }
193    res = apr_crypto_block_encrypt_finish(encrypt + encryptlen, &tlen, block);
194    if (APR_SUCCESS != res) {
195        ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, APLOGNO(01831)
196                "apr_crypto_block_encrypt_finish failed");
197        return res;
198    }
199    encryptlen += tlen;
200
201    /* prepend the salt and the iv to the result */
202    combined = apr_palloc(r->pool, ivSize + encryptlen + sizeof(apr_uuid_t));
203    memcpy(combined, &salt, sizeof(apr_uuid_t));
204    memcpy(combined + sizeof(apr_uuid_t), iv, ivSize);
205    memcpy(combined + sizeof(apr_uuid_t) + ivSize, encrypt, encryptlen);
206
207    /* base64 encode the result */
208    base64 = apr_palloc(r->pool, apr_base64_encode_len(ivSize + encryptlen +
209                    sizeof(apr_uuid_t) + 1)
210            * sizeof(char));
211    apr_base64_encode(base64, (const char *) combined,
212            ivSize + encryptlen + sizeof(apr_uuid_t));
213    *out = base64;
214
215    return res;
216
217}
218
219/**
220 * Decrypt the string given as per the current config.
221 *
222 * Returns APR_SUCCESS if successful.
223 */
224static apr_status_t decrypt_string(request_rec * r, const apr_crypto_t *f,
225        session_crypto_dir_conf *dconf, const char *in, char **out)
226{
227    apr_status_t res;
228    apr_crypto_key_t *key = NULL;
229    apr_size_t ivSize = 0;
230    apr_crypto_block_t *block = NULL;
231    unsigned char *decrypted = NULL;
232    apr_size_t decryptedlen, tlen;
233    apr_size_t decodedlen;
234    char *decoded;
235    apr_size_t blockSize = 0;
236    apr_crypto_block_key_type_e *cipher;
237    int i = 0;
238
239    /* strip base64 from the string */
240    decoded = apr_palloc(r->pool, apr_base64_decode_len(in));
241    decodedlen = apr_base64_decode(decoded, in);
242    decoded[decodedlen] = '\0';
243
244    res = crypt_init(r, f, &cipher, dconf);
245    if (res != APR_SUCCESS) {
246        return res;
247    }
248
249    /* try each passphrase in turn */
250    for (; i < dconf->passphrases->nelts; i++) {
251        const char *passphrase = APR_ARRAY_IDX(dconf->passphrases, i, char *);
252        apr_size_t len = decodedlen;
253        char *slider = decoded;
254
255        /* encrypt using the first passphrase in the list */
256        res = apr_crypto_passphrase(&key, &ivSize, passphrase,
257                strlen(passphrase),
258                (unsigned char *)decoded, sizeof(apr_uuid_t),
259                *cipher, APR_MODE_CBC, 1, 4096, f, r->pool);
260        if (APR_STATUS_IS_ENOKEY(res)) {
261            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, res, r, APLOGNO(01832)
262                    "the passphrase '%s' was empty", passphrase);
263            continue;
264        }
265        else if (APR_STATUS_IS_EPADDING(res)) {
266            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, res, r, APLOGNO(01833)
267                    "padding is not supported for cipher");
268            continue;
269        }
270        else if (APR_STATUS_IS_EKEYTYPE(res)) {
271            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, res, r, APLOGNO(01834)
272                    "the key type is not known");
273            continue;
274        }
275        else if (APR_SUCCESS != res) {
276            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, res, r, APLOGNO(01835)
277                    "encryption could not be configured.");
278            continue;
279        }
280
281        /* sanity check - decoded too short? */
282        if (decodedlen < (sizeof(apr_uuid_t) + ivSize)) {
283            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(01836)
284                    "too short to decrypt, skipping");
285            res = APR_ECRYPT;
286            continue;
287        }
288
289        /* bypass the salt at the start of the decoded block */
290        slider += sizeof(apr_uuid_t);
291        len -= sizeof(apr_uuid_t);
292
293        res = apr_crypto_block_decrypt_init(&block, &blockSize, (unsigned char *)slider, key,
294                r->pool);
295        if (APR_SUCCESS != res) {
296            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, res, r, APLOGNO(01837)
297                    "apr_crypto_block_decrypt_init failed");
298            continue;
299        }
300
301        /* bypass the iv at the start of the decoded block */
302        slider += ivSize;
303        len -= ivSize;
304
305        /* decrypt the given string */
306        res = apr_crypto_block_decrypt(&decrypted, &decryptedlen,
307                (unsigned char *)slider, len, block);
308        if (res) {
309            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, res, r, APLOGNO(01838)
310                    "apr_crypto_block_decrypt failed");
311            continue;
312        }
313        *out = (char *) decrypted;
314
315        res = apr_crypto_block_decrypt_finish(decrypted + decryptedlen, &tlen, block);
316        if (APR_SUCCESS != res) {
317            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, res, r, APLOGNO(01839)
318                    "apr_crypto_block_decrypt_finish failed");
319            continue;
320        }
321        decryptedlen += tlen;
322        decrypted[decryptedlen] = 0;
323
324        break;
325    }
326
327    if (APR_SUCCESS != res) {
328        ap_log_rerror(APLOG_MARK, APLOG_INFO, res, r, APLOGNO(01840)
329                "decryption failed");
330    }
331
332    return res;
333
334}
335
336/**
337 * Crypto encoding for the session.
338 *
339 * @param r The request pointer.
340 * @param z A pointer to where the session will be written.
341 */
342static apr_status_t session_crypto_encode(request_rec * r, session_rec * z)
343{
344
345    char *encoded = NULL;
346    apr_status_t res;
347    const apr_crypto_t *f = NULL;
348    session_crypto_dir_conf *dconf = ap_get_module_config(r->per_dir_config,
349            &session_crypto_module);
350
351    if (dconf->passphrases_set && z->encoded && *z->encoded) {
352        apr_pool_userdata_get((void **)&f, CRYPTO_KEY, r->server->process->pconf);
353        res = encrypt_string(r, f, dconf, z->encoded, &encoded);
354        if (res != OK) {
355            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, res, r, APLOGNO(01841)
356                    "encrypt session failed");
357            return res;
358        }
359        z->encoded = encoded;
360    }
361
362    return OK;
363
364}
365
366/**
367 * Crypto decoding for the session.
368 *
369 * @param r The request pointer.
370 * @param z A pointer to where the session will be written.
371 */
372static apr_status_t session_crypto_decode(request_rec * r,
373        session_rec * z)
374{
375
376    char *encoded = NULL;
377    apr_status_t res;
378    const apr_crypto_t *f = NULL;
379    session_crypto_dir_conf *dconf = ap_get_module_config(r->per_dir_config,
380            &session_crypto_module);
381
382    if ((dconf->passphrases_set) && z->encoded && *z->encoded) {
383        apr_pool_userdata_get((void **)&f, CRYPTO_KEY,
384                r->server->process->pconf);
385        res = decrypt_string(r, f, dconf, z->encoded, &encoded);
386        if (res != APR_SUCCESS) {
387            ap_log_rerror(APLOG_MARK, APLOG_ERR, res, r, APLOGNO(01842)
388                    "decrypt session failed, wrong passphrase?");
389            return res;
390        }
391        z->encoded = encoded;
392    }
393
394    return OK;
395
396}
397
398/**
399 * Initialise the SSL in the post_config hook.
400 */
401static int session_crypto_init(apr_pool_t *p, apr_pool_t *plog,
402        apr_pool_t *ptemp, server_rec *s)
403{
404    const apr_crypto_driver_t *driver = NULL;
405    apr_crypto_t *f = NULL;
406
407    session_crypto_conf *conf = ap_get_module_config(s->module_config,
408            &session_crypto_module);
409
410    /* session_crypto_init() will be called twice. Don't bother
411     * going through all of the initialization on the first call
412     * because it will just be thrown away.*/
413    if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG) {
414        return OK;
415    }
416
417    if (conf->library) {
418
419        const apu_err_t *err = NULL;
420        apr_status_t rv;
421
422        rv = apr_crypto_init(p);
423        if (APR_SUCCESS != rv) {
424            ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(01843)
425                    "APR crypto could not be initialised");
426            return rv;
427        }
428
429        rv = apr_crypto_get_driver(&driver, conf->library, conf->params, &err, p);
430        if (APR_EREINIT == rv) {
431            ap_log_error(APLOG_MARK, APLOG_WARNING, rv, s, APLOGNO(01844)
432                    "warning: crypto for '%s' was already initialised, "
433                    "using existing configuration", conf->library);
434            rv = APR_SUCCESS;
435        }
436        if (APR_SUCCESS != rv && err) {
437            ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(01845)
438                    "The crypto library '%s' could not be loaded: %s (%s: %d)", conf->library, err->msg, err->reason, err->rc);
439            return rv;
440        }
441        if (APR_ENOTIMPL == rv) {
442            ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(01846)
443                    "The crypto library '%s' could not be found",
444                    conf->library);
445            return rv;
446        }
447        if (APR_SUCCESS != rv || !driver) {
448            ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(01847)
449                    "The crypto library '%s' could not be loaded",
450                    conf->library);
451            return rv;
452        }
453
454        rv = apr_crypto_make(&f, driver, conf->params, p);
455        if (APR_SUCCESS != rv) {
456            ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(01848)
457                    "The crypto library '%s' could not be initialised",
458                    conf->library);
459            return rv;
460        }
461
462        ap_log_error(APLOG_MARK, APLOG_INFO, rv, s, APLOGNO(01849)
463                "The crypto library '%s' was loaded successfully",
464                conf->library);
465
466        apr_pool_userdata_set((const void *)f, CRYPTO_KEY,
467                apr_pool_cleanup_null, s->process->pconf);
468
469    }
470
471    return OK;
472}
473
474static void *create_session_crypto_config(apr_pool_t * p, server_rec *s)
475{
476    session_crypto_conf *new =
477    (session_crypto_conf *) apr_pcalloc(p, sizeof(session_crypto_conf));
478
479    /* if no library has been configured, set the recommended library
480     * as a sensible default.
481     */
482#ifdef APU_CRYPTO_RECOMMENDED_DRIVER
483    new->library = APU_CRYPTO_RECOMMENDED_DRIVER;
484#endif
485
486    return (void *) new;
487}
488
489static void *create_session_crypto_dir_config(apr_pool_t * p, char *dummy)
490{
491    session_crypto_dir_conf *new =
492    (session_crypto_dir_conf *) apr_pcalloc(p, sizeof(session_crypto_dir_conf));
493
494    new->passphrases = apr_array_make(p, 10, sizeof(char *));
495
496    /* default cipher AES256-SHA */
497    new->cipher = "aes256";
498
499    return (void *) new;
500}
501
502static void *merge_session_crypto_dir_config(apr_pool_t * p, void *basev, void *addv)
503{
504    session_crypto_dir_conf *new = (session_crypto_dir_conf *) apr_pcalloc(p, sizeof(session_crypto_dir_conf));
505    session_crypto_dir_conf *add = (session_crypto_dir_conf *) addv;
506    session_crypto_dir_conf *base = (session_crypto_dir_conf *) basev;
507
508    new->passphrases = (add->passphrases_set == 0) ? base->passphrases : add->passphrases;
509    new->passphrases_set = add->passphrases_set || base->passphrases_set;
510    new->cipher = (add->cipher_set == 0) ? base->cipher : add->cipher;
511    new->cipher_set = add->cipher_set || base->cipher_set;
512
513    return new;
514}
515
516static const char *set_crypto_driver(cmd_parms * cmd, void *config, const char *arg)
517{
518    session_crypto_conf *conf =
519    (session_crypto_conf *)ap_get_module_config(cmd->server->module_config,
520            &session_crypto_module);
521
522    const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
523
524    if (err != NULL) {
525        return err;
526    }
527
528    conf->library = ap_getword_conf(cmd->pool, &arg);
529    conf->params = arg;
530    conf->library_set = 1;
531
532    return NULL;
533}
534
535static const char *set_crypto_passphrase(cmd_parms * cmd, void *config, const char *arg)
536{
537    int arglen = strlen(arg);
538    char **argv;
539    char *result;
540    const char **passphrase;
541    session_crypto_dir_conf *dconf = (session_crypto_dir_conf *) config;
542
543    passphrase = apr_array_push(dconf->passphrases);
544
545    if ((arglen > 5) && strncmp(arg, "exec:", 5) == 0) {
546        if (apr_tokenize_to_argv(arg+5, &argv, cmd->temp_pool) != APR_SUCCESS) {
547            return apr_pstrcat(cmd->pool,
548                               "Unable to parse exec arguments from ",
549                               arg+5, NULL);
550        }
551        argv[0] = ap_server_root_relative(cmd->temp_pool, argv[0]);
552
553        if (!argv[0]) {
554            return apr_pstrcat(cmd->pool,
555                               "Invalid SessionCryptoPassphrase exec location:",
556                               arg+5, NULL);
557        }
558        result = ap_get_exec_line(cmd->pool,
559                                  (const char*)argv[0], (const char * const *)argv);
560
561        if(!result) {
562            return apr_pstrcat(cmd->pool,
563                               "Unable to get bind password from exec of ",
564                               arg+5, NULL);
565        }
566        *passphrase = result;
567    }
568    else {
569        *passphrase = arg;
570    }
571
572    dconf->passphrases_set = 1;
573
574    return NULL;
575}
576
577static const char *set_crypto_passphrase_file(cmd_parms *cmd, void *config,
578                                  const char *filename)
579{
580    char buffer[MAX_STRING_LEN];
581    char *arg;
582    const char *args;
583    ap_configfile_t *file;
584    apr_status_t rv;
585
586    filename = ap_server_root_relative(cmd->temp_pool, filename);
587    rv = ap_pcfg_openfile(&file, cmd->temp_pool, filename);
588    if (rv != APR_SUCCESS) {
589        return apr_psprintf(cmd->pool, "%s: Could not open file %s: %pm",
590                            cmd->cmd->name, filename, &rv);
591    }
592
593    while (!(ap_cfg_getline(buffer, sizeof(buffer), file))) {
594        args = buffer;
595        while (*(arg = ap_getword_conf(cmd->pool, &args)) != '\0') {
596            if (*arg == '#') {
597                break;
598            }
599            set_crypto_passphrase(cmd, config, arg);
600        }
601    }
602
603    ap_cfg_closefile(file);
604
605    return NULL;
606}
607
608static const char *set_crypto_cipher(cmd_parms * cmd, void *config, const char *cipher)
609{
610    session_crypto_dir_conf *dconf = (session_crypto_dir_conf *) config;
611
612    dconf->cipher = cipher;
613    dconf->cipher_set = 1;
614
615    return NULL;
616}
617
618static const command_rec session_crypto_cmds[] =
619{
620    AP_INIT_ITERATE("SessionCryptoPassphrase", set_crypto_passphrase, NULL, RSRC_CONF|OR_AUTHCFG,
621            "The passphrase(s) used to encrypt the session. First will be used for encryption, all phrases will be accepted for decryption"),
622    AP_INIT_TAKE1("SessionCryptoPassphraseFile", set_crypto_passphrase_file, NULL, RSRC_CONF|ACCESS_CONF,
623            "File containing passphrase(s) used to encrypt the session, one per line. First will be used for encryption, all phrases will be accepted for decryption"),
624    AP_INIT_TAKE1("SessionCryptoCipher", set_crypto_cipher, NULL, RSRC_CONF|OR_AUTHCFG,
625            "The underlying crypto cipher to use"),
626    AP_INIT_RAW_ARGS("SessionCryptoDriver", set_crypto_driver, NULL, RSRC_CONF,
627            "The underlying crypto library driver to use"),
628    { NULL }
629};
630
631static void register_hooks(apr_pool_t * p)
632{
633    ap_hook_session_encode(session_crypto_encode, NULL, NULL, APR_HOOK_LAST);
634    ap_hook_session_decode(session_crypto_decode, NULL, NULL, APR_HOOK_FIRST);
635    ap_hook_post_config(session_crypto_init, NULL, NULL, APR_HOOK_LAST);
636}
637
638AP_DECLARE_MODULE(session_crypto) =
639{
640    STANDARD20_MODULE_STUFF,
641    create_session_crypto_dir_config, /* dir config creater */
642    merge_session_crypto_dir_config, /* dir merger --- default is to override */
643    create_session_crypto_config, /* server config */
644    NULL, /* merge server config */
645    session_crypto_cmds, /* command apr_table_t */
646    register_hooks /* register hooks */
647};
648
649#endif
650