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 #ifdef HAVE_ECC
47 static const char *key_types[] = {"RSA", "DSA", "ECC"};
48 #else
49 static const char *key_types[] = {"RSA", "DSA"};
50 #endif
51 
52 /*
53  * Return true if the named file exists and is readable
54  */
55 
exists_and_readable(const char * fname,apr_pool_t * pool,apr_time_t * mtime)56 static apr_status_t exists_and_readable(const char *fname, apr_pool_t *pool,
57                                         apr_time_t *mtime)
58 {
59     apr_status_t stat;
60     apr_finfo_t sbuf;
61     apr_file_t *fd;
62 
63     if ((stat = apr_stat(&sbuf, fname, APR_FINFO_MIN, pool)) != APR_SUCCESS)
64         return stat;
65 
66     if (sbuf.filetype != APR_REG)
67         return APR_EGENERAL;
68 
69     if ((stat = apr_file_open(&fd, fname, APR_READ, 0, pool)) != APR_SUCCESS)
70         return stat;
71 
72     if (mtime) {
73         *mtime = sbuf.mtime;
74     }
75 
76     apr_file_close(fd);
77     return APR_SUCCESS;
78 }
79 
80 /*
81  * reuse vhost keys for asn1 tables where keys are allocated out
82  * of s->process->pool to prevent "leaking" each time we format
83  * a vhost key.  since the key is stored in a table with lifetime
84  * of s->process->pool, the key needs to have the same lifetime.
85  *
86  * XXX: probably seems silly to use a hash table with keys and values
87  * being the same, but it is easier than doing a linear search
88  * and will make it easier to remove keys if needed in the future.
89  * also have the problem with apr_array_header_t that if we
90  * underestimate the number of vhost keys when we apr_array_make(),
91  * the array will get resized when we push past the initial number
92  * of elts.  this resizing in the s->process->pool means "leaking"
93  * since apr_array_push() will apr_alloc arr->nalloc * 2 elts,
94  * leaving the original arr->elts to waste.
95  */
asn1_table_vhost_key(SSLModConfigRec * mc,apr_pool_t * p,const char * id,int i)96 static const char *asn1_table_vhost_key(SSLModConfigRec *mc, apr_pool_t *p,
97                                   const char *id, int i)
98 {
99     /* 'p' pool used here is cleared on restarts (or sooner) */
100     char *key = apr_psprintf(p, "%s:%d", id, i);
101     void *keyptr = apr_hash_get(mc->tVHostKeys, key,
102                                 APR_HASH_KEY_STRING);
103 
104     if (!keyptr) {
105         /* make a copy out of s->process->pool */
106         keyptr = apr_pstrdup(mc->pPool, key);
107         apr_hash_set(mc->tVHostKeys, keyptr,
108                      APR_HASH_KEY_STRING, keyptr);
109     }
110 
111     return (char *)keyptr;
112 }
113 
114 /*  _________________________________________________________________
115 **
116 **  Pass Phrase and Private Key Handling
117 **  _________________________________________________________________
118 */
119 
120 #define BUILTIN_DIALOG_BACKOFF 2
121 #define BUILTIN_DIALOG_RETRIES 5
122 
123 static apr_file_t *writetty = NULL;
124 static apr_file_t *readtty = NULL;
125 
126 int ssl_pphrase_Handle_CB(char *, int, int, void *);
127 
pphrase_array_get(apr_array_header_t * arr,int idx)128 static char *pphrase_array_get(apr_array_header_t *arr, int idx)
129 {
130     if ((idx < 0) || (idx >= arr->nelts)) {
131         return NULL;
132     }
133 
134     return ((char **)arr->elts)[idx];
135 }
136 
ssl_load_encrypted_pkey(server_rec * s,apr_pool_t * p,int idx,const char * pkey_file,apr_array_header_t ** pphrases)137 apr_status_t ssl_load_encrypted_pkey(server_rec *s, apr_pool_t *p, int idx,
138                                      const char *pkey_file,
139                                      apr_array_header_t **pphrases)
140 {
141     SSLModConfigRec *mc = myModConfig(s);
142     SSLSrvConfigRec *sc = mySrvConfig(s);
143     const char *key_id = asn1_table_vhost_key(mc, p, sc->vhost_id, idx);
144     EVP_PKEY *pPrivateKey = NULL;
145     ssl_asn1_t *asn1;
146     int nPassPhrase = (*pphrases)->nelts;
147     int nPassPhraseRetry = 0;
148     apr_time_t pkey_mtime = 0;
149     apr_status_t rv;
150     pphrase_cb_arg_t ppcb_arg;
151 
152     if (!pkey_file) {
153          ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(02573)
154                       "Init: No private key specified for %s", key_id);
155          return ssl_die(s);
156     }
157     else if ((rv = exists_and_readable(pkey_file, p, &pkey_mtime))
158              != APR_SUCCESS ) {
159          ap_log_error(APLOG_MARK, APLOG_EMERG, rv, s, APLOGNO(02574)
160                       "Init: Can't open server private key file %s", pkey_file);
161          return ssl_die(s);
162     }
163 
164     ppcb_arg.s                     = s;
165     ppcb_arg.p                     = p;
166     ppcb_arg.aPassPhrase           = *pphrases;
167     ppcb_arg.nPassPhraseCur        = 0;
168     ppcb_arg.cpPassPhraseCur       = NULL;
169     ppcb_arg.nPassPhraseDialog     = 0;
170     ppcb_arg.nPassPhraseDialogCur  = 0;
171     ppcb_arg.bPassPhraseDialogOnce = TRUE;
172     ppcb_arg.key_id                = key_id;
173     ppcb_arg.pkey_file             = pkey_file;
174 
175     /*
176      * if the private key is encrypted and SSLPassPhraseDialog
177      * is configured to "builtin" it isn't possible to prompt for
178      * a password after httpd has detached from the tty.
179      * in this case if we already have a private key and the
180      * file name/mtime hasn't changed, then reuse the existing key.
181      * we also reuse existing private keys that were encrypted for
182      * exec: and pipe: dialogs to minimize chances to snoop the
183      * password.  that and pipe: dialogs might prompt the user
184      * for password, which on win32 for example could happen 4
185      * times at startup.  twice for each child and twice within
186      * each since apache "restarts itself" on startup.
187      * of course this will not work for the builtin dialog if
188      * the server was started without LoadModule ssl_module
189      * configured, then restarted with it configured.
190      * but we fall through with a chance of success if the key
191      * is not encrypted or can be handled via exec or pipe dialog.
192      * and in the case of fallthrough, pkey_mtime and isatty()
193      * are used to give a better idea as to what failed.
194      */
195     if (pkey_mtime) {
196         ssl_asn1_t *asn1 = ssl_asn1_table_get(mc->tPrivateKey, key_id);
197         if (asn1 && (asn1->source_mtime == pkey_mtime)) {
198             ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(02575)
199                          "Reusing existing private key from %s on restart",
200                          ppcb_arg.pkey_file);
201             return APR_SUCCESS;
202         }
203     }
204 
205     ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(02576)
206                  "Attempting to load encrypted (?) private key %s", key_id);
207 
208     for (;;) {
209         /*
210          * Try to read the private key file with the help of
211          * the callback function which serves the pass
212          * phrases to OpenSSL
213          */
214 
215         ppcb_arg.cpPassPhraseCur = NULL;
216 
217         /* Ensure that the error stack is empty; some SSL
218          * functions will fail spuriously if the error stack
219          * is not empty. */
220         ERR_clear_error();
221 
222         pPrivateKey = modssl_read_privatekey(ppcb_arg.pkey_file,
223                                              ssl_pphrase_Handle_CB, &ppcb_arg);
224         /* If the private key was successfully read, nothing more to
225            do here. */
226         if (pPrivateKey != NULL)
227             break;
228 
229         /*
230          * when we have more remembered pass phrases
231          * try to reuse these first.
232          */
233         if (ppcb_arg.nPassPhraseCur < nPassPhrase) {
234             ppcb_arg.nPassPhraseCur++;
235             continue;
236         }
237 
238         /*
239          * else it's not readable and we have no more
240          * remembered pass phrases. Then this has to mean
241          * that the callback function popped up the dialog
242          * but a wrong pass phrase was entered.  We give the
243          * user (but not the dialog program) a few more
244          * chances...
245          */
246 #ifndef WIN32
247         if ((sc->server->pphrase_dialog_type == SSL_PPTYPE_BUILTIN
248              || sc->server->pphrase_dialog_type == SSL_PPTYPE_PIPE)
249 #else
250         if (sc->server->pphrase_dialog_type == SSL_PPTYPE_PIPE
251 #endif
252             && ppcb_arg.cpPassPhraseCur != NULL
253             && nPassPhraseRetry < BUILTIN_DIALOG_RETRIES ) {
254             apr_file_printf(writetty, "Apache:mod_ssl:Error: Pass phrase incorrect "
255                     "(%d more retr%s permitted).\n",
256                     (BUILTIN_DIALOG_RETRIES-nPassPhraseRetry),
257                     (BUILTIN_DIALOG_RETRIES-nPassPhraseRetry) == 1 ? "y" : "ies");
258             nPassPhraseRetry++;
259             if (nPassPhraseRetry > BUILTIN_DIALOG_BACKOFF)
260                 apr_sleep((nPassPhraseRetry-BUILTIN_DIALOG_BACKOFF)
261                             * 5 * APR_USEC_PER_SEC);
262             continue;
263         }
264 #ifdef WIN32
265         if (sc->server->pphrase_dialog_type == SSL_PPTYPE_BUILTIN) {
266             ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(02577)
267                          "Init: SSLPassPhraseDialog builtin is not "
268                          "supported on Win32 (key file "
269                          "%s)", ppcb_arg.pkey_file);
270             return ssl_die(s);
271         }
272 #endif /* WIN32 */
273 
274         /*
275          * Ok, anything else now means a fatal error.
276          */
277         if (ppcb_arg.cpPassPhraseCur == NULL) {
278             if (ppcb_arg.nPassPhraseDialogCur && pkey_mtime &&
279                 !isatty(fileno(stdout))) /* XXX: apr_isatty() */
280             {
281                 ap_log_error(APLOG_MARK, APLOG_ERR, 0,
282                              s, APLOGNO(02578)
283                              "Init: Unable to read pass phrase "
284                              "[Hint: key introduced or changed "
285                              "before restart?]");
286                 ssl_log_ssl_error(SSLLOG_MARK, APLOG_ERR, s);
287             }
288             else {
289                 ap_log_error(APLOG_MARK, APLOG_ERR, 0,
290                              s, APLOGNO(02579) "Init: Private key not found");
291                 ssl_log_ssl_error(SSLLOG_MARK, APLOG_ERR, s);
292             }
293             if (writetty) {
294                 apr_file_printf(writetty, "Apache:mod_ssl:Error: Private key not found.\n");
295                 apr_file_printf(writetty, "**Stopped\n");
296             }
297         }
298         else {
299             ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(02580)
300                          "Init: Pass phrase incorrect for key %s",
301                          key_id);
302             ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s);
303 
304             if (writetty) {
305                 apr_file_printf(writetty, "Apache:mod_ssl:Error: Pass phrase incorrect.\n");
306                 apr_file_printf(writetty, "**Stopped\n");
307             }
308         }
309         return ssl_die(s);
310     }
311 
312     if (pPrivateKey == NULL) {
313         ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(02581)
314                      "Init: Unable to read server private key from file %s",
315                      ppcb_arg.pkey_file);
316         ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s);
317         return ssl_die(s);
318     }
319 
320     /*
321      * Log the type of reading
322      */
323     if (ppcb_arg.nPassPhraseDialogCur == 0) {
324         ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(02582)
325                      "unencrypted %s private key - pass phrase not "
326                      "required", key_id);
327     }
328     else {
329         if (ppcb_arg.cpPassPhraseCur != NULL) {
330             ap_log_error(APLOG_MARK, APLOG_DEBUG, 0,
331                          s, APLOGNO(02583)
332                          "encrypted %s private key - pass phrase "
333                          "requested", key_id);
334         }
335         else {
336             ap_log_error(APLOG_MARK, APLOG_DEBUG, 0,
337                          s, APLOGNO(02584)
338                          "encrypted %s private key - pass phrase"
339                          " reused", key_id);
340         }
341     }
342 
343     /*
344      * Ok, when we have one more pass phrase store it
345      */
346     if (ppcb_arg.cpPassPhraseCur != NULL) {
347         *(const char **)apr_array_push(ppcb_arg.aPassPhrase) =
348             ppcb_arg.cpPassPhraseCur;
349         nPassPhrase++;
350     }
351 
352     /* Cache the private key in the global module configuration so it
353      * can be used after subsequent reloads. */
354     asn1 = ssl_asn1_table_set(mc->tPrivateKey, key_id, pPrivateKey);
355 
356     if (ppcb_arg.nPassPhraseDialogCur != 0) {
357         /* remember mtime of encrypted keys */
358         asn1->source_mtime = pkey_mtime;
359     }
360 
361     /*
362      * Free the private key structure
363      */
364     EVP_PKEY_free(pPrivateKey);
365 
366     /*
367      * Let the user know when we're successful.
368      */
369     if ((ppcb_arg.nPassPhraseDialog > 0) &&
370         (ppcb_arg.cpPassPhraseCur != NULL)) {
371         if (writetty) {
372             apr_file_printf(writetty, "\n"
373                             "OK: Pass Phrase Dialog successful.\n");
374         }
375     }
376 
377     /* Close the pipes if they were opened
378      */
379     if (readtty) {
380         apr_file_close(readtty);
381         apr_file_close(writetty);
382         readtty = writetty = NULL;
383     }
384 
385     return APR_SUCCESS;
386 }
387 
ssl_pipe_child_create(apr_pool_t * p,const char * progname)388 static apr_status_t ssl_pipe_child_create(apr_pool_t *p, const char *progname)
389 {
390     /* Child process code for 'ErrorLog "|..."';
391      * may want a common framework for this, since I expect it will
392      * be common for other foo-loggers to want this sort of thing...
393      */
394     apr_status_t rc;
395     apr_procattr_t *procattr;
396     apr_proc_t *procnew;
397 
398     if (((rc = apr_procattr_create(&procattr, p)) == APR_SUCCESS) &&
399         ((rc = apr_procattr_io_set(procattr,
400                                    APR_FULL_BLOCK,
401                                    APR_FULL_BLOCK,
402                                    APR_NO_PIPE)) == APR_SUCCESS)) {
403         char **args;
404 
405         apr_tokenize_to_argv(progname, &args, p);
406         procnew = (apr_proc_t *)apr_pcalloc(p, sizeof(*procnew));
407         rc = apr_proc_create(procnew, args[0], (const char * const *)args,
408                              NULL, procattr, p);
409         if (rc == APR_SUCCESS) {
410             /* XXX: not sure if we aught to...
411              * apr_pool_note_subprocess(p, procnew, APR_KILL_AFTER_TIMEOUT);
412              */
413             writetty = procnew->in;
414             readtty = procnew->out;
415         }
416     }
417 
418     return rc;
419 }
420 
pipe_get_passwd_cb(char * buf,int length,char * prompt,int verify)421 static int pipe_get_passwd_cb(char *buf, int length, char *prompt, int verify)
422 {
423     apr_status_t rc;
424     char *p;
425 
426     apr_file_puts(prompt, writetty);
427 
428     buf[0]='\0';
429     rc = apr_file_gets(buf, length, readtty);
430     apr_file_puts(APR_EOL_STR, writetty);
431 
432     if (rc != APR_SUCCESS || apr_file_eof(readtty)) {
433         memset(buf, 0, length);
434         return 1;  /* failure */
435     }
436     if ((p = strchr(buf, '\n')) != NULL) {
437         *p = '\0';
438     }
439 #ifdef WIN32
440     /* XXX: apr_sometest */
441     if ((p = strchr(buf, '\r')) != NULL) {
442         *p = '\0';
443     }
444 #endif
445     return 0;
446 }
447 
ssl_pphrase_Handle_CB(char * buf,int bufsize,int verify,void * srv)448 int ssl_pphrase_Handle_CB(char *buf, int bufsize, int verify, void *srv)
449 {
450     pphrase_cb_arg_t *ppcb_arg = (pphrase_cb_arg_t *)srv;
451     SSLSrvConfigRec *sc = mySrvConfig(ppcb_arg->s);
452     char *cpp;
453     int len = -1;
454 
455     ppcb_arg->nPassPhraseDialog++;
456     ppcb_arg->nPassPhraseDialogCur++;
457 
458     /*
459      * When remembered pass phrases are available use them...
460      */
461     if ((cpp = pphrase_array_get(ppcb_arg->aPassPhrase,
462                                  ppcb_arg->nPassPhraseCur)) != NULL) {
463         apr_cpystrn(buf, cpp, bufsize);
464         len = strlen(buf);
465         return len;
466     }
467 
468     /*
469      * Builtin or Pipe dialog
470      */
471     if (sc->server->pphrase_dialog_type == SSL_PPTYPE_BUILTIN
472           || sc->server->pphrase_dialog_type == SSL_PPTYPE_PIPE) {
473         char *prompt;
474         int i;
475 
476         if (sc->server->pphrase_dialog_type == SSL_PPTYPE_PIPE) {
477             if (!readtty) {
478                 ap_log_error(APLOG_MARK, APLOG_INFO, 0, ppcb_arg->s,
479                              APLOGNO(01965)
480                              "Init: Creating pass phrase dialog pipe child "
481                              "'%s'", sc->server->pphrase_dialog_path);
482                 if (ssl_pipe_child_create(ppcb_arg->p,
483                                           sc->server->pphrase_dialog_path)
484                         != APR_SUCCESS) {
485                     ap_log_error(APLOG_MARK, APLOG_ERR, 0, ppcb_arg->s,
486                                  APLOGNO(01966)
487                                  "Init: Failed to create pass phrase pipe '%s'",
488                                  sc->server->pphrase_dialog_path);
489                     PEMerr(PEM_F_PEM_DEF_CALLBACK,
490                            PEM_R_PROBLEMS_GETTING_PASSWORD);
491                     memset(buf, 0, (unsigned int)bufsize);
492                     return (-1);
493                 }
494             }
495             ap_log_error(APLOG_MARK, APLOG_INFO, 0, ppcb_arg->s, APLOGNO(01967)
496                          "Init: Requesting pass phrase via piped dialog");
497         }
498         else { /* sc->server->pphrase_dialog_type == SSL_PPTYPE_BUILTIN */
499 #ifdef WIN32
500             PEMerr(PEM_F_PEM_DEF_CALLBACK, PEM_R_PROBLEMS_GETTING_PASSWORD);
501             memset(buf, 0, (unsigned int)bufsize);
502             return (-1);
503 #else
504             /*
505              * stderr has already been redirected to the error_log.
506              * rather than attempting to temporarily rehook it to the terminal,
507              * we print the prompt to stdout before EVP_read_pw_string turns
508              * off tty echo
509              */
510             apr_file_open_stdout(&writetty, ppcb_arg->p);
511 
512             ap_log_error(APLOG_MARK, APLOG_INFO, 0, ppcb_arg->s, APLOGNO(01968)
513                          "Init: Requesting pass phrase via builtin terminal "
514                          "dialog");
515 #endif
516         }
517 
518         /*
519          * The first time display a header to inform the user about what
520          * program he actually speaks to, which module is responsible for
521          * this terminal dialog and why to the hell he has to enter
522          * something...
523          */
524         if (ppcb_arg->nPassPhraseDialog == 1) {
525             apr_file_printf(writetty, "%s mod_ssl (Pass Phrase Dialog)\n",
526                             AP_SERVER_BASEVERSION);
527             apr_file_printf(writetty, "Some of your private key files are encrypted for security reasons.\n");
528             apr_file_printf(writetty, "In order to read them you have to provide the pass phrases.\n");
529         }
530         if (ppcb_arg->bPassPhraseDialogOnce) {
531             ppcb_arg->bPassPhraseDialogOnce = FALSE;
532             apr_file_printf(writetty, "\n");
533             apr_file_printf(writetty, "Private key %s (%s)\n",
534                             ppcb_arg->key_id, ppcb_arg->pkey_file);
535         }
536 
537         /*
538          * Emulate the OpenSSL internal pass phrase dialog
539          * (see crypto/pem/pem_lib.c:def_callback() for details)
540          */
541         prompt = "Enter pass phrase:";
542 
543         for (;;) {
544             apr_file_puts(prompt, writetty);
545             if (sc->server->pphrase_dialog_type == SSL_PPTYPE_PIPE) {
546                 i = pipe_get_passwd_cb(buf, bufsize, "", FALSE);
547             }
548             else { /* sc->server->pphrase_dialog_type == SSL_PPTYPE_BUILTIN */
549                 i = EVP_read_pw_string(buf, bufsize, "", FALSE);
550             }
551             if (i != 0) {
552                 PEMerr(PEM_F_PEM_DEF_CALLBACK,PEM_R_PROBLEMS_GETTING_PASSWORD);
553                 memset(buf, 0, (unsigned int)bufsize);
554                 return (-1);
555             }
556             len = strlen(buf);
557             if (len < 1)
558                 apr_file_printf(writetty, "Apache:mod_ssl:Error: Pass phrase empty (needs to be at least 1 character).\n");
559             else
560                 break;
561         }
562     }
563 
564     /*
565      * Filter program
566      */
567     else if (sc->server->pphrase_dialog_type == SSL_PPTYPE_FILTER) {
568         const char *cmd = sc->server->pphrase_dialog_path;
569         const char **argv = apr_palloc(ppcb_arg->p, sizeof(char *) * 4);
570         const char *idx = ap_strrchr_c(ppcb_arg->key_id, ':') + 1;
571         char *result;
572         int i;
573 
574         ap_log_error(APLOG_MARK, APLOG_INFO, 0, ppcb_arg->s, APLOGNO(01969)
575                      "Init: Requesting pass phrase from dialog filter "
576                      "program (%s)", cmd);
577 
578         argv[0] = cmd;
579         argv[1] = apr_pstrndup(ppcb_arg->p, ppcb_arg->key_id,
580                                idx-1 - ppcb_arg->key_id);
581         if ((i = atoi(idx)) < CERTKEYS_IDX_MAX+1) {
582             /*
583              * For compatibility with existing 2.4.x configurations, use
584              * "RSA", "DSA" and "ECC" strings for the first two/three keys
585              */
586             argv[2] = key_types[i];
587         } else {
588             /* Four and above: use the integer index */
589             argv[2] = apr_pstrdup(ppcb_arg->p, idx);
590         }
591         argv[3] = NULL;
592 
593         result = ssl_util_readfilter(ppcb_arg->s, ppcb_arg->p, cmd, argv);
594         apr_cpystrn(buf, result, bufsize);
595         len = strlen(buf);
596     }
597 
598     /*
599      * Ok, we now have the pass phrase, so give it back
600      */
601     ppcb_arg->cpPassPhraseCur = apr_pstrdup(ppcb_arg->p, buf);
602 
603     /*
604      * And return its length to OpenSSL...
605      */
606     return (len);
607 }
608 
609 
610 #if defined(HAVE_OPENSSL_ENGINE_H) && defined(HAVE_ENGINE_INIT)
611 
612 /* OpenSSL UI implementation for passphrase entry; largely duplicated
613  * from ssl_pphrase_Handle_CB but adjusted for UI API. TODO: Might be
614  * worth trying to shift pphrase handling over to the UI API
615  * completely. */
passphrase_ui_open(UI * ui)616 static int passphrase_ui_open(UI *ui)
617 {
618     pphrase_cb_arg_t *ppcb = UI_get0_user_data(ui);
619     SSLSrvConfigRec *sc = mySrvConfig(ppcb->s);
620 
621     ppcb->nPassPhraseDialog++;
622     ppcb->nPassPhraseDialogCur++;
623 
624     /*
625      * Builtin or Pipe dialog
626      */
627     if (sc->server->pphrase_dialog_type == SSL_PPTYPE_BUILTIN
628         || sc->server->pphrase_dialog_type == SSL_PPTYPE_PIPE) {
629         if (sc->server->pphrase_dialog_type == SSL_PPTYPE_PIPE) {
630             if (!readtty) {
631                 ap_log_error(APLOG_MARK, APLOG_INFO, 0, ppcb->s,
632                              APLOGNO(10143)
633                              "Init: Creating pass phrase dialog pipe child "
634                              "'%s'", sc->server->pphrase_dialog_path);
635                 if (ssl_pipe_child_create(ppcb->p,
636                             sc->server->pphrase_dialog_path)
637                         != APR_SUCCESS) {
638                     ap_log_error(APLOG_MARK, APLOG_ERR, 0, ppcb->s,
639                                  APLOGNO(10144)
640                                  "Init: Failed to create pass phrase pipe '%s'",
641                                  sc->server->pphrase_dialog_path);
642                     return 0;
643                 }
644             }
645             ap_log_error(APLOG_MARK, APLOG_INFO, 0, ppcb->s, APLOGNO(10145)
646                          "Init: Requesting pass phrase via piped dialog");
647         }
648         else { /* sc->server->pphrase_dialog_type == SSL_PPTYPE_BUILTIN */
649 #ifdef WIN32
650             ap_log_error(APLOG_MARK, APLOG_ERR, 0, ppcb->s, APLOGNO(10146)
651                          "Init: Failed to create pass phrase pipe '%s'",
652                          sc->server->pphrase_dialog_path);
653             return 0;
654 #else
655             /*
656              * stderr has already been redirected to the error_log.
657              * rather than attempting to temporarily rehook it to the terminal,
658              * we print the prompt to stdout before EVP_read_pw_string turns
659              * off tty echo
660              */
661             apr_file_open_stdout(&writetty, ppcb->p);
662 
663             ap_log_error(APLOG_MARK, APLOG_INFO, 0, ppcb->s, APLOGNO(10147)
664                          "Init: Requesting pass phrase via builtin terminal "
665                          "dialog");
666 #endif
667         }
668 
669         /*
670          * The first time display a header to inform the user about what
671          * program he actually speaks to, which module is responsible for
672          * this terminal dialog and why to the hell he has to enter
673          * something...
674          */
675         if (ppcb->nPassPhraseDialog == 1) {
676             apr_file_printf(writetty, "%s mod_ssl (Pass Phrase Dialog)\n",
677                             AP_SERVER_BASEVERSION);
678             apr_file_printf(writetty,
679                             "A pass phrase is required to access the private key.\n");
680         }
681         if (ppcb->bPassPhraseDialogOnce) {
682             ppcb->bPassPhraseDialogOnce = FALSE;
683             apr_file_printf(writetty, "\n");
684             apr_file_printf(writetty, "Private key %s (%s)\n",
685                             ppcb->key_id, ppcb->pkey_file);
686         }
687     }
688 
689     return 1;
690 }
691 
passphrase_ui_read(UI * ui,UI_STRING * uis)692 static int passphrase_ui_read(UI *ui, UI_STRING *uis)
693 {
694     pphrase_cb_arg_t *ppcb = UI_get0_user_data(ui);
695     SSLSrvConfigRec *sc = mySrvConfig(ppcb->s);
696     const char *prompt;
697     int i;
698     int bufsize;
699     int len;
700     char *buf;
701 
702     prompt = UI_get0_output_string(uis);
703     if (prompt == NULL) {
704         prompt = "Enter pass phrase:";
705     }
706 
707     /*
708      * Get the maximum expected size and allocate the buffer
709      */
710     bufsize = UI_get_result_maxsize(uis);
711     buf = apr_pcalloc(ppcb->p, bufsize);
712 
713     if (sc->server->pphrase_dialog_type == SSL_PPTYPE_BUILTIN
714         || sc->server->pphrase_dialog_type == SSL_PPTYPE_PIPE) {
715         /*
716          * Get the pass phrase through a callback.
717          * Empty input is not accepted.
718          */
719         for (;;) {
720             if (sc->server->pphrase_dialog_type == SSL_PPTYPE_PIPE) {
721                 i = pipe_get_passwd_cb(buf, bufsize, "", FALSE);
722             }
723             else { /* sc->server->pphrase_dialog_type == SSL_PPTYPE_BUILTIN */
724                 i = EVP_read_pw_string(buf, bufsize, "", FALSE);
725             }
726             if (i != 0) {
727                 OPENSSL_cleanse(buf, bufsize);
728                 return 0;
729             }
730             len = strlen(buf);
731             if (len < 1){
732                 apr_file_printf(writetty, "Apache:mod_ssl:Error: Pass phrase"
733                                 "empty (needs to be at least 1 character).\n");
734                 apr_file_puts(prompt, writetty);
735             }
736             else {
737                 break;
738             }
739         }
740     }
741     /*
742      * Filter program
743      */
744     else if (sc->server->pphrase_dialog_type == SSL_PPTYPE_FILTER) {
745         const char *cmd = sc->server->pphrase_dialog_path;
746         const char **argv = apr_palloc(ppcb->p, sizeof(char *) * 3);
747         char *result;
748 
749         ap_log_error(APLOG_MARK, APLOG_INFO, 0, ppcb->s, APLOGNO(10148)
750                      "Init: Requesting pass phrase from dialog filter "
751                      "program (%s)", cmd);
752 
753         argv[0] = cmd;
754         argv[1] = ppcb->key_id;
755         argv[2] = NULL;
756 
757         result = ssl_util_readfilter(ppcb->s, ppcb->p, cmd, argv);
758         apr_cpystrn(buf, result, bufsize);
759         len = strlen(buf);
760     }
761 
762     /*
763      * Ok, we now have the pass phrase, so give it back
764      */
765     ppcb->cpPassPhraseCur = apr_pstrdup(ppcb->p, buf);
766     UI_set_result(ui, uis, buf);
767 
768     /* Clear sensitive data. */
769     OPENSSL_cleanse(buf, bufsize);
770     return 1;
771 }
772 
passphrase_ui_write(UI * ui,UI_STRING * uis)773 static int passphrase_ui_write(UI *ui, UI_STRING *uis)
774 {
775     pphrase_cb_arg_t *ppcb = UI_get0_user_data(ui);
776     SSLSrvConfigRec *sc;
777     const char *prompt;
778 
779     sc = mySrvConfig(ppcb->s);
780 
781     if (sc->server->pphrase_dialog_type == SSL_PPTYPE_BUILTIN
782         || sc->server->pphrase_dialog_type == SSL_PPTYPE_PIPE) {
783         prompt = UI_get0_output_string(uis);
784         apr_file_puts(prompt, writetty);
785     }
786 
787     return 1;
788 }
789 
passphrase_ui_close(UI * ui)790 static int passphrase_ui_close(UI *ui)
791 {
792     /*
793      * Close the pipes if they were opened
794      */
795     if (readtty) {
796         apr_file_close(readtty);
797         apr_file_close(writetty);
798         readtty = writetty = NULL;
799     }
800     return 1;
801 }
802 
pp_ui_method_cleanup(void * uip)803 static apr_status_t pp_ui_method_cleanup(void *uip)
804 {
805     UI_METHOD *uim = uip;
806 
807     UI_destroy_method(uim);
808 
809     return APR_SUCCESS;
810 }
811 
get_passphrase_ui(apr_pool_t * p)812 static UI_METHOD *get_passphrase_ui(apr_pool_t *p)
813 {
814     UI_METHOD *ui_method = UI_create_method("Passphrase UI");
815 
816     UI_method_set_opener(ui_method, passphrase_ui_open);
817     UI_method_set_reader(ui_method, passphrase_ui_read);
818     UI_method_set_writer(ui_method, passphrase_ui_write);
819     UI_method_set_closer(ui_method, passphrase_ui_close);
820 
821     apr_pool_cleanup_register(p, ui_method, pp_ui_method_cleanup,
822                               pp_ui_method_cleanup);
823 
824     return ui_method;
825 }
826 #endif
827 
828 
modssl_load_engine_keypair(server_rec * s,apr_pool_t * p,const char * vhostid,const char * certid,const char * keyid,X509 ** pubkey,EVP_PKEY ** privkey)829 apr_status_t modssl_load_engine_keypair(server_rec *s, apr_pool_t *p,
830                                         const char *vhostid,
831                                         const char *certid, const char *keyid,
832                                         X509 **pubkey, EVP_PKEY **privkey)
833 {
834 #if defined(HAVE_OPENSSL_ENGINE_H) && defined(HAVE_ENGINE_INIT)
835     const char *c, *scheme;
836     ENGINE *e;
837     UI_METHOD *ui_method = get_passphrase_ui(p);
838     pphrase_cb_arg_t ppcb;
839 
840     memset(&ppcb, 0, sizeof ppcb);
841     ppcb.s = s;
842     ppcb.p = p;
843     ppcb.bPassPhraseDialogOnce = TRUE;
844     ppcb.key_id = vhostid;
845     ppcb.pkey_file = keyid;
846 
847     c = ap_strchr_c(keyid, ':');
848     if (!c || c == keyid) {
849         ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(10131)
850                      "Init: Unrecognized private key identifier `%s'",
851                      keyid);
852         return ssl_die(s);
853     }
854 
855     scheme = apr_pstrmemdup(p, keyid, c - keyid);
856     if (!(e = ENGINE_by_id(scheme))) {
857         ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(10132)
858                      "Init: Failed to load engine for private key %s",
859                      keyid);
860         ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s);
861         return ssl_die(s);
862     }
863 
864     if (!ENGINE_init(e)) {
865         ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(10149)
866                      "Init: Failed to initialize engine %s for private key %s",
867                      scheme, keyid);
868         ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s);
869         return ssl_die(s);
870     }
871 
872     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
873                  "Init: Initialized engine %s for private key %s",
874                  scheme, keyid);
875 
876     if (APLOGdebug(s)) {
877         ENGINE_ctrl_cmd_string(e, "VERBOSE", NULL, 0);
878     }
879 
880     if (certid) {
881         struct {
882             const char *cert_id;
883             X509 *cert;
884         } params = { certid, NULL };
885 
886         if (!ENGINE_ctrl_cmd(e, "LOAD_CERT_CTRL", 0, &params, NULL, 1)) {
887             ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(10136)
888                          "Init: Unable to get the certificate");
889             ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s);
890             return ssl_die(s);
891         }
892 
893         *pubkey = params.cert;
894     }
895 
896     *privkey = ENGINE_load_private_key(e, keyid, ui_method, &ppcb);
897     if (*privkey == NULL) {
898         ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(10133)
899                      "Init: Unable to get the private key");
900         ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s);
901         return ssl_die(s);
902     }
903 
904     ENGINE_finish(e);
905     ENGINE_free(e);
906 
907     return APR_SUCCESS;
908 #else
909     return APR_ENOTIMPL;
910 #endif
911 }
912