On Sun, Dec 8, 2019 at 9:02 AM Tom Lane <t...@sss.pgh.pa.us> wrote: > > Andrew Dunstan <andrew.duns...@2ndquadrant.com> writes: > > Well that pretty much brings us back to the patch as submitted :-) > > Yeah, pretty nearly. Taking a quick look over the v3 patch, my > only quibble is that it doesn't provide any convenient way for the > external module to make decisions about how to interact with > ssl_passphrase_command --- in particular, if it would like to allow > that to take precedence, it can't because there's no way for it to > invoke the static function ssl_external_passwd_cb. > > But rather than expose that globally, maybe the theory ought to be > "set up the state as we'd normally do, then let loadable modules > choose to override it". So I'm tempted to propose a hook function > with the signature > > void openssl_tls_init_hook(SSL_CTX *context, bool isServerStart); > > and invoke that somewhere in be_tls_init --- maybe fairly late, > so that it can override other settings if it wants, not only the > SSL_CTX_set_default_passwd_cb setting. >
Not sure if the placement is what you want, but maybe something like this? cheers andrew -- Andrew Dunstan https://www.2ndQuadrant.com PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c index 0cc59f1be1..af9f1f71d5 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); @@ -116,27 +119,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; @@ -1310,3 +1296,27 @@ ssl_protocol_version_to_openssl(int v, const char *guc_name, int loglevel) GetConfigOption(guc_name, false, false)))); 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 7a92dac525..9e2777c896 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..90f80bb33a 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 passwor 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/ssl_passphrase_func.c b/src/test/modules/ssl_passphrase_callback/ssl_passphrase_func.c new file mode 100644 index 0000000000..0964ba3508 --- /dev/null +++ b/src/test/modules/ssl_passphrase_callback/ssl_passphrase_func.c @@ -0,0 +1,82 @@ +/*------------------------------------------------------------------------- + * + * 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 <float.h> +#include <stdio.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) +{ + 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..715d635c58 --- /dev/null +++ b/src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl @@ -0,0 +1,67 @@ + +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 3650 -nodes -text -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 +move("server.crt", $ddir); +move("server.key", $ddir); +chmod 0600, "$ddir/server.key"; + +$node->start; + +# if the server is running we must had successfully transformed the passphrase +ok(-e "$ddir/postmaster.pid","postgres started"); + +$node->stop('fast'); + +# 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 c735d529ca..2eb0311a14 100644 --- a/src/tools/msvc/Mkvcbuild.pm +++ b/src/tools/msvc/Mkvcbuild.pm @@ -429,7 +429,7 @@ sub mkvcbuild if (!$solution->{options}->{openssl}) { - push @contrib_excludes, 'sslinfo'; + push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback'; } if (!$solution->{options}->{uuid})