Provide a TLS init hook
authorAndrew Dunstan <andrew@dunslane.net>
Wed, 25 Mar 2020 21:13:17 +0000 (17:13 -0400)
committerAndrew Dunstan <andrew@dunslane.net>
Wed, 25 Mar 2020 21:13:17 +0000 (17:13 -0400)
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
src/backend/postmaster/postmaster.c
src/include/libpq/libpq-be.h
src/test/modules/Makefile
src/test/modules/ssl_passphrase_callback/.gitignore [new file with mode: 0644]
src/test/modules/ssl_passphrase_callback/Makefile [new file with mode: 0644]
src/test/modules/ssl_passphrase_callback/server.crt [new file with mode: 0644]
src/test/modules/ssl_passphrase_callback/server.key [new file with mode: 0644]
src/test/modules/ssl_passphrase_callback/ssl_passphrase_func.c [new file with mode: 0644]
src/test/modules/ssl_passphrase_callback/t/001_testfunc.pl [new file with mode: 0644]
src/tools/msvc/Mkvcbuild.pm

index b53c2b813ebb8687c20dba02400c19bc4ef0ed1f..a65f920343c599d9242599114e81f2896ad862a8 100644 (file)
@@ -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);
+       }
+}
index 2b9ab32293102e10f34f74f9adaaad74074ea4bd..73d278f3b2ccecb4c826eb049b1583cbaa9cc6fd 100644 (file)
@@ -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.
index 82e57afc642e86df083d8f11cfd8c735ea89b5ba..ee57fdc301b1d0ebb70927b5066f28f3ed85ac42 100644 (file)
@@ -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
index b2eaef3bff5b945008a87499c0ae3db18b0356d5..5f975ebcbac43018e630df2f55cae1e8792d11e0 100644 (file)
@@ -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 (file)
index 0000000..1dbadf7
--- /dev/null
@@ -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 (file)
index 0000000..e2d19f1
--- /dev/null
@@ -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 (file)
index 0000000..b3c4be4
--- /dev/null
@@ -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 (file)
index 0000000..1475007
--- /dev/null
@@ -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 (file)
index 0000000..c95cb50
--- /dev/null
@@ -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 <float.h>
+#include <stdio.h>
+
+#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 (file)
index 0000000..c052d72
--- /dev/null
@@ -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();
index 636428b04482c3214d586838d25d2b65ee2a405f..39709f20e672b385ec8f44d461daf1162ec98436 100644 (file)
@@ -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})