From 896fcdb230e729652d37270c8606ccdc45212f0d Mon Sep 17 00:00:00 2001 From: Andrew Dunstan Date: Wed, 25 Mar 2020 17:13:17 -0400 Subject: [PATCH] Provide a TLS init hook The default hook function sets the default password callback function. In order to allow preloaded libraries to have an opportunity to override the default, TLS initialization if now delayed slightly until after shared preloaded libraries have been loaded. A test module is provided which contains a trivial example that decodes an obfuscated password for an SSL certificate. Author: Andrew Dunstan Reviewed By: Andreas Karlsson, Asaba Takanori Discussion: https://postgr.es/m/04116472-818b-5859-1d74-3d995aab2252@2ndQuadrant.com --- src/backend/libpq/be-secure-openssl.c | 48 ++++++---- src/backend/postmaster/postmaster.c | 22 ++--- src/include/libpq/libpq-be.h | 4 + src/test/modules/Makefile | 5 ++ .../ssl_passphrase_callback/.gitignore | 1 + .../modules/ssl_passphrase_callback/Makefile | 24 +++++ .../ssl_passphrase_callback/server.crt | 19 ++++ .../ssl_passphrase_callback/server.key | 30 +++++++ .../ssl_passphrase_func.c | 88 +++++++++++++++++++ .../ssl_passphrase_callback/t/001_testfunc.pl | 80 +++++++++++++++++ src/tools/msvc/Mkvcbuild.pm | 2 +- 11 files changed, 292 insertions(+), 31 deletions(-) create mode 100644 src/test/modules/ssl_passphrase_callback/.gitignore create mode 100644 src/test/modules/ssl_passphrase_callback/Makefile create mode 100644 src/test/modules/ssl_passphrase_callback/server.crt create mode 100644 src/test/modules/ssl_passphrase_callback/server.key create mode 100644 src/test/modules/ssl_passphrase_callback/ssl_passphrase_func.c create mode 100644 src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c index b53c2b813eb..a65f920343c 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 2b9ab322931..73d278f3b2c 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 82e57afc642..ee57fdc301b 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 b2eaef3bff5..5f975ebcbac 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 00000000000..1dbadf7baf9 --- /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 00000000000..e2d19f131a6 --- /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 00000000000..b3c4be4fa5c --- /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 00000000000..1475007c73f --- /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 00000000000..c95cb509458 --- /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 00000000000..c052d72f9cd --- /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 636428b0448..39709f20e67 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})