1
0
mirror of https://github.com/postgres/postgres.git synced 2025-06-23 14:01:44 +03:00

Re-allow SSL passphrase prompt at server start, but not thereafter.

Leave OpenSSL's default passphrase collection callback in place during
the first call of secure_initialize() in server startup.  Although that
doesn't work terribly well in daemon contexts, some people feel we should
not break it for anyone who was successfully using it before.  We still
block passphrase demands during SIGHUP, meaning that you can't adjust SSL
configuration on-the-fly if you used a passphrase, but this is no worse
than what it was before commit de41869b6.  And we block passphrase demands
during EXEC_BACKEND reloads; that behavior wasn't useful either, but at
least now it's documented.

Tweak some related log messages for more readability, and avoid issuing
essentially duplicate messages about reload failure caused by a passphrase.

Discussion: https://postgr.es/m/29982.1483412575@sss.pgh.pa.us
This commit is contained in:
Tom Lane
2017-01-04 12:43:52 -05:00
parent 0fad355bec
commit 6667d9a6d7
6 changed files with 72 additions and 53 deletions

View File

@ -78,13 +78,14 @@ static DH *tmp_dh_cb(SSL *s, int is_export, int keylength);
static int ssl_passwd_cb(char *buf, int size, int rwflag, void *userdata);
static int verify_cb(int, X509_STORE_CTX *);
static void info_cb(const SSL *ssl, int type, int args);
static bool initialize_ecdh(SSL_CTX *context, bool failOnError);
static bool initialize_ecdh(SSL_CTX *context, bool isServerStart);
static const char *SSLerrmessage(unsigned long ecode);
static char *X509_NAME_to_cstring(X509_NAME *name);
static SSL_CTX *SSL_context = NULL;
static bool SSL_initialized = false;
static bool ssl_passwd_cb_called = false;
/* ------------------------------------------------------------ */
/* Hardcoded values */
@ -159,12 +160,12 @@ KWbuHn491xNO25CQWMtem80uKw+pTnisBRF/454n1Jnhub144YRBoN8CAQI=\n\
/*
* Initialize global SSL context.
*
* If failOnError is true, report any errors as FATAL (so we don't return).
* Otherwise, log errors at LOG level and return -1 to indicate trouble.
* Returns 0 if OK.
* If isServerStart is true, report any errors as FATAL (so we don't return).
* Otherwise, log errors at LOG level and return -1 to indicate trouble,
* preserving the old SSL state if any. Returns 0 if OK.
*/
int
be_tls_init(bool failOnError)
be_tls_init(bool isServerStart)
{
STACK_OF(X509_NAME) *root_cert_list = NULL;
SSL_CTX *context;
@ -192,7 +193,7 @@ be_tls_init(bool failOnError)
context = SSL_CTX_new(SSLv23_method());
if (!context)
{
ereport(failOnError ? FATAL : LOG,
ereport(isServerStart ? FATAL : LOG,
(errmsg("could not create SSL context: %s",
SSLerrmessage(ERR_get_error()))));
goto error;
@ -205,16 +206,21 @@ be_tls_init(bool failOnError)
SSL_CTX_set_mode(context, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
/*
* Override OpenSSL's default handling of passphrase-protected files.
* If reloading, override OpenSSL's default handling of
* passphrase-protected files, because we don't want to prompt for a
* passphrase in an already-running server. (Not that the default
* handling is very desirable during server start either, but some people
* insist we need to keep it.)
*/
SSL_CTX_set_default_passwd_cb(context, ssl_passwd_cb);
if (!isServerStart)
SSL_CTX_set_default_passwd_cb(context, ssl_passwd_cb);
/*
* Load and verify server's certificate and private key
*/
if (SSL_CTX_use_certificate_chain_file(context, ssl_cert_file) != 1)
{
ereport(failOnError ? FATAL : LOG,
ereport(isServerStart ? FATAL : LOG,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("could not load server certificate file \"%s\": %s",
ssl_cert_file, SSLerrmessage(ERR_get_error()))));
@ -223,7 +229,7 @@ be_tls_init(bool failOnError)
if (stat(ssl_key_file, &buf) != 0)
{
ereport(failOnError ? FATAL : LOG,
ereport(isServerStart ? FATAL : LOG,
(errcode_for_file_access(),
errmsg("could not access private key file \"%s\": %m",
ssl_key_file)));
@ -232,7 +238,7 @@ be_tls_init(bool failOnError)
if (!S_ISREG(buf.st_mode))
{
ereport(failOnError ? FATAL : LOG,
ereport(isServerStart ? FATAL : LOG,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("private key file \"%s\" is not a regular file",
ssl_key_file)));
@ -240,14 +246,14 @@ be_tls_init(bool failOnError)
}
/*
* Refuse to load files owned by users other than us or root.
* Refuse to load key files owned by users other than us or root.
*
* XXX surely we can check this on Windows somehow, too.
*/
#if !defined(WIN32) && !defined(__CYGWIN__)
if (buf.st_uid != geteuid() && buf.st_uid != 0)
{
ereport(failOnError ? FATAL : LOG,
ereport(isServerStart ? FATAL : LOG,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("private key file \"%s\" must be owned by the database user or root",
ssl_key_file)));
@ -270,7 +276,7 @@ be_tls_init(bool failOnError)
if ((buf.st_uid == geteuid() && buf.st_mode & (S_IRWXG | S_IRWXO)) ||
(buf.st_uid == 0 && buf.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)))
{
ereport(failOnError ? FATAL : LOG,
ereport(isServerStart ? FATAL : LOG,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("private key file \"%s\" has group or world access",
ssl_key_file),
@ -279,20 +285,31 @@ be_tls_init(bool failOnError)
}
#endif
/*
* OK, try to load the private key file.
*/
ssl_passwd_cb_called = false;
if (SSL_CTX_use_PrivateKey_file(context,
ssl_key_file,
SSL_FILETYPE_PEM) != 1)
{
ereport(failOnError ? FATAL : LOG,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("could not load private key file \"%s\": %s",
ssl_key_file, SSLerrmessage(ERR_get_error()))));
if (ssl_passwd_cb_called)
ereport(isServerStart ? FATAL : LOG,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("private key file \"%s\" cannot be reloaded because it requires a passphrase",
ssl_key_file)));
else
ereport(isServerStart ? FATAL : LOG,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("could not load private key file \"%s\": %s",
ssl_key_file, SSLerrmessage(ERR_get_error()))));
goto error;
}
if (SSL_CTX_check_private_key(context) != 1)
{
ereport(failOnError ? FATAL : LOG,
ereport(isServerStart ? FATAL : LOG,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("check of private key failed: %s",
SSLerrmessage(ERR_get_error()))));
@ -306,13 +323,13 @@ be_tls_init(bool failOnError)
SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
/* set up ephemeral ECDH keys */
if (!initialize_ecdh(context, failOnError))
if (!initialize_ecdh(context, isServerStart))
goto error;
/* set up the allowed cipher list */
if (SSL_CTX_set_cipher_list(context, SSLCipherSuites) != 1)
{
ereport(failOnError ? FATAL : LOG,
ereport(isServerStart ? FATAL : LOG,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("could not set the cipher list (no valid ciphers available)")));
goto error;
@ -330,7 +347,7 @@ be_tls_init(bool failOnError)
if (SSL_CTX_load_verify_locations(context, ssl_ca_file, NULL) != 1 ||
(root_cert_list = SSL_load_client_CA_file(ssl_ca_file)) == NULL)
{
ereport(failOnError ? FATAL : LOG,
ereport(isServerStart ? FATAL : LOG,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("could not load root certificate file \"%s\": %s",
ssl_ca_file, SSLerrmessage(ERR_get_error()))));
@ -366,7 +383,7 @@ be_tls_init(bool failOnError)
}
else
{
ereport(failOnError ? FATAL : LOG,
ereport(isServerStart ? FATAL : LOG,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("could not load SSL certificate revocation list file \"%s\": %s",
ssl_crl_file, SSLerrmessage(ERR_get_error()))));
@ -1071,19 +1088,16 @@ tmp_dh_cb(SSL *s, int is_export, int keylength)
*
* If OpenSSL is told to use a passphrase-protected server key, by default
* it will issue a prompt on /dev/tty and try to read a key from there.
* That's completely no good for a postmaster SIGHUP cycle, not to mention
* SSL context reload in an EXEC_BACKEND postmaster child. So override it
* with this dummy function that just returns an empty passphrase,
* guaranteeing failure. Later we might think about collecting a passphrase
* at server start and feeding it to OpenSSL repeatedly, but we'd still
* need this callback for that.
* That's no good during a postmaster SIGHUP cycle, not to mention SSL context
* reload in an EXEC_BACKEND postmaster child. So override it with this dummy
* function that just returns an empty passphrase, guaranteeing failure.
*/
static int
ssl_passwd_cb(char *buf, int size, int rwflag, void *userdata)
{
ereport(LOG,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("server's private key file requires a passphrase")));
/* Set flag to change the error message we'll report */
ssl_passwd_cb_called = true;
/* And return empty string */
Assert(size > 0);
buf[0] = '\0';
return 0;
@ -1151,7 +1165,7 @@ info_cb(const SSL *ssl, int type, int args)
}
static bool
initialize_ecdh(SSL_CTX *context, bool failOnError)
initialize_ecdh(SSL_CTX *context, bool isServerStart)
{
#ifndef OPENSSL_NO_ECDH
EC_KEY *ecdh;
@ -1160,7 +1174,7 @@ initialize_ecdh(SSL_CTX *context, bool failOnError)
nid = OBJ_sn2nid(SSLECDHCurve);
if (!nid)
{
ereport(failOnError ? FATAL : LOG,
ereport(isServerStart ? FATAL : LOG,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("ECDH: unrecognized curve name: %s", SSLECDHCurve)));
return false;
@ -1169,7 +1183,7 @@ initialize_ecdh(SSL_CTX *context, bool failOnError)
ecdh = EC_KEY_new_by_curve_name(nid);
if (!ecdh)
{
ereport(failOnError ? FATAL : LOG,
ereport(isServerStart ? FATAL : LOG,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("ECDH: could not create key")));
return false;