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, ¶ms, 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