Skip to content

Commit 4dc6355

Browse files
committed
libq support for sslpassword connection param, DER format keys
This patch providies for support for password protected SSL client keys in libpq, and for DER format keys, both encrypted and unencrypted. There is a new connection parameter sslpassword, which is supplied to the OpenSSL libraries via a callback function. The callback function can also be set by an application by calling PQgetSSLKeyPassHook(). There is also a function to retreive the connection setting, PQsslpassword(). Craig Ringer and Andrew Dunstan Reviewed by: Greg Nancarrow Discussion: https://postgr.es/m/f7ee88ed-95c4-95c1-d4bf-7b415363ab62@2ndQuadrant.com
1 parent 3ff660b commit 4dc6355

File tree

13 files changed

+376
-17
lines changed

13 files changed

+376
-17
lines changed

contrib/dblink/expected/dblink.out

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -879,7 +879,7 @@ $d$;
879879
CREATE USER MAPPING FOR public SERVER fdtest
880880
OPTIONS (server 'localhost'); -- fail, can't specify server here
881881
ERROR: invalid option "server"
882-
HINT: Valid options in this context are: user, password
882+
HINT: Valid options in this context are: user, password, sslpassword
883883
CREATE USER MAPPING FOR public SERVER fdtest OPTIONS (user :'USER');
884884
GRANT USAGE ON FOREIGN SERVER fdtest TO regress_dblink_user;
885885
GRANT EXECUTE ON FUNCTION dblink_connect_u(text, text) TO regress_dblink_user;

doc/src/sgml/libpq.sgml

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -776,6 +776,72 @@ PGPing PQping(const char *conninfo);
776776
</listitem>
777777
</varlistentry>
778778

779+
<varlistentry id="libpq-pqsetsslkeypasshook">
780+
<term><function>PQsetSSLKeyPassHook</function><indexterm><primary>PQsetSSLKeyPassHook</primary></indexterm></term>
781+
<listitem>
782+
<para>
783+
<function>PQsetSSLKeyPassHook</function> lets an application override
784+
<literal>libpq</literal>'s <link linkend="libpq-ssl-clientcert">default
785+
handling of encrypted client certificate key files</link> using
786+
<xref linkend="libpq-connect-sslpassword"/> or interactive prompting.
787+
788+
<synopsis>
789+
void PQsetSSLKeyPassHook(PQsslKeyPassHook_type hook);
790+
</synopsis>
791+
792+
The application passes a pointer to a callback function with signature:
793+
<programlisting>
794+
int callback_fn(char *buf, int size, PGconn *conn);
795+
</programlisting>
796+
which <literal>libpq</literal> will then call <emphasis>instead of</emphasis>
797+
its default <function>PQdefaultSSLKeyPassHook</function> handler. The callback
798+
should determine the password for the key and copy it to result-buffer
799+
<literal>buf</literal> of size <literal>size</literal>. The string in <literal>
800+
buf</literal> must be null-terminated. The calback must return the length of
801+
the password stored in <literal>buf</literal> excluding the null terminator.
802+
On failure, the callback should set <literal>buf[0] = '\0'</literal> and return 0.
803+
See <function>PQdefaultSSLKeyPassHook</function> in <literal>libpq</literal>'s
804+
source code for an example.
805+
</para>
806+
807+
<para>
808+
If the user specified an explicit key location,
809+
its path will be in <literal>conn->pgsslkey</literal> when the callback
810+
is invoked. This will be empty if the default key path is being used.
811+
For keys that are engine specifiers, it is up to engine implementations
812+
whether they use the OpenSSL password callback or define their own handling.
813+
</para>
814+
815+
<para>
816+
The app callback may choose to delegate unhandled cases to
817+
<function>PQdefaultSSLKeyPassHook</function>,
818+
or call it first and try something else if it returns 0, or completely override it.
819+
</para>
820+
821+
<para>
822+
The callback <emphasis>must not</emphasis> escape normal flow control with exceptions,
823+
<function>longjmp(...)</function>, etc. It must return normally.
824+
</para>
825+
826+
</listitem>
827+
</varlistentry>
828+
829+
<varlistentry id="libpq-pqgetsslkeypasshook">
830+
<term><function>PQgetSSLKeyPassHook</function><indexterm><primary>PQgetSSLKeyPassHook</primary></indexterm></term>
831+
<listitem>
832+
<para>
833+
<function>PQgetSSLKeyPassHook</function> returns the current
834+
client certificate key password hook, or <literal>NULL</literal>
835+
if none has been set.
836+
837+
<synopsis>
838+
PQsslKeyPassHook_type PQgetSSLKeyPassHook(void);
839+
</synopsis>
840+
</para>
841+
842+
</listitem>
843+
</varlistentry>
844+
779845
</variablelist>
780846
</para>
781847

