diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml index 38f561886a..130c386462 100644 --- a/doc/src/sgml/runtime.sgml +++ b/doc/src/sgml/runtime.sgml @@ -2159,8 +2159,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433 - The private key cannot be protected with a passphrase, as there is no - way to supply the passphrase to the server. + If the private key is protected with a passphrase, the + server will prompt for the passphrase and will not start until it has + been entered. + Using a passphrase also disables the ability to change the server's SSL + configuration without a server restart. + Furthermore, passphrase-protected private keys cannot be used at all + on Windows. @@ -2293,9 +2298,9 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433 If an error in these files is detected at server start, the server will refuse to start. But if an error is detected during a configuration - reload, the files are ignored and the old values continue to be used. - On Windows systems, if an error in these - files is detected at backend start, that backend will be unable to + reload, the files are ignored and the old SSL configuration continues to + be used. On Windows systems, if an error in + these files is detected at backend start, that backend will be unable to establish an SSL connection. In all these cases, the error condition is reported in the server log. @@ -2314,8 +2319,8 @@ openssl req -new -text -out server.req you enter the local host name as Common Name; the challenge password can be left blank. The program will generate a key that is passphrase protected; it will not accept a passphrase that is less - than four characters long. To remove the passphrase again (as you must), - next run the commands: + than four characters long. To remove the passphrase again (as you must + if you want automatic start-up of the server), next run the commands: openssl rsa -in privkey.pem -out server.key rm privkey.pem diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c index 07341ff696..44c84a7869 100644 --- a/src/backend/libpq/be-secure-openssl.c +++ b/src/backend/libpq/be-secure-openssl.c @@ -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; diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c index e5ee974c27..785dadb6c2 100644 --- a/src/backend/libpq/be-secure.c +++ b/src/backend/libpq/be-secure.c @@ -65,15 +65,15 @@ bool SSLPreferServerCiphers; /* * Initialize global 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 -secure_initialize(bool failOnError) +secure_initialize(bool isServerStart) { #ifdef USE_SSL - return be_tls_init(failOnError); + return be_tls_init(isServerStart); #else return 0; #endif diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 21066e7bb7..5be30b0ee1 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -2507,11 +2507,11 @@ SIGHUP_handler(SIGNAL_ARGS) /* Reload authentication config files too */ if (!load_hba()) ereport(LOG, - (errmsg("pg_hba.conf not reloaded"))); + (errmsg("pg_hba.conf was not reloaded"))); if (!load_ident()) ereport(LOG, - (errmsg("pg_ident.conf not reloaded"))); + (errmsg("pg_ident.conf was not reloaded"))); #ifdef USE_SSL /* Reload SSL configuration as well */ @@ -2521,7 +2521,7 @@ SIGHUP_handler(SIGNAL_ARGS) LoadedSSL = true; else ereport(LOG, - (errmsg("SSL context not reloaded"))); + (errmsg("SSL configuration was not reloaded"))); } else { @@ -4772,7 +4772,7 @@ SubPostmasterMain(int argc, char *argv[]) LoadedSSL = true; else ereport(LOG, - (errmsg("SSL context could not be reloaded in child process"))); + (errmsg("SSL configuration could not be loaded in child process"))); } #endif diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h index b7582d6126..79d38cedd7 100644 --- a/src/include/libpq/libpq-be.h +++ b/src/include/libpq/libpq-be.h @@ -199,7 +199,7 @@ typedef struct Port * These functions are implemented by the glue code specific to each * SSL implementation (e.g. be-secure-openssl.c) */ -extern int be_tls_init(bool failOnError); +extern int be_tls_init(bool isServerStart); extern void be_tls_destroy(void); extern int be_tls_open_server(Port *port); extern void be_tls_close(Port *port); diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h index 46c5b726ba..538066e106 100644 --- a/src/include/libpq/libpq.h +++ b/src/include/libpq/libpq.h @@ -81,7 +81,7 @@ extern char *ssl_key_file; extern char *ssl_ca_file; extern char *ssl_crl_file; -extern int secure_initialize(bool failOnError); +extern int secure_initialize(bool isServerStart); extern bool secure_loaded_verify_locations(void); extern void secure_destroy(void); extern int secure_open_server(Port *port);