]> granicus.if.org Git - apache/blob - modules/ssl/ssl_engine_pphrase.c
mod_cache: Don't add cached/revalidated entity headers to a 304 response.
[apache] / modules / ssl / ssl_engine_pphrase.c
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 /*                      _             _
18  *  _ __ ___   ___   __| |    ___ ___| |  mod_ssl
19  * | '_ ` _ \ / _ \ / _` |   / __/ __| |  Apache Interface to OpenSSL
20  * | | | | | | (_) | (_| |   \__ \__ \ |
21  * |_| |_| |_|\___/ \__,_|___|___/___/_|
22  *                      |_____|
23  *  ssl_engine_pphrase.c
24  *  Pass Phrase Dialog
25  */
26                              /* ``Treat your password like your
27                                   toothbrush. Don't let anybody
28                                   else use it, and get a new one
29                                   every six months.''
30                                            -- Clifford Stoll     */
31 #include "ssl_private.h"
32
33 typedef struct {
34     server_rec         *s;
35     apr_pool_t         *p;
36     apr_array_header_t *aPassPhrase;
37     int                 nPassPhraseCur;
38     char               *cpPassPhraseCur;
39     int                 nPassPhraseDialog;
40     int                 nPassPhraseDialogCur;
41     BOOL                bPassPhraseDialogOnce;
42     const char         *key_id;
43     const char         *pkey_file;
44 } pphrase_cb_arg_t;
45
46 /*
47  * Return true if the named file exists and is readable
48  */
49
50 static apr_status_t exists_and_readable(const char *fname, apr_pool_t *pool,
51                                         apr_time_t *mtime)
52 {
53     apr_status_t stat;
54     apr_finfo_t sbuf;
55     apr_file_t *fd;
56
57     if ((stat = apr_stat(&sbuf, fname, APR_FINFO_MIN, pool)) != APR_SUCCESS)
58         return stat;
59
60     if (sbuf.filetype != APR_REG)
61         return APR_EGENERAL;
62
63     if ((stat = apr_file_open(&fd, fname, APR_READ, 0, pool)) != APR_SUCCESS)
64         return stat;
65
66     if (mtime) {
67         *mtime = sbuf.mtime;
68     }
69
70     apr_file_close(fd);
71     return APR_SUCCESS;
72 }
73
74 /*
75  * reuse vhost keys for asn1 tables where keys are allocated out
76  * of s->process->pool to prevent "leaking" each time we format
77  * a vhost key.  since the key is stored in a table with lifetime
78  * of s->process->pool, the key needs to have the same lifetime.
79  *
80  * XXX: probably seems silly to use a hash table with keys and values
81  * being the same, but it is easier than doing a linear search
82  * and will make it easier to remove keys if needed in the future.
83  * also have the problem with apr_array_header_t that if we
84  * underestimate the number of vhost keys when we apr_array_make(),
85  * the array will get resized when we push past the initial number
86  * of elts.  this resizing in the s->process->pool means "leaking"
87  * since apr_array_push() will apr_alloc arr->nalloc * 2 elts,
88  * leaving the original arr->elts to waste.
89  */
90 static const char *asn1_table_vhost_key(SSLModConfigRec *mc, apr_pool_t *p,
91                                   const char *id, int i)
92 {
93     /* 'p' pool used here is cleared on restarts (or sooner) */
94     char *key = apr_psprintf(p, "%s:%d", id, i);
95     void *keyptr = apr_hash_get(mc->tVHostKeys, key,
96                                 APR_HASH_KEY_STRING);
97
98     if (!keyptr) {
99         /* make a copy out of s->process->pool */
100         keyptr = apr_pstrdup(mc->pPool, key);
101         apr_hash_set(mc->tVHostKeys, keyptr,
102                      APR_HASH_KEY_STRING, keyptr);
103     }
104
105     return (char *)keyptr;
106 }
107
108 /*  _________________________________________________________________
109 **
110 **  Pass Phrase and Private Key Handling
111 **  _________________________________________________________________
112 */
113
114 #define BUILTIN_DIALOG_BACKOFF 2
115 #define BUILTIN_DIALOG_RETRIES 5
116
117 static apr_file_t *writetty = NULL;
118 static apr_file_t *readtty = NULL;
119
120 int ssl_pphrase_Handle_CB(char *, int, int, void *);
121
122 static char *pphrase_array_get(apr_array_header_t *arr, int idx)
123 {
124     if ((idx < 0) || (idx >= arr->nelts)) {
125         return NULL;
126     }
127
128     return ((char **)arr->elts)[idx];
129 }
130
131 apr_status_t ssl_load_encrypted_pkey(server_rec *s, apr_pool_t *p, int idx,
132                                      const char *pkey_file,
133                                      apr_array_header_t **pphrases)
134 {
135     SSLModConfigRec *mc = myModConfig(s);
136     SSLSrvConfigRec *sc = mySrvConfig(s);
137     const char *key_id = asn1_table_vhost_key(mc, p, sc->vhost_id, idx);
138     EVP_PKEY *pPrivateKey = NULL;
139     ssl_asn1_t *asn1;
140     unsigned char *ucp;
141     long int length;
142     BOOL bReadable;
143     int nPassPhrase = (*pphrases)->nelts;
144     int nPassPhraseRetry = 0;
145     apr_time_t pkey_mtime = 0;
146     apr_status_t rv;
147     pphrase_cb_arg_t ppcb_arg;
148
149     if (!pkey_file) {
150          ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(02573)
151                       "Init: No private key specified for %s", key_id);
152          return ssl_die(s);
153     }
154     else if ((rv = exists_and_readable(pkey_file, p, &pkey_mtime))
155              != APR_SUCCESS ) {
156          ap_log_error(APLOG_MARK, APLOG_EMERG, rv, s, APLOGNO(02574)
157                       "Init: Can't open server private key file %s", pkey_file);
158          return ssl_die(s);
159     }
160
161     ppcb_arg.s                     = s;
162     ppcb_arg.p                     = p;
163     ppcb_arg.aPassPhrase           = *pphrases;
164     ppcb_arg.nPassPhraseCur        = 0;
165     ppcb_arg.cpPassPhraseCur       = NULL;
166     ppcb_arg.nPassPhraseDialog     = 0;
167     ppcb_arg.nPassPhraseDialogCur  = 0;
168     ppcb_arg.bPassPhraseDialogOnce = TRUE;
169     ppcb_arg.key_id                = key_id;
170     ppcb_arg.pkey_file             = pkey_file;
171
172     /*
173      * if the private key is encrypted and SSLPassPhraseDialog
174      * is configured to "builtin" it isn't possible to prompt for
175      * a password after httpd has detached from the tty.
176      * in this case if we already have a private key and the
177      * file name/mtime hasn't changed, then reuse the existing key.
178      * we also reuse existing private keys that were encrypted for
179      * exec: and pipe: dialogs to minimize chances to snoop the
180      * password.  that and pipe: dialogs might prompt the user
181      * for password, which on win32 for example could happen 4
182      * times at startup.  twice for each child and twice within
183      * each since apache "restarts itself" on startup.
184      * of course this will not work for the builtin dialog if
185      * the server was started without LoadModule ssl_module
186      * configured, then restarted with it configured.
187      * but we fall through with a chance of success if the key
188      * is not encrypted or can be handled via exec or pipe dialog.
189      * and in the case of fallthrough, pkey_mtime and isatty()
190      * are used to give a better idea as to what failed.
191      */
192     if (pkey_mtime) {
193         ssl_asn1_t *asn1 = ssl_asn1_table_get(mc->tPrivateKey, key_id);
194         if (asn1 && (asn1->source_mtime == pkey_mtime)) {
195             ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(02575)
196                          "Reusing existing private key from %s on restart",
197                          ppcb_arg.pkey_file);
198             return APR_SUCCESS;
199         }
200     }
201
202     ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(02576)
203                  "Attempting to load encrypted (?) private key %s", key_id);
204
205     for (;;) {
206         /*
207          * Try to read the private key file with the help of
208          * the callback function which serves the pass
209          * phrases to OpenSSL
210          */
211
212         ppcb_arg.cpPassPhraseCur = NULL;
213
214         /* Ensure that the error stack is empty; some SSL
215          * functions will fail spuriously if the error stack
216          * is not empty. */
217         ERR_clear_error();
218
219         bReadable = ((pPrivateKey = SSL_read_PrivateKey(ppcb_arg.pkey_file,
220                      NULL, ssl_pphrase_Handle_CB, &ppcb_arg)) != NULL ?
221                      TRUE : FALSE);
222
223         /*
224          * when the private key file now was readable,
225          * it's fine and we go out of the loop
226          */
227         if (bReadable)
228            break;
229
230         /*
231          * when we have more remembered pass phrases
232          * try to reuse these first.
233          */
234         if (ppcb_arg.nPassPhraseCur < nPassPhrase) {
235             ppcb_arg.nPassPhraseCur++;
236             continue;
237         }
238
239         /*
240          * else it's not readable and we have no more
241          * remembered pass phrases. Then this has to mean
242          * that the callback function popped up the dialog
243          * but a wrong pass phrase was entered.  We give the
244          * user (but not the dialog program) a few more
245          * chances...
246          */
247 #ifndef WIN32
248         if ((sc->server->pphrase_dialog_type == SSL_PPTYPE_BUILTIN
249              || sc->server->pphrase_dialog_type == SSL_PPTYPE_PIPE)
250 #else
251         if (sc->server->pphrase_dialog_type == SSL_PPTYPE_PIPE
252 #endif
253             && ppcb_arg.cpPassPhraseCur != NULL
254             && nPassPhraseRetry < BUILTIN_DIALOG_RETRIES ) {
255             apr_file_printf(writetty, "Apache:mod_ssl:Error: Pass phrase incorrect "
256                     "(%d more retr%s permitted).\n",
257                     (BUILTIN_DIALOG_RETRIES-nPassPhraseRetry),
258                     (BUILTIN_DIALOG_RETRIES-nPassPhraseRetry) == 1 ? "y" : "ies");
259             nPassPhraseRetry++;
260             if (nPassPhraseRetry > BUILTIN_DIALOG_BACKOFF)
261                 apr_sleep((nPassPhraseRetry-BUILTIN_DIALOG_BACKOFF)
262                             * 5 * APR_USEC_PER_SEC);
263             continue;
264         }
265 #ifdef WIN32
266         if (sc->server->pphrase_dialog_type == SSL_PPTYPE_BUILTIN) {
267             ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(02577)
268                          "Init: SSLPassPhraseDialog builtin is not "
269                          "supported on Win32 (key file "
270                          "%s)", ppcb_arg.pkey_file);
271             return ssl_die(s);
272         }
273 #endif /* WIN32 */
274
275         /*
276          * Ok, anything else now means a fatal error.
277          */
278         if (ppcb_arg.cpPassPhraseCur == NULL) {
279             if (ppcb_arg.nPassPhraseDialogCur && pkey_mtime &&
280                 !isatty(fileno(stdout))) /* XXX: apr_isatty() */
281             {
282                 ap_log_error(APLOG_MARK, APLOG_ERR, 0,
283                              s, APLOGNO(02578)
284                              "Init: Unable to read pass phrase "
285                              "[Hint: key introduced or changed "
286                              "before restart?]");
287                 ssl_log_ssl_error(SSLLOG_MARK, APLOG_ERR, s);
288             }
289             else {
290                 ap_log_error(APLOG_MARK, APLOG_ERR, 0,
291                              s, APLOGNO(02579) "Init: Private key not found");
292                 ssl_log_ssl_error(SSLLOG_MARK, APLOG_ERR, s);
293             }
294             if (writetty) {
295                 apr_file_printf(writetty, "Apache:mod_ssl:Error: Private key not found.\n");
296                 apr_file_printf(writetty, "**Stopped\n");
297             }
298         }
299         else {
300             ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(02580)
301                          "Init: Pass phrase incorrect for key %s",
302                          key_id);
303             ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s);
304
305             if (writetty) {
306                 apr_file_printf(writetty, "Apache:mod_ssl:Error: Pass phrase incorrect.\n");
307                 apr_file_printf(writetty, "**Stopped\n");
308             }
309         }
310         return ssl_die(s);
311     }
312
313     if (pPrivateKey == NULL) {
314         ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(02581)
315                      "Init: Unable to read server private key from file %s",
316                      ppcb_arg.pkey_file);
317         ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s);
318         return ssl_die(s);
319     }
320
321     /*
322      * Log the type of reading
323      */
324     if (ppcb_arg.nPassPhraseDialogCur == 0) {
325         ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(02582)
326                      "unencrypted %s private key - pass phrase not "
327                      "required", key_id);
328     }
329     else {
330         if (ppcb_arg.cpPassPhraseCur != NULL) {
331             ap_log_error(APLOG_MARK, APLOG_DEBUG, 0,
332                          s, APLOGNO(02583)
333                          "encrypted %s private key - pass phrase "
334                          "requested", key_id);
335         }
336         else {
337             ap_log_error(APLOG_MARK, APLOG_DEBUG, 0,
338                          s, APLOGNO(02584)
339                          "encrypted %s private key - pass phrase"
340                          " reused", key_id);
341         }
342     }
343
344     /*
345      * Ok, when we have one more pass phrase store it
346      */
347     if (ppcb_arg.cpPassPhraseCur != NULL) {
348         *(const char **)apr_array_push(ppcb_arg.aPassPhrase) =
349             ppcb_arg.cpPassPhraseCur;
350         nPassPhrase++;
351     }
352
353     /*
354      * Insert private key into the global module configuration
355      * (we convert it to a stand-alone DER byte sequence
356      * because the SSL library uses static variables inside a
357      * RSA structure which do not survive DSO reloads!)
358      */
359     length = i2d_PrivateKey(pPrivateKey, NULL);
360     ucp = ssl_asn1_table_set(mc->tPrivateKey, key_id, length);
361     (void)i2d_PrivateKey(pPrivateKey, &ucp); /* 2nd arg increments */
362
363     if (ppcb_arg.nPassPhraseDialogCur != 0) {
364         /* remember mtime of encrypted keys */
365         asn1 = ssl_asn1_table_get(mc->tPrivateKey, key_id);
366         asn1->source_mtime = pkey_mtime;
367     }
368
369     /*
370      * Free the private key structure
371      */
372     EVP_PKEY_free(pPrivateKey);
373
374     /*
375      * Let the user know when we're successful.
376      */
377     if ((ppcb_arg.nPassPhraseDialog > 0) &&
378         (ppcb_arg.cpPassPhraseCur != NULL)) {
379         if (writetty) {
380             apr_file_printf(writetty, "\n"
381                             "OK: Pass Phrase Dialog successful.\n");
382         }
383     }
384
385     /* Close the pipes if they were opened
386      */
387     if (readtty) {
388         apr_file_close(readtty);
389         apr_file_close(writetty);
390         readtty = writetty = NULL;
391     }
392
393     return APR_SUCCESS;
394 }
395
396 static apr_status_t ssl_pipe_child_create(apr_pool_t *p, const char *progname)
397 {
398     /* Child process code for 'ErrorLog "|..."';
399      * may want a common framework for this, since I expect it will
400      * be common for other foo-loggers to want this sort of thing...
401      */
402     apr_status_t rc;
403     apr_procattr_t *procattr;
404     apr_proc_t *procnew;
405
406     if (((rc = apr_procattr_create(&procattr, p)) == APR_SUCCESS) &&
407         ((rc = apr_procattr_io_set(procattr,
408                                    APR_FULL_BLOCK,
409                                    APR_FULL_BLOCK,
410                                    APR_NO_PIPE)) == APR_SUCCESS)) {
411         char **args;
412
413         apr_tokenize_to_argv(progname, &args, p);
414         procnew = (apr_proc_t *)apr_pcalloc(p, sizeof(*procnew));
415         rc = apr_proc_create(procnew, args[0], (const char * const *)args,
416                              NULL, procattr, p);
417         if (rc == APR_SUCCESS) {
418             /* XXX: not sure if we aught to...
419              * apr_pool_note_subprocess(p, procnew, APR_KILL_AFTER_TIMEOUT);
420              */
421             writetty = procnew->in;
422             readtty = procnew->out;
423         }
424     }
425
426     return rc;
427 }
428
429 static int pipe_get_passwd_cb(char *buf, int length, char *prompt, int verify)
430 {
431     apr_status_t rc;
432     char *p;
433
434     apr_file_puts(prompt, writetty);
435
436     buf[0]='\0';
437     rc = apr_file_gets(buf, length, readtty);
438     apr_file_puts(APR_EOL_STR, writetty);
439
440     if (rc != APR_SUCCESS || apr_file_eof(readtty)) {
441         memset(buf, 0, length);
442         return 1;  /* failure */
443     }
444     if ((p = strchr(buf, '\n')) != NULL) {
445         *p = '\0';
446     }
447 #ifdef WIN32
448     /* XXX: apr_sometest */
449     if ((p = strchr(buf, '\r')) != NULL) {
450         *p = '\0';
451     }
452 #endif
453     return 0;
454 }
455
456 int ssl_pphrase_Handle_CB(char *buf, int bufsize, int verify, void *srv)
457 {
458     pphrase_cb_arg_t *ppcb_arg = (pphrase_cb_arg_t *)srv;
459     SSLSrvConfigRec *sc = mySrvConfig(ppcb_arg->s);
460     char *cpp;
461     int len = -1;
462
463     ppcb_arg->nPassPhraseDialog++;
464     ppcb_arg->nPassPhraseDialogCur++;
465
466     /*
467      * When remembered pass phrases are available use them...
468      */
469     if ((cpp = pphrase_array_get(ppcb_arg->aPassPhrase,
470                                  ppcb_arg->nPassPhraseCur)) != NULL) {
471         apr_cpystrn(buf, cpp, bufsize);
472         len = strlen(buf);
473         return len;
474     }
475
476     /*
477      * Builtin or Pipe dialog
478      */
479     if (sc->server->pphrase_dialog_type == SSL_PPTYPE_BUILTIN
480           || sc->server->pphrase_dialog_type == SSL_PPTYPE_PIPE) {
481         char *prompt;
482         int i;
483
484         if (sc->server->pphrase_dialog_type == SSL_PPTYPE_PIPE) {
485             if (!readtty) {
486                 ap_log_error(APLOG_MARK, APLOG_INFO, 0, ppcb_arg->s,
487                              APLOGNO(01965)
488                              "Init: Creating pass phrase dialog pipe child "
489                              "'%s'", sc->server->pphrase_dialog_path);
490                 if (ssl_pipe_child_create(ppcb_arg->p,
491                                           sc->server->pphrase_dialog_path)
492                         != APR_SUCCESS) {
493                     ap_log_error(APLOG_MARK, APLOG_ERR, 0, ppcb_arg->s,
494                                  APLOGNO(01966)
495                                  "Init: Failed to create pass phrase pipe '%s'",
496                                  sc->server->pphrase_dialog_path);
497                     PEMerr(PEM_F_PEM_DEF_CALLBACK,
498                            PEM_R_PROBLEMS_GETTING_PASSWORD);
499                     memset(buf, 0, (unsigned int)bufsize);
500                     return (-1);
501                 }
502             }
503             ap_log_error(APLOG_MARK, APLOG_INFO, 0, ppcb_arg->s, APLOGNO(01967)
504                          "Init: Requesting pass phrase via piped dialog");
505         }
506         else { /* sc->server->pphrase_dialog_type == SSL_PPTYPE_BUILTIN */
507 #ifdef WIN32
508             PEMerr(PEM_F_PEM_DEF_CALLBACK, PEM_R_PROBLEMS_GETTING_PASSWORD);
509             memset(buf, 0, (unsigned int)bufsize);
510             return (-1);
511 #else
512             /*
513              * stderr has already been redirected to the error_log.
514              * rather than attempting to temporarily rehook it to the terminal,
515              * we print the prompt to stdout before EVP_read_pw_string turns
516              * off tty echo
517              */
518             apr_file_open_stdout(&writetty, ppcb_arg->p);
519
520             ap_log_error(APLOG_MARK, APLOG_INFO, 0, ppcb_arg->s, APLOGNO(01968)
521                          "Init: Requesting pass phrase via builtin terminal "
522                          "dialog");
523 #endif
524         }
525
526         /*
527          * The first time display a header to inform the user about what
528          * program he actually speaks to, which module is responsible for
529          * this terminal dialog and why to the hell he has to enter
530          * something...
531          */
532         if (ppcb_arg->nPassPhraseDialog == 1) {
533             apr_file_printf(writetty, "%s mod_ssl (Pass Phrase Dialog)\n",
534                             AP_SERVER_BASEVERSION);
535             apr_file_printf(writetty, "Some of your private key files are encrypted for security reasons.\n");
536             apr_file_printf(writetty, "In order to read them you have to provide the pass phrases.\n");
537         }
538         if (ppcb_arg->bPassPhraseDialogOnce) {
539             ppcb_arg->bPassPhraseDialogOnce = FALSE;
540             apr_file_printf(writetty, "\n");
541             apr_file_printf(writetty, "Private key %s (%s)\n",
542                             ppcb_arg->key_id, ppcb_arg->pkey_file);
543         }
544
545         /*
546          * Emulate the OpenSSL internal pass phrase dialog
547          * (see crypto/pem/pem_lib.c:def_callback() for details)
548          */
549         prompt = "Enter pass phrase:";
550
551         for (;;) {
552             apr_file_puts(prompt, writetty);
553             if (sc->server->pphrase_dialog_type == SSL_PPTYPE_PIPE) {
554                 i = pipe_get_passwd_cb(buf, bufsize, "", FALSE);
555             }
556             else { /* sc->server->pphrase_dialog_type == SSL_PPTYPE_BUILTIN */
557                 i = EVP_read_pw_string(buf, bufsize, "", FALSE);
558             }
559             if (i != 0) {
560                 PEMerr(PEM_F_PEM_DEF_CALLBACK,PEM_R_PROBLEMS_GETTING_PASSWORD);
561                 memset(buf, 0, (unsigned int)bufsize);
562                 return (-1);
563             }
564             len = strlen(buf);
565             if (len < 1)
566                 apr_file_printf(writetty, "Apache:mod_ssl:Error: Pass phrase empty (needs to be at least 1 character).\n");
567             else
568                 break;
569         }
570     }
571
572     /*
573      * Filter program
574      */
575     else if (sc->server->pphrase_dialog_type == SSL_PPTYPE_FILTER) {
576         const char *cmd = sc->server->pphrase_dialog_path;
577         const char **argv = apr_palloc(ppcb_arg->p, sizeof(char *) * 3);
578         char *result;
579
580         ap_log_error(APLOG_MARK, APLOG_INFO, 0, ppcb_arg->s, APLOGNO(01969)
581                      "Init: Requesting pass phrase from dialog filter "
582                      "program (%s)", cmd);
583
584         argv[0] = cmd;
585         argv[1] = ppcb_arg->key_id;
586         argv[2] = NULL;
587
588         result = ssl_util_readfilter(ppcb_arg->s, ppcb_arg->p, cmd, argv);
589         apr_cpystrn(buf, result, bufsize);
590         len = strlen(buf);
591     }
592
593     /*
594      * Ok, we now have the pass phrase, so give it back
595      */
596     ppcb_arg->cpPassPhraseCur = apr_pstrdup(ppcb_arg->p, buf);
597
598     /*
599      * And return its length to OpenSSL...
600      */
601     return (len);
602 }