Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 8a3d942

Browse files
committed
Add ssl_passphrase_command setting
This allows specifying an external command for prompting for or otherwise obtaining passphrases for SSL key files. This is useful because in many cases there is no TTY easily available during service startup. Also add a setting ssl_passphrase_command_supports_reload, which allows supporting SSL configuration reload even if SSL files need passphrases. Reviewed-by: Daniel Gustafsson <daniel@yesql.se>
1 parent 7a50bb6 commit 8a3d942

File tree

13 files changed

+313
-20
lines changed

13 files changed

+313
-20
lines changed

doc/src/sgml/config.sgml

+60
Original file line numberDiff line numberDiff line change
@@ -1313,6 +1313,66 @@ include_dir 'conf.d'
13131313
</para>
13141314
</listitem>
13151315
</varlistentry>
1316+
1317+
<varlistentry id="guc-ssl-passphrase-command" xreflabel="ssl_passphrase_command">
1318+
<term><varname>ssl_passphrase_command</varname> (<type>string</type>)
1319+
<indexterm>
1320+
<primary><varname>ssl_passphrase_command</varname> configuration parameter</primary>
1321+
</indexterm>
1322+
</term>
1323+
<listitem>
1324+
<para>
1325+
Sets an external command to be invoked when a passphrase for
1326+
decrypting an SSL file such as a private key needs to be obtained. By
1327+
default, this parameter is empty, which means the built-in prompting
1328+
mechanism is used.
1329+
</para>
1330+
<para>
1331+
The command must print the passphrase to the standard output and exit
1332+
with code 0. In the parameter value, <literal>%p</literal> is
1333+
replaced by a prompt string. (Write <literal>%%</literal> for a
1334+
literal <literal>%</literal>.) Note that the prompt string will
1335+
probably contain whitespace, so be sure to quote adequately. A single
1336+
newline is stripped from the end of the output if present.
1337+
</para>
1338+
<para>
1339+
The command does not actually have to prompt the user for a
1340+
passphrase. It can read it from a file, obtain it from a keychain
1341+
facility, or similar. It is up to the user to make sure the chosen
1342+
mechanism is adequately secure.
1343+
</para>
1344+
<para>
1345+
This parameter can only be set in the <filename>postgresql.conf</filename>
1346+
file or on the server command line.
1347+
</para>
1348+
</listitem>
1349+
</varlistentry>
1350+
1351+
<varlistentry id="guc-ssl-passphrase-command-supports-reload" xreflabel="ssl_passphrase_command_supports_reload">
1352+
<term><varname>ssl_passphrase_command_supports_reload</varname> (<type>boolean</type>)
1353+
<indexterm>
1354+
<primary><varname>ssl_passphrase_command_supports_reload</varname> configuration parameter</primary>
1355+
</indexterm>
1356+
</term>
1357+
<listitem>
1358+
<para>
1359+
This setting determines whether the passphrase command set by
1360+
<varname>ssl_passphrase_command</varname> will also be called during a
1361+
configuration reload if a key file needs a passphrase. If this
1362+
setting is false (the default), then
1363+
<varname>ssl_passphrase_command</varname> will be ignored during a
1364+
reload and the SSL configuration will not be reloaded if a passphrase
1365+
is needed. That setting is appropriate for a command that requires a
1366+
TTY for prompting, which might not be available when the server is
1367+
running. Setting this to true might be appropriate if the passphrase
1368+
is obtained from a file, for example.
1369+
</para>
1370+
<para>
1371+
This parameter can only be set in the <filename>postgresql.conf</filename>
1372+
file or on the server command line.
1373+
</para>
1374+
</listitem>
1375+
</varlistentry>
13161376
</variablelist>
13171377
</sect2>
13181378
</sect1>

src/backend/libpq/Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ include $(top_builddir)/src/Makefile.global
1414

1515
# be-fsstubs is here for historical reasons, probably belongs elsewhere
1616

17-
OBJS = be-fsstubs.o be-secure.o auth.o crypt.o hba.o ifaddr.o pqcomm.o \
17+
OBJS = be-fsstubs.o be-secure.o be-secure-common.o auth.o crypt.o hba.o ifaddr.o pqcomm.o \
1818
pqformat.o pqmq.o pqsignal.o auth-scram.o
1919

2020
ifeq ($(with_openssl),yes)

