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
This commit is contained in:
Andrew Dunstan 2020-03-25 17:13:17 -04:00
parent ffd398021c
commit 896fcdb230
11 changed files with 292 additions and 31 deletions

View 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);
}
}

View 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.

View 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

View File

@ -25,4 +25,9 @@ SUBDIRS = \
unsafe_tests \
worker_spi
ifeq ($(with_openssl),yes)
SUBDIRS += ssl_passphrase_callback
endif
$(recurse)

View File

@ -0,0 +1 @@
tmp_check

View File

@ -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)

View File

@ -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-----

View File

@ -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-----

View File

@ -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);
}

View File

@ -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();

View 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})