@@ -1586,6 +1652,36 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
15861652
</listitem>
15871653
</varlistentry>
15881654

1655+
<varlistentry id="libpq-connect-sslpassword" xreflabel="sslpassword">
1656+
<term><literal>sslpassword</literal></term>
1657+
<listitem>
1658+
<para>
1659+
This parameter specifies the password for the secret key specified in
1660+
<literal>sslkey</literal>, allowing client certificate private keys
1661+
to be stored in encrypted form on disk even when interactive passphrase
1662+
input is not practical.
1663+
</para>
1664+
<para>
1665+
Specifying this parameter with any non-empty value suppresses the
1666+
<literal>Enter PEM passphrase:</literal>
1667+
prompt that OpenSSL will emit by default when an encrypted client
1668+
certificate key is provided to <literal>libpq</literal>.
1669+
</para>
1670+
<para>
1671+
If the key is not encrypted this parameter is ignored. The parameter has no
1672+
effect on keys specified by OpenSSL engines unless the engine uses the
1673+
OpenSSL password callback mechanism for prompts.
1674+
</para>
1675+
<para>
1676+
There is no environment variable equivalent to this option, and no
1677+
facility for looking it up in <filename>.pgpass</filename>. It can be
1678+
used in a service file connection definition. Users with
1679+
more sophisticated uses should consider using openssl engines and
1680+
tools like PKCS#11 or USB crypto offload devices.
1681+
</para>
1682+
</listitem>
1683+
</varlistentry>
1684+
15891685
<varlistentry id="libpq-connect-sslrootcert" xreflabel="sslrootcert">
15901686
<term><literal>sslrootcert</literal></term>
15911687
<listitem>
@@ -1771,6 +1867,24 @@ char *PQpass(const PGconn *conn);
17711867
</listitem>
17721868
</varlistentry>
17731869

1870+
<varlistentry id="libpq-PQsslpassword">
1871+
<term><function>PQsslpassword</function><indexterm><primary>PQsslpassword</primary></indexterm></term>
1872+
1873+
<listitem>
1874+
<para>
1875+
Returns the password for the SSL client key.
1876+
<synopsis>
1877+
char *PQsslpassword(const PGconn *conn);
1878+
</synopsis>
1879+
</para>
1880+
1881+
<para>
1882+
<xref linkend="libpq-PQsslpassword"/> will return the SSL password specified
1883+
in the connection parameters.
1884+
</para>
1885+
</listitem>
1886+
</varlistentry>
1887+
17741888
<varlistentry id="libpq-PQhost">
17751889
<term><function>PQhost</function><indexterm><primary>PQhost</primary></indexterm></term>
17761890

@@ -7499,6 +7613,26 @@ ldap://ldap.acme.com/cn=dbserver,cn=hosts?pgconnectinfo?base?(objectclass=*)
74997613
certificates on the server (<xref linkend="guc-ssl-ca-file"/>).
75007614
</para>
75017615

7616+
<para>
7617+
The certificate and key may be in PEM or ASN.1 DER format.
7618+
</para>
7619+
7620+
<para>
7621+
The key may be
7622+
stored in cleartext or encrypted with a passphrase using any algorithm supported
7623+
by OpenSSL, like AES-128. If the key is stored encrypted, then the passphrase
7624+
may be provided in the <xref linkend="libpq-connect-sslpassword"/> connection
7625+
option. If an encrypted key is supplied and the <literal>sslpassword</literal>
7626+
option is absent or blank, a password will be prompted for interactively by
7627+
OpenSSL with a
7628+
<programlisting>
7629+
Enter PEM Passphrase:
7630+
</programlisting>
7631+
prompt if a TTY is available. Applications can override the client certificate
7632+
prompt and the handling of the <literal>sslpassword</literal> parameter by supplying
7633+
their own key password callback; see <xref linkend="libpq-pqsetsslkeypasshook"/>.
7634+
</para>
7635+
75027636
<para>
75037637
For instructions on creating certificates, see <xref
75047638
linkend="ssl-certificate-creation"/>.