src/backend/libpq/be-secure-common.c

+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/*-------------------------------------------------------------------------
2+
*
3+
* be-secure-common.c
4+
*
5+
* common implementation-independent SSL support code
6+
*
7+
* While be-secure.c contains the interfaces that the rest of the
8+
* communications code calls, this file contains support routines that are
9+
* used by the library-specific implementations such as be-secure-openssl.c.
10+
*
11+
* Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
12+
* Portions Copyright (c) 1994, Regents of the University of California
13+
*
14+
* IDENTIFICATION
15+
* src/backend/libpq/be-secure-common.c
16+
*
17+
*-------------------------------------------------------------------------
18+
*/
19+
20+
#include "postgres.h"
21+
22+
#include "libpq/libpq.h"
23+
#include "storage/fd.h"
24+
25+
/*
26+
* Run ssl_passphrase_command
27+
*
28+
* prompt will be substituted for %p. is_server_start determines the loglevel
29+
* of error messages.
30+
*
31+
* The result will be put in buffer buf, which is of size size. The return
32+
* value is the length of the actual result.
33+
*/
34+
int
35+
run_ssl_passphrase_command(const char *prompt, bool is_server_start, char *buf, int size)
36+
{
37+
int loglevel = is_server_start ? ERROR : LOG;
38+
StringInfoData command;
39+
char *p;
40+
FILE *fh;
41+
int pclose_rc;
42+
size_t len = 0;
43+
44+
Assert(prompt);
45+
Assert(size > 0);
46+
buf[0] = '\0';
47+
48+
initStringInfo(&command);
49+
50+
for (p = ssl_passphrase_command; *p; p++)
51+
{
52+
if (p[0] == '%')
53+
{
54+
switch (p[1])
55+
{
56+
case 'p':
57+
appendStringInfoString(&command, prompt);
58+
p++;
59+
break;
60+
case '%':
61+
appendStringInfoChar(&command, '%');
62+
p++;
63+
break;
64+
default:
65+
appendStringInfoChar(&command, p[0]);
66+
}
67+
}
68+
else
69+
appendStringInfoChar(&command, p[0]);
70+
}
71+
72+
fh = OpenPipeStream(command.data, "r");
73+
if (fh == NULL)
74+
{
75+
ereport(loglevel,
76+
(errcode_for_file_access(),
77+
errmsg("could not execute command \"%s\": %m",
78+
command.data)));
79+
goto error;
80+
}
81+
82+
if (!fgets(buf, size, fh))
83+
{
84+
if (ferror(fh))
85+
{
86+
ereport(loglevel,
87+
(errcode_for_file_access(),
88+
errmsg("could not read from command \"%s\": %m",
89+
command.data)));
90+
goto error;
91+
}
92+
}
93+
94+
pclose_rc = ClosePipeStream(fh);
95+
if (pclose_rc == -1)
96+
{
97+
ereport(loglevel,
98+
(errcode_for_file_access(),
99+
errmsg("could not close pipe to external command: %m")));
100+
goto error;
101+
}
102+
else if (pclose_rc != 0)
103+
{
104+
ereport(loglevel,
105+
(errcode_for_file_access(),
106+
errmsg("command \"%s\" failed",
107+
command.data),
108+
errdetail_internal("%s", wait_result_to_str(pclose_rc))));
109+
goto error;
110+
}
111+
112+
/* strip trailing newline */
113+
len = strlen(buf);
114+
if (buf[len - 1] == '\n')
115+
buf[len-- -1] = '\0';
116+
117+
error:
118+
pfree(command.data);
119+
return len;
120+
}

src/backend/libpq/be-secure-openssl.c

+44-14
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ static int my_SSL_set_fd(Port *port, int fd);
5252

5353
static DH *load_dh_file(char *filename, bool isServerStart);
5454
static DH *load_dh_buffer(const char *, size_t);
55-
static int ssl_passwd_cb(char *buf, int size, int rwflag, void *userdata);
55+
static int ssl_external_passwd_cb(char *buf, int size, int rwflag, void *userdata);
56+
static int dummy_ssl_passwd_cb(char *buf, int size, int rwflag, void *userdata);
5657
static int verify_cb(int, X509_STORE_CTX *);
5758
static void info_cb(const SSL *ssl, int type, int args);
5859
static bool initialize_dh(SSL_CTX *context, bool isServerStart);
@@ -63,7 +64,8 @@ static char *X509_NAME_to_cstring(X509_NAME *name);
6364

