diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c index b53c2b813e..a65f920343 100644 --- a/src/backend/libpq/be-secure-openssl.c +++ b/src/backend/libpq/be-secure-openssl.c @@ -45,6 +45,9 @@ #include "tcop/tcopprot.h" #include "utils/memutils.h" +/* default init hook can be overridden by a shared library */ +static void default_openssl_tls_init(SSL_CTX *context, bool isServerStart); +openssl_tls_init_hook_typ openssl_tls_init_hook = default_openssl_tls_init; static int my_sock_read(BIO *h, char *buf, int size); static int my_sock_write(BIO *h, const char *buf, int size); @@ -117,27 +120,10 @@ be_tls_init(bool isServerStart) SSL_CTX_set_mode(context, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); /* - * Set password callback + * Call init hook (usually to set password callback) */ - if (isServerStart) - { - if (ssl_passphrase_command[0]) - SSL_CTX_set_default_passwd_cb(context, ssl_external_passwd_cb); - } - else - { - if (ssl_passphrase_command[0] && ssl_passphrase_command_supports_reload) - SSL_CTX_set_default_passwd_cb(context, ssl_external_passwd_cb); - else + (* openssl_tls_init_hook)(context, isServerStart); - /* - * If reloading and no external command is configured, override - * OpenSSL's default handling of passphrase-protected files, - * because we don't want to prompt for a passphrase in an - * already-running server. - */ - SSL_CTX_set_default_passwd_cb(context, dummy_ssl_passwd_cb); - } /* used by the callback */ ssl_is_server_start = isServerStart; @@ -1338,3 +1324,27 @@ ssl_protocol_version_to_openssl(int v) return -1; } + + +static void +default_openssl_tls_init(SSL_CTX *context, bool isServerStart) +{ + if (isServerStart) + { + if (ssl_passphrase_command[0]) + SSL_CTX_set_default_passwd_cb(context, ssl_external_passwd_cb); + } + else + { + if (ssl_passphrase_command[0] && ssl_passphrase_command_supports_reload) + SSL_CTX_set_default_passwd_cb(context, ssl_external_passwd_cb); + else + /* + * If reloading and no external command is configured, override + * OpenSSL's default handling of passphrase-protected files, + * because we don't want to prompt for a passphrase in an + * already-running server. + */ + SSL_CTX_set_default_passwd_cb(context, dummy_ssl_passwd_cb); + } +} diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 2b9ab32293..73d278f3b2 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -972,17 +972,6 @@ PostmasterMain(int argc, char *argv[]) */ LocalProcessControlFile(false); - /* - * Initialize SSL library, if specified. - */ -#ifdef USE_SSL - if (EnableSSL) - { - (void) secure_initialize(true); - LoadedSSL = true; - } -#endif - /* * Register the apply launcher. Since it registers a background worker, * it needs to be called before InitializeMaxBackends(), and it's probably @@ -996,6 +985,17 @@ PostmasterMain(int argc, char *argv[]) */ process_shared_preload_libraries(); + /* + * Initialize SSL library, if specified. + */ +#ifdef USE_SSL + if (EnableSSL) + { + (void) secure_initialize(true); + LoadedSSL = true; + } +#endif + /* * Now that loadable modules have had their chance to register background * workers, calculate MaxBackends. diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h index 82e57afc64..ee57fdc301 100644 --- a/src/include/libpq/libpq-be.h +++ b/src/include/libpq/libpq-be.h @@ -287,6 +287,10 @@ extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len); extern char *be_tls_get_certificate_hash(Port *port, size_t *len); #endif +/* init hook for SSL, the default sets the password callback if appropriate */ +typedef void(* openssl_tls_init_hook_typ)(SSL_CTX *context, bool isServerStart); +extern openssl_tls_init_hook_typ openssl_tls_init_hook; + #endif /* USE_SSL */ #ifdef ENABLE_GSS diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile index b2eaef3bff..5f975ebcba 100644 --- a/src/test/modules/Makefile +++ b/src/test/modules/Makefile @@ -25,4 +25,9 @@ SUBDIRS = \ unsafe_tests \ worker_spi +ifeq ($(with_openssl),yes) +SUBDIRS += ssl_passphrase_callback +endif + + $(recurse) diff --git a/src/test/modules/ssl_passphrase_callback/.gitignore b/src/test/modules/ssl_passphrase_callback/.gitignore new file mode 100644 index 0000000000..1dbadf7baf --- /dev/null +++ b/src/test/modules/ssl_passphrase_callback/.gitignore @@ -0,0 +1 @@ +tmp_check diff --git a/src/test/modules/ssl_passphrase_callback/Makefile b/src/test/modules/ssl_passphrase_callback/Makefile new file mode 100644 index 0000000000..e2d19f131a --- /dev/null +++ b/src/test/modules/ssl_passphrase_callback/Makefile @@ -0,0 +1,24 @@ +# ssl_passphrase Makefile + +export with_openssl + +MODULE_big = ssl_passphrase_func +OBJS = ssl_passphrase_func.o $(WIN32RES) +PGFILEDESC = "callback function to provide a passphrase" + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = src/test/modules/ssl_passphrase_callback +top_builddir = ../../../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif + +check: prove-check + +prove-check: ssl_passphrase_func$(DLSUFFIX) | temp-install + @echo running prove ... + $(prove_check) diff --git a/src/test/modules/ssl_passphrase_callback/server.crt b/src/test/modules/ssl_passphrase_callback/server.crt new file mode 100644 index 0000000000..b3c4be4fa5 --- /dev/null +++ b/src/test/modules/ssl_passphrase_callback/server.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDCTCCAfGgAwIBAgIUfHgPLNys4V0d0cWrzRHqfs91LFMwDQYJKoZIhvcNAQEL +BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTIwMDMyMTE0MDM1OVoXDTQ3MDgw +NzE0MDM1OVowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEA2j0PZwmeahBC7QpG7i9/VUVJrLzy+b8oVaqZUO6nlPbY +wuPISYTO/jqc0XDfs/Gb0kccDJ6bPfNfvSnRTG1omE6OO9YjR0u3296l4bWAmYVq +q4SesgQmm1Wy8ODNpeGaoBUwR51OB/gFHFjUlqAjRwOmrTCbDiAsLt7e+cx+W26r +2SrJIweiSJsqaQsMMaqlY2qpHnYgWfqRUTqwXqlno0dXuqBt+KKgqeHMY3w3XS51 +8roOI0+Q9KWsexL/aYnLwMRsHRMZcthhzTK6HD/OrLh9CxURImr4ed9TtsNiZltA +KqLTeGbtS1D2AvFqJU8n5DvtU+26wDrHu6pEM3kSJQIDAQABo1MwUTAdBgNVHQ4E +FgQUkkfa08hDnxYs1UjG2ydCBJs1b2AwHwYDVR0jBBgwFoAUkkfa08hDnxYs1UjG +2ydCBJs1b2AwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAjsJh +p4tCopCA/Pvxupv3VEwGJ+nbH7Zg/hp+o2IWuHBOK1qrkyXBv34h/69bRnWZ5UFV +HxQwL7CjNtjZu9SbpKkaHbZXPWANC9fbPKdBz9fAEwunf33KbZe3dPv/7xbJirMz +e+j5V0LE0Spkr/p89LipXfjGw0jLC8VRTx/vKPnmbiBsCKw5SQKh3w7CcBx84Y6q +Nc27WQ8ReR4W4X1zHGN6kEV4H+yPN2Z9OlSixTiSNvr2mtJQsZa7gK7Wwfm79RN7 +5Kf3l8b6e2BToJwLorpK9mvu41NtwRzl4UoJ1BFJDyhMplFMd8RcwTW6yT2biOFC +lYCajcBoms3IiyqBog== +-----END CERTIFICATE----- diff --git a/src/test/modules/ssl_passphrase_callback/server.key b/src/test/modules/ssl_passphrase_callback/server.key new file mode 100644 index 0000000000..1475007c73 --- /dev/null +++ b/src/test/modules/ssl_passphrase_callback/server.key @@ -0,0 +1,30 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,DB0E7068D4DCE79FFE63C95B8D8F7CEA + +Y4uvnlWX/kyulqsmt8aWI55vKFdfJL4wEZItL8ZKlQFuZuxC9w0OworyjTdqO38R +v9hwnetZBDgK8kEv6U7wR58mTfwHHCGuxYgSiPZtiW7btS4zu16ePdh8oBEzCxjW +ALrCFt7uvRu5h2AWy/4BgV4gLNVPNB+lJFABtUoiSnUDr7+bcx7UjZ4An6HriGxC +Kg/N1pKjT/xiKOy+yHtrp1Jih5HYDE4i99jPtMuTROf8Uyz7ibyrdd/E7QNvANQN +Cmw4I4Xk4hZ68F0iGU0C0wLND3pWbeYPKorpo3PkI4Du+Aqlg15ae5u8CtU3fXGJ +mq4/qLGAi1sr/45f5P5a3Q8BQpKkCmGopXMaFYOOiaf3YYgD1eVOxLhsCWqUB+O8 +ygcTNRCoKhzY+ULComXp880J3fFk5b92g4Hm1PAO42uDKzhWSnrmCBJ7ynXvnEc+ +JqhiE8Obrp6FBIHvfN26JtHcXTd/bgUMXSh7AXjsotfvPPV0URve9JJG+RnwckeT +K3AYDOQK/lbqDGliNqHg1WiMSA2oHSqDhUMB0Sm0jh6+jxCQlsmSDvPvJfWRo5wY +zbZZZARQnFUaHa9CZVdFxbaPGhYU6vAwxDqi42osSJEdf68Gy2KVXcelqpU/2dKk +aHfTgAWOsajbgt9p+0369TeZb39+zLODdDJnvZYiu1pTASHP5VrJ2xHhu5zOdjXm +GafYiPwYBM280wkIVQ0HsTX7BViU2R/7W3FqflXgQvBiraVQVwHyaX4bOU1a3rzg +emHNLTCpRamT0i/D0tkEPgS42bWSVi9ko5Mn9yb+qToBjAOLVUOAOs9Bv3qxawhI +XFbBDZ7DS59l2yV6eQkrG7DUCLDf4dv4WZeBnhrPe/Jg8HKcsKcJYV3cejZh8sgu +XHeCU50+jpJDfTZVPW3TjZWmrTqStGwF1UFpj+tTsTcX+OHAY/shFs3bBZulAsMy +5UWZWzyWHMWr/wbxW7dbhTb1gNmOgpQQz9dunSgcZ8umzSGLa0ZGmnQj9P/kZkQA +RenuswH5O7CK/MDmf3J6svwyLt/jULmH26MZTcNu7igT6dj3VMSwkoQQaaQdtmzb +glzN3uqf8qM+CEjV8dxlt8fv6KJV7gvoYfPAz+1pp5DVJBmRo/+b4e/d4QTV9iWS +ScBYdonc9WXcrjmExX9+Wf/K/IKfLnKLIi2MZ3pwr1n7yY+dMeF6iREYSjFVIpZd +MH3G9/SxTrqR7X/eHjwdv1UupYYyaDag8wpVn1RMCb0xYqh2/QP1k0pQycckL0WQ +lieXibEuQhV/heXcqt83G6pGqLImc6YPYU46jdGpPIMyOK+ZSqJTHUWHfRMQTIMz +varR2M3uhHvwUFzmvjLh/o6I3r0a0Rl1MztpYfjBV6MS4BKYfraWZ0kxCyV+e6tz +O7vD0P5W2qm6b89Md3nqjUcbOM8AojcfBl3xpQrpSdgJ25YJBoJ9L2I2pIMNCK/x +yDNEJl7yP87fdHfXZm2VoUXclDUYHyNys9Rtv9NSr+VNkIMcqrCHEgpAxwQQ5NsO +/vOZe3wjhXXLyRO7Nh5W8jojw3xcb9c9avFUWUvM2BaS4vEYcItUoF4QuHohrCwk +-----END RSA PRIVATE KEY----- diff --git a/src/test/modules/ssl_passphrase_callback/ssl_passphrase_func.c b/src/test/modules/ssl_passphrase_callback/ssl_passphrase_func.c new file mode 100644 index 0000000000..c95cb50945 --- /dev/null +++ b/src/test/modules/ssl_passphrase_callback/ssl_passphrase_func.c @@ -0,0 +1,88 @@ +/*------------------------------------------------------------------------- + * + * ssl_passphrase_func.c + * + * Loadable PostgreSQL module fetch an ssl passphrase for the server cert. + * instead of calling an external program. This implementation just hands + * back the configured password rot13'd. + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include +#include + +#include "libpq/libpq.h" +#include "libpq/libpq-be.h" +#include "utils/guc.h" + +PG_MODULE_MAGIC; + +void _PG_init(void); +void _PG_fini(void); + +static char *ssl_passphrase = NULL; + +/* callback function */ +static int rot13_passphrase(char *buf, int size, int rwflag, void *userdata); +/* hook function to set the callback */ +static void set_rot13(SSL_CTX *context, bool isServerStart); +/* + * Module load callback + */ +void +_PG_init(void) +{ + /* Define custom GUC variable. */ + DefineCustomStringVariable("ssl_passphrase.passphrase", + "passphrase before transformation", + NULL, + &ssl_passphrase, + NULL, + PGC_SIGHUP, + 0, /* no flags required */ + NULL, + NULL, + NULL); + if (ssl_passphrase) + openssl_tls_init_hook = set_rot13; +} + +void +_PG_fini(void) +{ + /* do nothing yet */ +} + +static void +set_rot13(SSL_CTX *context, bool isServerStart) +{ + /* warn if the user has set ssl_passphrase_command */ + if(ssl_passphrase_command[0]) + ereport(WARNING, + (errmsg("ssl_passphrase_command setting ignored by ssl_passphrase_func module"))); + + SSL_CTX_set_default_passwd_cb(context, rot13_passphrase); +} + +static int +rot13_passphrase(char *buf, int size, int rwflag, void *userdata) +{ + + Assert(ssl_passphrase != NULL); + StrNCpy(buf, ssl_passphrase, size); + for (char *p = buf; *p; p++) + { + char c = *p; + + if ((c >= 'a' && c <= 'm') || (c >= 'A' && c <= 'M')) + *p = c + 13; + else if ((c >= 'n' && c <= 'z') || (c >= 'N' && c <= 'Z')) + *p = c - 13; + } + + return strlen(buf); + +} diff --git a/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl b/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl new file mode 100644 index 0000000000..c052d72f9c --- /dev/null +++ b/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl @@ -0,0 +1,80 @@ +use strict; +use warnings; + +use File::Copy; + +use TestLib; +use Test::More; +use PostgresNode; + +unless (($ENV{with_openssl} || 'no') eq 'yes') +{ + plan skip_all => 'SSL not supported by this build'; +} + +my $clearpass = "FooBaR1"; +my $rot13pass = "SbbOnE1"; + +# self-signed cert was generated like this: +# system('openssl req -new -x509 -days 10000 -nodes -out server.crt -keyout server.ckey -subj "/CN=localhost"'); +# add the cleartext passphrase to the key, remove the unprotected key +# system("openssl rsa -aes256 -in server.ckey -out server.key -passout pass:$clearpass"); +# unlink "server.ckey"; + + +my $node = get_new_node('main'); +$node->init; +$node->append_conf('postgresql.conf', + "ssl_passphrase.passphrase = '$rot13pass'"); +$node->append_conf('postgresql.conf', + "shared_preload_libraries = 'ssl_passphrase_func'"); +$node->append_conf('postgresql.conf', "listen_addresses = 'localhost'"); +$node->append_conf('postgresql.conf', "ssl = 'on'"); + +my $ddir = $node->data_dir; + +# install certificate and protected key +copy("server.crt", $ddir); +copy("server.key", $ddir); +chmod 0600, "$ddir/server.key"; + +$node->start; + +# if the server is running we must have successfully transformed the passphrase +ok(-e "$ddir/postmaster.pid", "postgres started"); + +$node->stop('fast'); + +# should get a warning if ssl_passphrase_command is set +my $log = $node->rotate_logfile(); + +$node->append_conf('postgresql.conf', + "ssl_passphrase_command = 'echo spl0tz'"); + +$node->start; + +$node->stop('fast'); + +my $log_contents = slurp_file($log); + +like( + $log_contents, + qr/WARNING.*ssl_passphrase_command setting ignored by ssl_passphrase_func module/, + "ssl_passphrase_command set warning"); + +# set the wrong passphrase +$node->append_conf('postgresql.conf', "ssl_passphrase.passphrase = 'blurfl'"); + +# try to start the server again +my $ret = TestLib::system_log('pg_ctl', '-D', $node->data_dir, '-l', + $node->logfile, 'start'); + + +# with a bad passphrase the server should not start +ok($ret, "pg_ctl fails with bad passphrase"); +ok(!-e "$ddir/postmaster.pid", "postgres not started with bad passphrase"); + +# just in case +$node->stop('fast'); + +done_testing(); diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm index 636428b044..39709f20e6 100644 --- a/src/tools/msvc/Mkvcbuild.pm +++ b/src/tools/msvc/Mkvcbuild.pm @@ -430,7 +430,7 @@ sub mkvcbuild if (!$solution->{options}->{openssl}) { - push @contrib_excludes, 'sslinfo'; + push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback'; } if (!$solution->{options}->{uuid})