doc/src/sgml/postgres-fdw.sgml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@
112112
<itemizedlist spacing="compact">
113113
<listitem>
114114
<para>
115-
<literal>user</literal> and <literal>password</literal> (specify these
115+
<literal>user</literal>, <literal>password</literal> and <literal>sslpassword</literal> (specify these
116116
in a user mapping, instead)
117117
</para>
118118
</listitem>

src/interfaces/libpq/exports.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,3 +176,7 @@ PQresultMemorySize 173
176176
PQhostaddr 174
177177
PQgssEncInUse 175
178178
PQgetgssctx 176
179+
PQsslpassword 177
180+
PQsetSSLKeyPassHook 178
181+
PQgetSSLKeyPassHook 179
182+
PQdefaultSSLKeyPassHook 180

src/interfaces/libpq/fe-connect.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
351351
"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
352352
offsetof(struct pg_conn, target_session_attrs)},
353353

354+
{"sslpassword", NULL, NULL, NULL,
355+
"SSL-Client-Key-Password", "*", 20,
356+
offsetof(struct pg_conn, sslpassword)},
357+
354358
/* Terminating entry --- MUST BE LAST */
355359
{NULL, NULL, NULL, NULL,
356360
NULL, NULL, 0}
@@ -4026,6 +4030,8 @@ freePGconn(PGconn *conn)
40264030
free(conn->target_session_attrs);
40274031
termPQExpBuffer(&conn->errorMessage);
40284032
termPQExpBuffer(&conn->workBuffer);
4033+
if (conn->sslpassword)
4034+
free(conn->sslpassword);
40294035

40304036
free(conn);
40314037

@@ -6544,6 +6550,14 @@ PQport(const PGconn *conn)
65446550
return "";
65456551
}
65466552

6553+
char *
6554+
PQsslpassword(const PGconn *conn)
6555+
{
6556+
if (!conn)
6557+
return NULL;
6558+
return conn->sslpassword;
6559+
}
6560+
65476561
char *
65486562
PQtty(const PGconn *conn)
65496563
{

src/interfaces/libpq/fe-secure-openssl.c

Lines changed: 95 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ static int initialize_SSL(PGconn *conn);
7070
static PostgresPollingStatusType open_client_SSL(PGconn *);
7171
static char *SSLerrmessage(unsigned long ecode);
7272
static void SSLerrfree(char *buf);
73+
static int PQssl_passwd_cb(char *buf, int size, int rwflag, void *userdata);
7374

7475
static int my_sock_read(BIO *h, char *buf, int size);
7576
static int my_sock_write(BIO *h, const char *buf, int size);
@@ -93,6 +94,7 @@ static long win32_ssl_create_mutex = 0;
9394
#endif
9495
#endif /* ENABLE_THREAD_SAFETY */
9596

97+
static PQsslKeyPassHook_type PQsslKeyPassHook = NULL;
9698

9799
/* ------------------------------------------------------------ */
98100
/* Procedures common to all secure sessions */
@@ -818,6 +820,26 @@ initialize_SSL(PGconn *conn)
818820
return -1;
819821
}
820822

823+
/*
824+
* Delegate the client cert password prompt to the libpq wrapper
825+
* callback if any is defined.
826+
*
827+
* If the application hasn't installed its own and the sslpassword
828+
* parameter is non-null, we install ours now to make sure we
829+
* supply PGconn->sslpassword to OpenSSL instead of letting it
830+
* prompt on stdin.
831+
*
832+
* This will replace OpenSSL's default PEM_def_callback (which
833+
* prompts on stdin), but we're only setting it for this SSL
834+
* context so it's harmless.
835+
*/
836+
if (PQsslKeyPassHook
837+
|| (conn->sslpassword && strlen(conn->sslpassword) > 0))
838+
{
839+
SSL_CTX_set_default_passwd_cb(SSL_context, PQssl_passwd_cb);
840+
SSL_CTX_set_default_passwd_cb_userdata(SSL_context, conn);
841+
}
842+
821843
/* Disable old protocol versions */
822844
SSL_CTX_set_options(SSL_context, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
823845

@@ -1123,11 +1145,29 @@ initialize_SSL(PGconn *conn)
11231145
{
11241146
char *err = SSLerrmessage(ERR_get_error());
11251147

1126-
printfPQExpBuffer(&conn->errorMessage,
1127-
libpq_gettext("could not load private key file \"%s\": %s\n"),
1128-
fnbuf, err);
1148+
/*
1149+
* We'll try to load the file in DER (binary ASN.1) format, and if
1150+
* that fails too, report the original error. This could mask
1151+
* issues where there's something wrong with a DER-format cert, but
1152+
* we'd have to duplicate openssl's format detection to be smarter
1153+
* than this. We can't just probe for a leading -----BEGIN because
1154+
* PEM can have leading non-matching lines and blanks. OpenSSL
1155+
* doesn't expose its get_name(...) and its PEM routines don't
1156+
* differentiate between failure modes in enough detail to let us
1157+
* tell the difference between "not PEM, try DER" and "wrong
1158+
* password".
1159+
*/
1160+
if (SSL_use_PrivateKey_file(conn->ssl, fnbuf, SSL_FILETYPE_ASN1) != 1)
1161+
{
1162+
printfPQExpBuffer(&conn->errorMessage,
1163+
libpq_gettext("could not load private key file \"%s\": %s\n"),
1164+
fnbuf, err);
1165+
SSLerrfree(err);
1166+
return -1;
1167+
}
1168+
11291169
SSLerrfree(err);
1130-
return -1;
1170+
11311171
}
11321172
}
11331173

