This time with attachment.
On 10/31/19 6:33 PM, Andrew Dunstan wrote:
> This patch provides for an sslpassword parameter for libpq, and a hook
> that a client can fill in for a callback function to set the password.
>
>
> This provides similar facilities to those already available in the JDBC
> driver.
>
>
> There is also a function to fetch the sslpassword from the connection
> parameters, in the same way that other settings can be fetched.
>
>
> This is mostly the excellent work of my colleague Craig Ringer, with a
> few embellishments from me.
>
>
> Here are his notes:
>
>
> Allow libpq to non-interactively decrypt client certificates that
> are stored
> encrypted by adding a new "sslpassword" connection option.
>
> The sslpassword option offers a middle ground between a cleartext
> key and
> setting up advanced key mangement via openssl engines, PKCS#11, USB
> crypto
> offload and key escrow, etc.
>
> Previously use of encrypted client certificate keys only worked if
> the user
> could enter the key's password interactively on stdin, in response
> to openssl's
> default prompt callback:
>
> Enter PEM passhprase:
>
> That's infesible in many situations, especially things like use from
> postgres_fdw.
>
> This change also allows admins to prevent libpq from ever prompting
> for a
> password by calling:
>
> PQsetSSLKeyPassHook(PQdefaultSSLKeyPassHook);
>
> which is useful since OpenSSL likes to open /dev/tty to prompt for a
> password,
> so even closing stdin won't stop it blocking if there's no user
> input available.
> Applications may also override or extend SSL password fetching with
> their own
> callback.
>
> There is deliberately no environment variable equivalent for the
> sslpassword
> option.
>
>
> cheers
>
>
> andrew
>
>
--
Andrew Dunstan https://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
diff --git a/contrib/dblink/expected/dblink.out b/contrib/dblink/expected/dblink.out
index 6ceabb453c..6516d4f131 100644
--- a/contrib/dblink/expected/dblink.out
+++ b/contrib/dblink/expected/dblink.out
@@ -879,7 +879,7 @@ $d$;
CREATE USER MAPPING FOR public SERVER fdtest
OPTIONS (server 'localhost'); -- fail, can't specify server here
ERROR: invalid option "server"
-HINT: Valid options in this context are: user, password
+HINT: Valid options in this context are: user, password, sslpassword
CREATE USER MAPPING FOR public SERVER fdtest OPTIONS (user :'USER');
GRANT USAGE ON FOREIGN SERVER fdtest TO regress_dblink_user;
GRANT EXECUTE ON FUNCTION dblink_connect_u(text, text) TO regress_dblink_user;
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index c58527b0c3..a606680182 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -776,6 +776,72 @@ PGPing PQping(const char *conninfo);
</listitem>
</varlistentry>
+ <varlistentry id="libpq-pqsetsslkeypasshook">
+ <term><function>PQsetSSLKeyPassHook</function><indexterm><primary>PQsetSSLKeyPassHook</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQsetSSLKeyPassHook</function> lets an application override
+ <literal>libpq</literal>'s <link linkend="libpq-ssl-clientcert">default
+ handling of encrypted client certificate key files</link> using
+ <xref linkend="libpq-connect-sslpassword"/> or interactive prompting.
+
+<synopsis>
+void PQsetSSLKeyPassHook(PQsslKeyPassHook_type hook);
+</synopsis>
+
+ The application passes a pointer to a callback function with signature:
+ <programlisting>
+ int callback_fn(char *buf, int size, PGconn *conn);
+ </programlisting>
+ which <literal>libpq</literal> will then call <emphasis>instead of</emphasis>
+ its default <function>PQdefaultSSLKeyPassHook</function> handler. The callback
+ should determine the password for the key and copy it to result-buffer
+ <literal>buf</literal> of size <literal>size</literal>. The string in <literal>
+ buf</literal> must be null-terminated. The calback must return the length of
+ the password stored in <literal>buf</literal> excluding the null terminator.
+ On failure, the callback should set <literal>buf[0] = '\0'</literal> and return 0.
+ See <function>PQdefaultSSLKeyPassHook</function> in <literal>libpq</literal>'s
+ source code for an example.
+ </para>
+
+ <para>
+ If the user specified an explicit key location,
+ its path will be in <literal>conn->pgsslkey</literal> when the callback
+ is invoked. This will be empty if the default key path is being used.
+ For keys that are engine specifiers, it is up to engine implementations
+ whether they use the OpenSSL password callback or define their own handling.
+ </para>
+
+ <para>
+ The app callback may choose to delegate unhandled cases to
+ <function>PQdefaultSSLKeyPassHook</function>,
+ or call it first and try something else if it returns 0, or completely override it.
+ </para>
+
+ <para>
+ The callback <emphasis>must not</emphasis> escape normal flow control with exceptions,
+ <function>longjmp(...)</function>, etc. It must return normally.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="libpq-pqgetsslkeypasshook">
+ <term><function>PQgetSSLKeyPassHook</function><indexterm><primary>PQgetSSLKeyPassHook</primary></indexterm></term>
+ <listitem>
+ <para>
+ <function>PQgetSSLKeyPassHook</function> returns the current
+ client certificate key password hook, or <literal>NULL</literal>
+ if none has been set.
+
+<synopsis>
+PQsslKeyPassHook_type PQgetSSLKeyPassHook(void);
+</synopsis>
+ </para>
+
+ </listitem>
+ </varlistentry>
+
</variablelist>
</para>
@@ -1586,6 +1652,36 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
</listitem>
</varlistentry>
+ <varlistentry id="libpq-connect-sslpassword" xreflabel="sslpassword">
+ <term><literal>sslpassword</literal></term>
+ <listitem>
+ <para>
+ This parameter specifies the password for the secret key specified in
+ <literal>sslkey</literal>, allowing client certificate private keys
+ to be stored in encrypted form on disk even when interactive passphrase
+ input is not practical.
+ </para>
+ <para>
+ Specifying this parameter with any non-empty value suppresses the
+ <literal>Enter PEM passphrase:</literal>
+ prompt that OpenSSL will emit by default when an encrypted client
+ certificate key is provided to <literal>libpq</literal>.
+ </para>
+ <para>
+ If the key is not encrypted this parameter is ignored. The parameter has no
+ effect on keys specified by OpenSSL engines unless the engine uses the
+ OpenSSL password callback mechanism for prompts.
+ </para>
+ <para>
+ There is no environment variable equivalent to this option, and no
+ facility for looking it up in <filename>.pgpass</filename>. It can be
+ used in a service file connection definition. Users with
+ more sophisticated uses should consider using openssl engines and
+ tools like PKCS#11 or USB crypto offload devices.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="libpq-connect-sslrootcert" xreflabel="sslrootcert">
<term><literal>sslrootcert</literal></term>
<listitem>
@@ -7499,6 +7595,26 @@ ldap://ldap.acme.com/cn=dbserver,cn=hosts?pgconnectinfo?base?(objectclass=*)
certificates on the server (<xref linkend="guc-ssl-ca-file"/>).
</para>
+ <para>
+ The certificate and key may be in PEM or ASN.1 DER format.
+ </para>
+
+ <para>
+ The key may be
+ stored in cleartext or encrypted with a passphrase using any algorithm supported
+ by OpenSSL, like AES-128. If the key is stored encrypted, then the passphrase
+ may be provided in the <xref linkend="libpq-connect-sslpassword"/> connection
+ option. If an encrypted key is supplied and the <literal>sslpassword</literal>
+ option is absent or blank, a password will be prompted for interactively by
+ OpenSSL with a
+ <programlisting>
+ Enter PEM Passphrase:
+ </programlisting>
+ prompt if a TTY is available. Applications can override the client certificate
+ prompt and the handling of the <literal>sslpassword</literal> parameter by supplying
+ their own key password callback; see <xref linkend="libpq-pqsetsslkeypasshook"/>.
+ </para>
+
<para>
For instructions on creating certificates, see <xref
linkend="ssl-certificate-creation"/>.
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index fb4ceb355a..bdd6ff8ae4 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -112,7 +112,7 @@
<itemizedlist spacing="compact">
<listitem>
<para>
- <literal>user</literal> and <literal>password</literal> (specify these
+ <literal>user</literal>, <literal>password</literal> and <literal>sslpassword</literal> (specify these
in a user mapping, instead)
</para>
</listitem>
diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt
index ccec59919b..53f8ee0fc4 100644
--- a/src/interfaces/libpq/exports.txt
+++ b/src/interfaces/libpq/exports.txt
@@ -176,3 +176,7 @@ PQresultMemorySize 173
PQhostaddr 174
PQgssEncInUse 175
PQgetgssctx 176
+PQsslpassword 177
+PQsetSSLKeyPassHook 178
+PQgetSSLKeyPassHook 179
+PQdefaultSSLKeyPassHook 180
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 99051231a6..e6c9cfda01 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -353,6 +353,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"sslpassword", NULL, NULL, NULL,
+ "SSL-Client-Key-Password", "*", 20,
+ offsetof(struct pg_conn, sslpassword)},
+
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
@@ -4028,6 +4032,8 @@ freePGconn(PGconn *conn)
free(conn->target_session_attrs);
termPQExpBuffer(&conn->errorMessage);
termPQExpBuffer(&conn->workBuffer);
+ if (conn->sslpassword)
+ free(conn->sslpassword);
free(conn);
@@ -6546,6 +6552,14 @@ PQport(const PGconn *conn)
return "";
}
+char *
+PQsslpassword(const PGconn *conn)
+{
+ if (!conn)
+ return NULL;
+ return conn->sslpassword;
+}
+
char *
PQtty(const PGconn *conn)
{
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index c8dddfb5fd..cba81f63c0 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -70,6 +70,7 @@ static int initialize_SSL(PGconn *conn);
static PostgresPollingStatusType open_client_SSL(PGconn *);
static char *SSLerrmessage(unsigned long ecode);
static void SSLerrfree(char *buf);
+static int PQssl_passwd_cb(char *buf, int size, int rwflag, void *userdata);
static int my_sock_read(BIO *h, char *buf, int size);
static int my_sock_write(BIO *h, const char *buf, int size);
@@ -93,6 +94,7 @@ static long win32_ssl_create_mutex = 0;
#endif
#endif /* ENABLE_THREAD_SAFETY */
+static PQsslKeyPassHook_type PQsslKeyPassHook = NULL;
/* ------------------------------------------------------------ */
/* Procedures common to all secure sessions */
@@ -818,6 +820,26 @@ initialize_SSL(PGconn *conn)
return -1;
}
+ /*
+ * Delegate the client cert password prompt to the libpq wrapper
+ * callback if any is defined.
+ *
+ * If the application hasn't installed its own and the sslpassword
+ * parameter is non-null, we install ours now to make sure we
+ * supply PGconn->sslpassword to OpenSSL instead of letting it
+ * prompt on stdin.
+ *
+ * This will replace OpenSSL's default PEM_def_callback (which
+ * prompts on stdin), but we're only setting it for this SSL
+ * context so it's harmless.
+ */
+ if (PQsslKeyPassHook
+ || (conn->sslpassword && strlen(conn->sslpassword) > 0))
+ {
+ SSL_CTX_set_default_passwd_cb(SSL_context, PQssl_passwd_cb);
+ SSL_CTX_set_default_passwd_cb_userdata(SSL_context, conn);
+ }
+
/* Disable old protocol versions */
SSL_CTX_set_options(SSL_context, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
@@ -1123,11 +1145,29 @@ initialize_SSL(PGconn *conn)
{
char *err = SSLerrmessage(ERR_get_error());
- printfPQExpBuffer(&conn->errorMessage,
- libpq_gettext("could not load private key file \"%s\": %s\n"),
- fnbuf, err);
+ /*
+ * We'll try to load the file in DER (binary ASN.1) format, and if
+ * that fails too, report the original error. This could mask
+ * issues where there's something wrong with a DER-format cert, but
+ * we'd have to duplicate openssl's format detection to be smarter
+ * than this. We can't just probe for a leading -----BEGIN because
+ * PEM can have leading non-matching lines and blanks. OpenSSL
+ * doesn't expose its get_name(...) and its PEM routines don't
+ * differentiate between failure modes in enough detail to let us
+ * tell the difference between "not PEM, try DER" and "wrong
+ * password".
+ */
+ if (SSL_use_PrivateKey_file(conn->ssl, fnbuf, SSL_FILETYPE_ASN1) != 1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not load private key file \"%s\": %s\n"),
+ fnbuf, err);
+ SSLerrfree(err);
+ return -1;
+ }
+
SSLerrfree(err);
- return -1;
+
}
}
@@ -1580,3 +1620,54 @@ my_SSL_set_fd(PGconn *conn, int fd)
err:
return ret;
}
+
+/*
+ * This is the default handler to return a client cert password from
+ * conn->sslpassword. Apps may install it explicitly if they want to
+ * prevent openssl from ever prompting on stdin.
+ */
+int
+PQdefaultSSLKeyPassHook(char *buf, int size, PGconn *conn)
+{
+ if (conn->sslpassword)
+ {
+ if (strlen(conn->sslpassword) + 1 > size)
+ fprintf(stderr, libpq_gettext("WARNING: sslpassword truncated"));
+ strncpy(buf, conn->sslpassword, size);
+ buf[size-1] = '\0';
+ return strlen(buf);
+ }
+ else
+ {
+ buf[0] = '\0';
+ return 0;
+ }
+}
+
+PQsslKeyPassHook_type
+PQgetSSLKeyPassHook(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook(PQsslKeyPassHook_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * Supply a password to decrypt a client certificate.
+ *
+ * This must match OpenSSL type pem_passwd_cb.
+ */
+static int
+PQssl_passwd_cb(char *buf, int size, int rwflag, void *userdata)
+{
+ PGconn *conn = userdata;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(buf, size, conn);
+ else
+ return PQdefaultSSLKeyPassHook(buf, size, conn);
+}
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 5f65db30e4..4d52fd7994 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -317,6 +317,7 @@ extern char *PQpass(const PGconn *conn);
extern char *PQhost(const PGconn *conn);
extern char *PQhostaddr(const PGconn *conn);
extern char *PQport(const PGconn *conn);
+extern char *PQsslpassword(const PGconn *conn);
extern char *PQtty(const PGconn *conn);
extern char *PQoptions(const PGconn *conn);
extern ConnStatusType PQstatus(const PGconn *conn);
@@ -617,6 +618,19 @@ extern int pg_char_to_encoding(const char *name);
extern const char *pg_encoding_to_char(int encoding);
extern int pg_valid_server_encoding_id(int encoding);
+/* == in fe-secure-openssl.c === */
+
+/*
+ * Support for overriding sslpassword handling with a callback.
+ *
+ * 2ndQPostgres extension. If you need to be compatible with unpatched libpq
+ * you must dlsym() these.
+ */
+typedef int (*PQsslKeyPassHook_type)(char *buf, int size, PGconn *conn);
+extern PQsslKeyPassHook_type PQgetSSLKeyPassHook(void);
+extern void PQsetSSLKeyPassHook(PQsslKeyPassHook_type hook);
+extern int PQdefaultSSLKeyPassHook(char *buf, int size, PGconn *conn);
+
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 64468ab4da..7f5be7db7a 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -512,6 +512,8 @@ struct pg_conn
/* Buffer for receiving various parts of messages */
PQExpBufferData workBuffer; /* expansible string */
+
+ char *sslpassword; /* client key file password */
};
/* PGcancel stores all data necessary to cancel a connection. A copy of this
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index 3b53972f6f..cea5ace7aa 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -27,9 +27,14 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) $(CERTIFICATES:%=ssl/%.crt) \
ssl/both-cas-1.crt ssl/both-cas-2.crt \
ssl/root+server_ca.crt ssl/root+server.crl \
ssl/root+client_ca.crt ssl/root+client.crl \
- ssl/client+client_ca.crt
+ ssl/client+client_ca.crt ssl/client-der.key \
+ ssl/client-encrypted-pem.key ssl/client-encrypted-der.key
-# This target generates all the key and certificate files.
+# This target re-generates all the key and certificate files. Usually we just
+# use the ones that are committed to the tree without rebuilding them.
+#
+# This target will fail unless preceded by sslfiles-clean.
+#
sslfiles: $(SSLFILES)
# OpenSSL requires a directory to put all generated certificates in. We don't
@@ -90,6 +95,18 @@ ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl x509 -in ssl/temp.crt -out ssl/client-revoked.crt # to keep just the PEM cert
rm ssl/client-revoked.csr ssl/temp.crt
+# Convert the key to DER, to test our behaviour there too
+ssl/client-der.key: ssl/client.key
+ openssl rsa -in ssl/client.key -outform DER -out ssl/client-der.key
+
+# Convert the existing key to encrypted PEM (X.509 text) and DER (X.509 ASN.1) formats
+# to test libpq's support for the sslpassword= option.
+ssl/client-encrypted-pem.key: ssl/client.key
+ openssl rsa -in ssl/client.key -outform PEM -aes128 -passout 'pass:dUmmyP^#+' -out ssl/client-encrypted-pem.key
+
+ssl/client-encrypted-der.key: ssl/client.key
+ openssl rsa -in ssl/client.key -outform DER -aes128 -passout 'pass:dUmmyP^#+' -out ssl/client-encrypted-der.key
+
# Root certificate files that contains both CA certificates, for testing
# that multiple certificates can be used.
ssl/both-cas-1.crt: ssl/root_ca.crt ssl/client_ca.crt ssl/server_ca.crt
@@ -138,6 +155,7 @@ clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
$(prove_check)
diff --git a/src/test/ssl/ssl/client-der.key b/src/test/ssl/ssl/client-der.key
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/test/ssl/ssl/client-encrypted-der.key b/src/test/ssl/ssl/client-encrypted-der.key
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/test/ssl/ssl/client-encrypted-pem.key b/src/test/ssl/ssl/client-encrypted-pem.key
new file mode 100644
index 0000000000..8a3c7d6e07
--- /dev/null
+++ b/src/test/ssl/ssl/client-encrypted-pem.key
@@ -0,0 +1,18 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-128-CBC,DFCCEA1226790120AE185F8A42941296
+
+jkCJfxg36xJjsat8dyJaIaMkjAWeuJMC1XCoHjIXFbGOno6wkLH0vb+4UhUh3UgU
+hq9owmwrwWXzER7FawI1P0cCtusdXoU/35FJwe9KAY0PaozCOf5iS755y/S6/9Y3
+Jb4KBvPw/3OokYRrzMr3tPgSGY61028HPiHrnZbR3Q/eDzcQ9DlsYsvio6I6iHkC
+kQyznEH1gpRQGLHg17LZVS1Jr6Cgw7qBTgUWW++AGSdFk9YD+NdyM0ZFhyyXNoCE
+MWRzo9HbSNk3jIqBHVMxSW7AbcFWuEWwAO4ycrXzuHDY6qqwbi/phW2MS5hUKprW
+aAbNFdA1aS9FIanxVEqsvrSMyR6qsgFgo7mFvt8jKkLMphRvwi5yImxD8Ml4U4GF
+37VSfD2OG3i6GMFfscURy1GgagpOhprJsxI35kh36cI15dPrY7FKw6c3PJ7ZbZ6G
+HnsdXlDaozlqXkzd5ZtEeqm7YYzwNw7Ih0uhTq4PNiKGMWs0a+s9jUNgL+y9VmOO
+thqCzbgES2UdBVclEri/Ucdb7irT1FuFKXNxIMxgaxziJO0QJHgxYLcKzCDWvuK3
+r5jL9oOmeP98XKyvcgZ48oLiNqk1uk72sPJLo4OvMueC9g56n64g0LE9A7klOdRt
+xfI33YBG34fk489NjbAesuJM9dlwELOVk4yzjBdwV8235SF+D38VP9N4v0htNc4T
+wGnMU8NyTepfKv04LgUr0CQ7VvLNjInz36YC1ZzWf+pUjtxukA5yOCKXsNQ4vAVe
+EPksrbgfzD7ASpwHq3ZHVTQWlbrtwHdjC9RX8GE70YX+dKNpo2Y9RZsRLs1n2Xu3
+-----END RSA PRIVATE KEY-----
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index 67a3a28db6..ec58d45500 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -13,7 +13,7 @@ use SSLServer;
if ($ENV{with_openssl} eq 'yes')
{
- plan tests => 75;
+ plan tests => 85;
}
else
{
@@ -32,10 +32,17 @@ my $common_connstr;
# The client's private key must not be world-readable, so take a copy
# of the key stored in the code tree and update its permissions.
-copy("ssl/client.key", "ssl/client_tmp.key");
-chmod 0600, "ssl/client_tmp.key";
-copy("ssl/client-revoked.key", "ssl/client-revoked_tmp.key");
-chmod 0600, "ssl/client-revoked_tmp.key";
+#
+# This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+# of the tests.
+my @keys = ("client", "client-revoked", "client-der", "client-encrypted-pem", "client-encrypted-der");
+foreach my $key (@keys)
+{
+ copy("ssl/${key}.key", "ssl/${key}_tmp.key")
+ or die "couldn't copy ssl/${key}.key to ssl/${key}_tmp.key for permissions change: $!";
+ chmod 0600, "ssl/${key}_tmp.key"
+ or die "failed to change permissions on ssl/${key}_tmp.key: $!";
+}
# Also make a copy of that explicitly world-readable. We can't
# necessarily rely on the file in the source tree having those
@@ -344,11 +351,52 @@ test_connect_fails(
qr/connection requires a valid client certificate/,
"certificate authorization fails without client cert");
-# correct client cert
+# correct client cert in unencrypted PEM
test_connect_ok(
$common_connstr,
"user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
- "certificate authorization succeeds with correct client cert");
+ "certificate authorization succeeds with correct client cert in PEM format");
+
+# correct client cert in unencrypted DER
+test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-der_tmp.key",
+ "certificate authorization succeeds with correct client cert in DER format");
+
+# correct client cert in encrypted PEM
+test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted PEM format");
+
+# correct client cert in encrypted DER
+test_connect_ok(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-der_tmp.key sslpassword='dUmmyP^#+'",
+ "certificate authorization succeeds with correct client cert in encrypted DER format");
+
+# correct client cert in encrypted PEM with wrong password
+test_connect_fails(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='wrong'",
+ qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": bad decrypt\E!,
+ "certificate authorization fails with correct client cert and wrong password in encrypted PEM format");
+
+# correct client cert in encrypted PEM with empty password
+test_connect_fails(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key sslpassword=''",
+ qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": processing error\E!,
+ "certificate authorization fails with correct client cert and empty password in encrypted PEM format",
+ close_stdin => 1);
+
+# correct client cert in encrypted PEM with no password
+test_connect_fails(
+ $common_connstr,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key",
+ qr!\Qprivate key file "ssl/client-encrypted-pem_tmp.key": processing error\E!,
+ "certificate authorization fails with correct client cert and no password in encrypted PEM format",
+ close_stdin => 1);
# pg_stat_ssl
command_like(
@@ -436,5 +484,7 @@ test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt",
qr/SSL error/, "intermediate client certificate is missing");
# clean up
-unlink("ssl/client_tmp.key", "ssl/client_wrongperms_tmp.key",
- "ssl/client-revoked_tmp.key");
+foreach my $key (@keys)
+{
+ unlink("ssl/${key}_tmp.key");
+}
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSLServer.pm
index 005955a2ff..a081cc3ab2 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSLServer.pm
@@ -66,7 +66,7 @@ sub test_connect_fails
{
local $Test::Builder::Level = $Test::Builder::Level + 1;
- my ($common_connstr, $connstr, $expected_stderr, $test_name) = @_;
+ my ($common_connstr, $connstr, $expected_stderr, $test_name, %kwargs) = @_;
my $cmd = [
'psql', '-X', '-A', '-t', '-c',
@@ -74,7 +74,18 @@ sub test_connect_fails
'-d', "$common_connstr $connstr"
];
- command_fails_like($cmd, $expected_stderr, $test_name);
+ my @extra_ipcrun_opts = ();
+ if ($kwargs{'close_stdin'})
+ {
+ my $eof_in = "\x04";
+ # We use IPC::Run's pty support here because OpenSSL likes to use
+ # /dev/tty to write its prompt and read the result instead of using
+ # stdio. So we can't stop it blocking the test just by closing stdin.
+ push(@extra_ipcrun_opts, '<pty<', \$eof_in);
+ }
+
+ command_fails_like($cmd, $expected_stderr, $test_name,
+ extra_ipcrun_opts => \@extra_ipcrun_opts);
return;
}