/* Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License.
*/
/* _ _ * _ __ ___ ___ __| | ___ ___| | mod_ssl * | '_ ` _ \ / _ \ / _` | / __/ __| | Apache Interface to OpenSSL * | | | | | | (_) | (_| | \__ \__ \ | * |_| |_| |_|\___/ \__,_|___|___/___/_| * |_____| * ssl_engine_pphrase.c * Pass Phrase Dialog
*/ /* ``Treat your password like your toothbrush. Don't let anybody else use it, and get a new one every six months.''
-- Clifford Stoll */ #include"ssl_private.h"
/* * reuse vhost keys for asn1 tables where keys are allocated out * of s->process->pool to prevent "leaking" each time we format * a vhost key. since the key is stored in a table with lifetime * of s->process->pool, the key needs to have the same lifetime. * * XXX: probably seems silly to use a hash table with keys and values * being the same, but it is easier than doing a linear search * and will make it easier to remove keys if needed in the future. * also have the problem with apr_array_header_t that if we * underestimate the number of vhost keys when we apr_array_make(), * the array will get resized when we push past the initial number * of elts. this resizing in the s->process->pool means "leaking" * since apr_array_push() will apr_alloc arr->nalloc * 2 elts, * leaving the original arr->elts to waste.
*/ staticconstchar *asn1_table_vhost_key(SSLModConfigRec *mc, apr_pool_t *p, constchar *id, int i)
{ /* 'p' pool used here is cleared on restarts (or sooner) */ char *key = apr_psprintf(p, "%s:%d", id, i); void *keyptr = apr_hash_get(mc->tVHostKeys, key,
APR_HASH_KEY_STRING);
if (!keyptr) { /* make a copy out of s->process->pool */
keyptr = apr_pstrdup(mc->pPool, key);
apr_hash_set(mc->tVHostKeys, keyptr,
APR_HASH_KEY_STRING, keyptr);
}
/* * if the private key is encrypted and SSLPassPhraseDialog * is configured to "builtin" it isn't possible to prompt for * a password after httpd has detached from the tty. * in this case if we already have a private key and the * file name/mtime hasn't changed, then reuse the existing key. * we also reuse existing private keys that were encrypted for * exec: and pipe: dialogs to minimize chances to snoop the * password. that and pipe: dialogs might prompt the user * for password, which on win32 for example could happen 4 * times at startup. twice for each child and twice within * each since apache "restarts itself" on startup. * of course this will not work for the builtin dialog if * the server was started without LoadModule ssl_module * configured, then restarted with it configured. * but we fall through with a chance of success if the key * is not encrypted or can be handled via exec or pipe dialog. * and in the case of fallthrough, pkey_mtime and isatty() * are used to give a better idea as to what failed.
*/ if (pkey_mtime) {
ssl_asn1_t *asn1 = ssl_asn1_table_get(mc->tPrivateKey, key_id); if (asn1 && (asn1->source_mtime == pkey_mtime)) {
ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(02575) "Reusing existing private key from %s on restart",
ppcb_arg.pkey_file); return APR_SUCCESS;
}
}
for (;;) { /* * Try to read the private key file with the help of * the callback function which serves the pass * phrases to OpenSSL
*/
ppcb_arg.cpPassPhraseCur = NULL;
/* Ensure that the error stack is empty; some SSL * functions will fail spuriously if the error stack
* is not empty. */
ERR_clear_error();
pPrivateKey = modssl_read_privatekey(ppcb_arg.pkey_file,
ssl_pphrase_Handle_CB, &ppcb_arg); /* If the private key was successfully read, nothing more to
do here. */ if (pPrivateKey != NULL) break;
/* * when we have more remembered pass phrases * try to reuse these first.
*/ if (ppcb_arg.nPassPhraseCur < nPassPhrase) {
ppcb_arg.nPassPhraseCur++; continue;
}
/* * else it's not readable and we have no more * remembered pass phrases. Then this has to mean * that the callback function popped up the dialog * but a wrong pass phrase was entered. We give the * user (but not the dialog program) a few more * chances...
*/ #ifndef WIN32 if ((sc->server->pphrase_dialog_type == SSL_PPTYPE_BUILTIN
|| sc->server->pphrase_dialog_type == SSL_PPTYPE_PIPE) #else if (sc->server->pphrase_dialog_type == SSL_PPTYPE_PIPE #endif
&& ppcb_arg.cpPassPhraseCur != NULL
&& nPassPhraseRetry < BUILTIN_DIALOG_RETRIES ) {
apr_file_printf(writetty, "Apache:mod_ssl:Error: Pass phrase incorrect " "(%d more retr%s permitted).\n",
(BUILTIN_DIALOG_RETRIES-nPassPhraseRetry),
(BUILTIN_DIALOG_RETRIES-nPassPhraseRetry) == 1 ? "y" : "ies");
nPassPhraseRetry++; if (nPassPhraseRetry > BUILTIN_DIALOG_BACKOFF)
apr_sleep((nPassPhraseRetry-BUILTIN_DIALOG_BACKOFF)
* 5 * APR_USEC_PER_SEC); continue;
} #ifdef WIN32 if (sc->server->pphrase_dialog_type == SSL_PPTYPE_BUILTIN) {
ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(02577) "Init: SSLPassPhraseDialog builtin is not " "supported on Win32 (key file " "%s)", ppcb_arg.pkey_file); return ssl_die(s);
} #endif/* WIN32 */
/* * Ok, anything else now means a fatal error.
*/ if (ppcb_arg.cpPassPhraseCur == NULL) { if (ppcb_arg.nPassPhraseDialogCur && pkey_mtime &&
!isatty(fileno(stdout))) /* XXX: apr_isatty() */
{
ap_log_error(APLOG_MARK, APLOG_ERR, 0,
s, APLOGNO(02578) "Init: Unable to read pass phrase " "[Hint: key introduced or changed " "before restart?]");
ssl_log_ssl_error(SSLLOG_MARK, APLOG_ERR, s);
} else {
ap_log_error(APLOG_MARK, APLOG_ERR, 0,
s, APLOGNO(02579) "Init: Private key not found");
ssl_log_ssl_error(SSLLOG_MARK, APLOG_ERR, s);
} if (writetty) {
apr_file_printf(writetty, "Apache:mod_ssl:Error: Private key not found.\n");
apr_file_printf(writetty, "**Stopped\n");
}
} else {
ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(02580) "Init: Pass phrase incorrect for key %s",
key_id);
ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s);
/* * Ok, when we have one more pass phrase store it
*/ if (ppcb_arg.cpPassPhraseCur != NULL) {
*(constchar **)apr_array_push(ppcb_arg.aPassPhrase) =
ppcb_arg.cpPassPhraseCur;
nPassPhrase++;
}
/* Cache the private key in the global module configuration so it
* can be used after subsequent reloads. */
asn1 = ssl_asn1_table_set(mc->tPrivateKey, key_id, pPrivateKey);
if (ppcb_arg.nPassPhraseDialogCur != 0) { /* remember mtime of encrypted keys */
asn1->source_mtime = pkey_mtime;
}
/* * Free the private key structure
*/
EVP_PKEY_free(pPrivateKey);
/* * Let the user know when we're successful.
*/ if ((ppcb_arg.nPassPhraseDialog > 0) &&
(ppcb_arg.cpPassPhraseCur != NULL)) { if (writetty) {
apr_file_printf(writetty, "\n" "OK: Pass Phrase Dialog successful.\n");
}
}
/* Close the pipes if they were opened
*/ if (readtty) {
apr_file_close(readtty);
apr_file_close(writetty);
readtty = writetty = NULL;
}
return APR_SUCCESS;
}
static apr_status_t ssl_pipe_child_create(apr_pool_t *p, constchar *progname)
{ /* Child process code for 'ErrorLog "|..."'; * may want a common framework for this, since I expect it will * be common for other foo-loggers to want this sort of thing...
*/
apr_status_t rc;
apr_procattr_t *procattr;
apr_proc_t *procnew;
int ssl_pphrase_Handle_CB(char *buf, int bufsize, int verify, void *srv)
{
pphrase_cb_arg_t *ppcb_arg = (pphrase_cb_arg_t *)srv;
SSLSrvConfigRec *sc = mySrvConfig(ppcb_arg->s); char *cpp; int len = -1;
/* * When remembered pass phrases are available use them...
*/ if ((cpp = pphrase_array_get(ppcb_arg->aPassPhrase,
ppcb_arg->nPassPhraseCur)) != NULL) {
apr_cpystrn(buf, cpp, bufsize);
len = strlen(buf); return len;
}
/* * Builtin or Pipe dialog
*/ if (sc->server->pphrase_dialog_type == SSL_PPTYPE_BUILTIN
|| sc->server->pphrase_dialog_type == SSL_PPTYPE_PIPE) { char *prompt; int i;
if (sc->server->pphrase_dialog_type == SSL_PPTYPE_PIPE) { if (!readtty) {
ap_log_error(APLOG_MARK, APLOG_INFO, 0, ppcb_arg->s,
APLOGNO(01965) "Init: Creating pass phrase dialog pipe child " "'%s'", sc->server->pphrase_dialog_path); if (ssl_pipe_child_create(ppcb_arg->p,
sc->server->pphrase_dialog_path)
!= APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, ppcb_arg->s,
APLOGNO(01966) "Init: Failed to create pass phrase pipe '%s'",
sc->server->pphrase_dialog_path);
PEMerr(PEM_F_PEM_DEF_CALLBACK,
PEM_R_PROBLEMS_GETTING_PASSWORD);
memset(buf, 0, (unsignedint)bufsize); return (-1);
}
}
ap_log_error(APLOG_MARK, APLOG_INFO, 0, ppcb_arg->s, APLOGNO(01967) "Init: Requesting pass phrase via piped dialog");
} else { /* sc->server->pphrase_dialog_type == SSL_PPTYPE_BUILTIN */ #ifdef WIN32
PEMerr(PEM_F_PEM_DEF_CALLBACK, PEM_R_PROBLEMS_GETTING_PASSWORD);
memset(buf, 0, (unsignedint)bufsize); return (-1); #else /* * stderr has already been redirected to the error_log. * rather than attempting to temporarily rehook it to the terminal, * we print the prompt to stdout before EVP_read_pw_string turns * off tty echo
*/
apr_file_open_stdout(&writetty, ppcb_arg->p);
/* * The first time display a header to inform the user about what * program he actually speaks to, which module is responsible for * this terminal dialog and why to the hell he has to enter * something...
*/ if (ppcb_arg->nPassPhraseDialog == 1) {
apr_file_printf(writetty, "%s mod_ssl (Pass Phrase Dialog)\n",
AP_SERVER_BASEVERSION);
apr_file_printf(writetty, "Some of your private key files are encrypted for security reasons.\n");
apr_file_printf(writetty, "In order to read them you have to provide the pass phrases.\n");
} if (ppcb_arg->bPassPhraseDialogOnce) {
ppcb_arg->bPassPhraseDialogOnce = FALSE;
apr_file_printf(writetty, "\n");
apr_file_printf(writetty, "Private key %s (%s)\n",
ppcb_arg->key_id, ppcb_arg->pkey_file);
}
/* * Emulate the OpenSSL internal pass phrase dialog * (see crypto/pem/pem_lib.c:def_callback() for details)
*/
prompt = "Enter pass phrase:";
for (;;) {
apr_file_puts(prompt, writetty); if (sc->server->pphrase_dialog_type == SSL_PPTYPE_PIPE) {
i = pipe_get_passwd_cb(buf, bufsize, "", FALSE);
} else { /* sc->server->pphrase_dialog_type == SSL_PPTYPE_BUILTIN */
i = EVP_read_pw_string(buf, bufsize, "", FALSE);
} if (i != 0) {
PEMerr(PEM_F_PEM_DEF_CALLBACK,PEM_R_PROBLEMS_GETTING_PASSWORD);
memset(buf, 0, (unsignedint)bufsize); return (-1);
}
len = strlen(buf); if (len < 1)
apr_file_printf(writetty, "Apache:mod_ssl:Error: Pass phrase empty (needs to be at least 1 character).\n"); else break;
}
}
argv[0] = cmd;
argv[1] = apr_pstrndup(ppcb_arg->p, ppcb_arg->key_id,
idx-1 - ppcb_arg->key_id); if ((i = atoi(idx)) < CERTKEYS_IDX_MAX+1) { /* * For compatibility with existing 2.4.x configurations, use * "RSA", "DSA" and "ECC" strings for the first two/three keys
*/
argv[2] = key_types[i];
} else { /* Four and above: use the integer index */
argv[2] = apr_pstrdup(ppcb_arg->p, idx);
}
argv[3] = NULL;
result = ssl_util_readfilter(ppcb_arg->s, ppcb_arg->p, cmd, argv);
apr_cpystrn(buf, result, bufsize);
len = strlen(buf);
}
/* * Ok, we now have the pass phrase, so give it back
*/
ppcb_arg->cpPassPhraseCur = apr_pstrdup(ppcb_arg->p, buf);
/* * And return its length to OpenSSL...
*/ return (len);
}
/* OpenSSL UI implementation for passphrase entry; largely duplicated * from ssl_pphrase_Handle_CB but adjusted for UI API. TODO: Might be * worth trying to shift pphrase handling over to the UI API
* completely. */ staticint passphrase_ui_open(UI *ui)
{
pphrase_cb_arg_t *ppcb = UI_get0_user_data(ui);
SSLSrvConfigRec *sc = mySrvConfig(ppcb->s);
/* * The first time display a header to inform the user about what * program he actually speaks to, which module is responsible for * this terminal dialog and why to the hell he has to enter * something...
*/ if (ppcb->nPassPhraseDialog == 1) {
apr_file_printf(writetty, "%s mod_ssl (Pass Phrase Dialog)\n",
AP_SERVER_BASEVERSION);
apr_file_printf(writetty, "A pass phrase is required to access the private key.\n");
} if (ppcb->bPassPhraseDialogOnce) {
ppcb->bPassPhraseDialogOnce = FALSE;
apr_file_printf(writetty, "\n");
apr_file_printf(writetty, "Private key %s (%s)\n",
ppcb->key_id, ppcb->pkey_file);
}
}
return 1;
}
staticint passphrase_ui_read(UI *ui, UI_STRING *uis)
{
pphrase_cb_arg_t *ppcb = UI_get0_user_data(ui);
SSLSrvConfigRec *sc = mySrvConfig(ppcb->s); constchar *prompt; int i; int bufsize; int len; char *buf;
staticint passphrase_ui_close(UI *ui)
{ /* * Close the pipes if they were opened
*/ if (readtty) {
apr_file_close(readtty);
apr_file_close(writetty);
readtty = writetty = NULL;
} return 1;
}
/* Tries to load the key and optionally certificate via the ENGINE * API. Returns APR_ENOTIMPL if an ENGINE could not be identified
* loaded from the key name. */ static apr_status_t modssl_load_keypair_engine(server_rec *s, apr_pool_t *pconf,
apr_pool_t *ptemp, constchar *vhostid, constchar *certid, constchar *keyid,
X509 **pubkey,
EVP_PKEY **privkey)
{ constchar *c, *scheme;
ENGINE *e;
UI_METHOD *ui_method = get_passphrase_ui(ptemp);
pphrase_cb_arg_t ppcb;
if (!ENGINE_ctrl_cmd(e, "LOAD_CERT_CTRL", 0, ¶ms, NULL, 1)) {
ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(10136) "Init: Unable to get the certificate");
ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s); return ssl_die(s);
}
*pubkey = params.cert;
}
*privkey = ENGINE_load_private_key(e, keyid, ui_method, &ppcb); if (*privkey == NULL) {
ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(10133) "Init: Unable to get the private key");
ssl_log_ssl_error(SSLLOG_MARK, APLOG_EMERG, s); return ssl_die(s);
}
/* Release the functional reference obtained by ENGINE_init() only
* when after the ENGINE is no longer used. */
apr_pool_cleanup_register(pconf, e, modssl_engine_cleanup, modssl_engine_cleanup);
/* Release the structural reference obtained by ENGINE_by_id()
* immediately. */
ENGINE_free(e);
rv = modssl_load_keypair_engine(s, pconf, ptemp,
vhostid, certid, keyid,
pubkey, privkey); if (rv == APR_SUCCESS) { return rv;
} /* If STORE support is not present, all errors are fatal here; if * STORE is present and the ENGINE could not be loaded, ignore the
* error and fall through to try loading via the STORE API. */ elseif (!MODSSL_HAVE_OPENSSL_STORE || rv != APR_ENOTIMPL) { return ssl_die(s);
}
#endif #if MODSSL_HAVE_OPENSSL_STORE return modssl_load_keypair_store(s, ptemp, vhostid, certid, keyid,
pubkey, privkey); #else
ap_log_error(APLOG_MARK, APLOG_EMERG, 0, s, APLOGNO(10496) "Init: no method for loading keypair for %s (%s | %s)",
vhostid, certid ? certid : "no cert", keyid); return APR_ENOTIMPL; #endif
}
¤ Dauer der Verarbeitung: 0.22 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.