@@ -1580,3 +1620,54 @@ my_SSL_set_fd(PGconn *conn, int fd)
15801620
err:
15811621
return ret;
15821622
}
1623+
1624+
/*
1625+
* This is the default handler to return a client cert password from
1626+
* conn->sslpassword. Apps may install it explicitly if they want to
1627+
* prevent openssl from ever prompting on stdin.
1628+
*/
1629+
int
1630+
PQdefaultSSLKeyPassHook(char *buf, int size, PGconn *conn)
1631+
{
1632+
if (conn->sslpassword)
1633+
{
1634+
if (strlen(conn->sslpassword) + 1 > size)
1635+
fprintf(stderr, libpq_gettext("WARNING: sslpassword truncated"));
1636+
strncpy(buf, conn->sslpassword, size);
1637+
buf[size-1] = '\0';
1638+
return strlen(buf);
1639+
}
1640+
else
1641+
{
1642+
buf[0] = '\0';
1643+
return 0;
1644+
}
1645+
}
1646+
1647+
PQsslKeyPassHook_type
1648+
PQgetSSLKeyPassHook(void)
1649+
{
1650+
return PQsslKeyPassHook;
1651+
}
1652+
1653+
void
1654+
PQsetSSLKeyPassHook(PQsslKeyPassHook_type hook)
1655+
{
1656+
PQsslKeyPassHook = hook;
1657+
}
1658+
1659+
/*
1660+
* Supply a password to decrypt a client certificate.
1661+
*
1662+
* This must match OpenSSL type pem_passwd_cb.
1663+
*/
1664+
static int
1665+
PQssl_passwd_cb(char *buf, int size, int rwflag, void *userdata)
1666+
{
1667+
PGconn *conn = userdata;
1668+
1669+
if (PQsslKeyPassHook)
1670+
return PQsslKeyPassHook(buf, size, conn);
1671+
else
1672+
return PQdefaultSSLKeyPassHook(buf, size, conn);
1673+
}

src/interfaces/libpq/libpq-fe.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,7 @@ extern char *PQpass(const PGconn *conn);
317317
extern char *PQhost(const PGconn *conn);
318318
extern char *PQhostaddr(const PGconn *conn);
319319
extern char *PQport(const PGconn *conn);
320+
extern char *PQsslpassword(const PGconn *conn);
320321
extern char *PQtty(const PGconn *conn);
321322
extern char *PQoptions(const PGconn *conn);
322323
extern ConnStatusType PQstatus(const PGconn *conn);
@@ -617,6 +618,14 @@ extern int pg_char_to_encoding(const char *name);
617618
extern const char *pg_encoding_to_char(int encoding);
618619
extern int pg_valid_server_encoding_id(int encoding);
619620

621+
/* == in fe-secure-openssl.c === */
622+
623+
/* Support for overriding sslpassword handling with a callback. */
624+
typedef int (*PQsslKeyPassHook_type)(char *buf, int size, PGconn *conn);
625+
extern PQsslKeyPassHook_type PQgetSSLKeyPassHook(void);
626+
extern void PQsetSSLKeyPassHook(PQsslKeyPassHook_type hook);
627+
extern int PQdefaultSSLKeyPassHook(char *buf, int size, PGconn *conn);
628+
620629
#ifdef __cplusplus
621630
}
622631
#endif

src/interfaces/libpq/libpq-int.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,8 @@ struct pg_conn
512512

513513
/* Buffer for receiving various parts of messages */
514514
PQExpBufferData workBuffer; /* expansible string */
515+
516+
char *sslpassword; /* client key file password */
515517
};
516518

517519
/* PGcancel stores all data necessary to cancel a connection. A copy of this

0 commit comments

Comments
 (0)