6465
static SSL_CTX *SSL_context = NULL;
6566
static bool SSL_initialized = false;
66-
static bool ssl_passwd_cb_called = false;
67+
static bool dummy_ssl_passwd_cb_called = false;
68+
static bool ssl_is_server_start;
6769

6870

6971
/* ------------------------------------------------------------ */
@@ -111,14 +113,28 @@ be_tls_init(bool isServerStart)
111113
SSL_CTX_set_mode(context, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
112114

113115
/*
114-
* If reloading, override OpenSSL's default handling of
115-
* passphrase-protected files, because we don't want to prompt for a
116-
* passphrase in an already-running server. (Not that the default
117-
* handling is very desirable during server start either, but some people
118-
* insist we need to keep it.)
116+
* Set password callback
119117
*/
120-
if (!isServerStart)
121-
SSL_CTX_set_default_passwd_cb(context, ssl_passwd_cb);
118+
if (isServerStart)
119+
{
120+
if (ssl_passphrase_command[0])
121+
SSL_CTX_set_default_passwd_cb(context, ssl_external_passwd_cb);
122+
}
123+
else
124+
{
125+
if (ssl_passphrase_command[0] && ssl_passphrase_command_supports_reload)
126+
SSL_CTX_set_default_passwd_cb(context, ssl_external_passwd_cb);
127+
else
128+
/*
129+
* If reloading and no external command is configured, override
130+
* OpenSSL's default handling of passphrase-protected files,
131+
* because we don't want to prompt for a passphrase in an
132+
* already-running server.
133+
*/
134+
SSL_CTX_set_default_passwd_cb(context, dummy_ssl_passwd_cb);
135+
}
136+
/* used by the callback */
137+
ssl_is_server_start = isServerStart;
122138

123139
/*
124140
* Load and verify server's certificate and private key
@@ -138,13 +154,13 @@ be_tls_init(bool isServerStart)
138154
/*
139155
* OK, try to load the private key file.
140156
*/
141-
ssl_passwd_cb_called = false;
157+
dummy_ssl_passwd_cb_called = false;
142158

143159
if (SSL_CTX_use_PrivateKey_file(context,
144160
ssl_key_file,
145161
SSL_FILETYPE_PEM) != 1)
146162
{
147-
if (ssl_passwd_cb_called)
163+
if (dummy_ssl_passwd_cb_called)
148164
ereport(isServerStart ? FATAL : LOG,
149165
(errcode(ERRCODE_CONFIG_FILE_ERROR),
150166
errmsg("private key file \"%s\" cannot be reloaded because it requires a passphrase",
@@ -839,7 +855,21 @@ load_dh_buffer(const char *buffer, size_t len)
839855
}
840856

841857
/*
842-
* Passphrase collection callback
858+
* Passphrase collection callback using ssl_passphrase_command
859+
*/
860+
static int
861+
ssl_external_passwd_cb(char *buf, int size, int rwflag, void *userdata)
862+
{
863+
/* same prompt as OpenSSL uses internally */
864+
const char *prompt = "Enter PEM pass phrase:";
865+
866+
Assert(rwflag == 0);
867+
868+
return run_ssl_passphrase_command(prompt, ssl_is_server_start, buf, size);
869+
}
870+
871+
/*
872+
* Dummy passphrase callback
843873
*
844874
* If OpenSSL is told to use a passphrase-protected server key, by default
845875
* it will issue a prompt on /dev/tty and try to read a key from there.
@@ -848,10 +878,10 @@ load_dh_buffer(const char *buffer, size_t len)
848878
* function that just returns an empty passphrase, guaranteeing failure.
849879
*/
850880
static int
851-
ssl_passwd_cb(char *buf, int size, int rwflag, void *userdata)
881+
dummy_ssl_passwd_cb(char *buf, int size, int rwflag, void *userdata)
852882
{
853883
/* Set flag to change the error message we'll report */
854-
ssl_passwd_cb_called = true;
884+
dummy_ssl_passwd_cb_called = true;
855885
/* And return empty string */
856886
Assert(size > 0);
857887
buf[0] = '\0';

src/backend/libpq/be-secure.c

+2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ char *ssl_key_file;
4545
char *ssl_ca_file;
4646
char *ssl_crl_file;
4747
char *ssl_dh_params_file;
48+
char *ssl_passphrase_command;
49+
bool ssl_passphrase_command_supports_reload;
4850

4951
#ifdef USE_SSL
5052
bool ssl_loaded_verify_locations = false;

src/backend/utils/misc/guc.c

+19
Original file line numberDiff line numberDiff line change
@@ -988,6 +988,15 @@ static struct config_bool ConfigureNamesBool[] =
988988
false,
989989
check_ssl, NULL, NULL
990990
},
991+
{
992+
{"ssl_passphrase_command_supports_reload", PGC_SIGHUP, CONN_AUTH_SSL,
993+
gettext_noop("Also use ssl_passphrase_command during server reload."),
994+
NULL
995+
},
996+
&ssl_passphrase_command_supports_reload,
997+
false,
998+
NULL, NULL, NULL
999+
},
9911000
{
9921001
{"ssl_prefer_server_ciphers", PGC_SIGHUP, CONN_AUTH_SSL,
9931002
gettext_noop("Give priority to server ciphersuite order."),
@@ -3655,6 +3664,16 @@ static struct config_string ConfigureNamesString[] =
36553664
NULL, NULL, NULL
36563665
},
36573666

3667+
{
3668+
{"ssl_passphrase_command", PGC_SIGHUP, CONN_AUTH_SSL,
3669+
gettext_noop("Command to obtain passphrases for SSL."),
3670+
NULL
3671+
},
3672+
&ssl_passphrase_command,
3673+
"",
3674+
NULL, NULL, NULL
3675+
},
3676+
36583677
{
36593678
{"application_name", PGC_USERSET, LOGGING_WHAT,
36603679
gettext_noop("Sets the application name to be reported in statistics and logs."),

src/backend/utils/misc/postgresql.conf.sample

+2
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@
104104
#ssl_prefer_server_ciphers = on
105105
#ssl_ecdh_curve = 'prime256v1'
106106
#ssl_dh_params_file = ''
107+
#ssl_passphrase_command = ''
108+
#ssl_passphrase_command_supports_reload = off
107109

108110

109111
#------------------------------------------------------------------------------

src/include/libpq/libpq.h

+8
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ extern char *ssl_key_file;
8080
extern char *ssl_ca_file;
8181
extern char *ssl_crl_file;
8282
extern char *ssl_dh_params_file;
83+
extern char *ssl_passphrase_command;
84+
extern bool ssl_passphrase_command_supports_reload;
8385

8486
extern int secure_initialize(bool isServerStart);
8587
extern bool secure_loaded_verify_locations(void);
@@ -101,4 +103,10 @@ extern char *SSLCipherSuites;
101103
extern char *SSLECDHCurve;
102104
extern bool SSLPreferServerCiphers;
103105

106+
/*
107+
* prototypes for functions in be-secure-common.c
108+
*/
109+
extern int run_ssl_passphrase_command(const char *prompt, bool is_server_start,
110+
char *buf, int size);
111+
104112
#endif /* LIBPQ_H */

src/test/ssl/Makefile

+5
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ CERTIFICATES := server_ca server-cn-and-alt-names \
2222
root_ca
2323

2424
SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
25+
ssl/server-password.key \
2526
ssl/client.crl ssl/server.crl ssl/root.crl \
2627
ssl/both-cas-1.crt ssl/both-cas-2.crt \
2728
ssl/root+server_ca.crt ssl/root+server.crl \
@@ -71,6 +72,10 @@ ssl/server-ss.crt: ssl/server-cn-only.key ssl/server-cn-only.crt server-cn-only.
7172
openssl x509 -req -days 10000 -in ssl/server-ss.csr -signkey ssl/server-cn-only.key -out ssl/server-ss.crt -extensions v3_req -extfile server-cn-only.config
7273
rm ssl/server-ss.csr
7374

75+
# Password-protected version of server-cn-only.key
76+
ssl/server-password.key: ssl/server-cn-only.key
77+
openssl rsa -des -in $< -out $@ -passout 'pass:secret1'
78+
7479
# Client certificate, signed by the client CA:
7580
ssl/client.crt: ssl/client.key ssl/client_ca.crt
7681
openssl req -new -key ssl/client.key -out ssl/client.csr -config client.config

0 commit comments

Comments
 (0)