[prev in list] [next in list] [prev in thread] [next in thread]
List: postgresql-hackers
Subject: Re: Support for NSS as a libpq TLS backend
From: Andrew Dunstan <andrew.dunstan () 2ndquadrant ! com>
Date: 2020-07-31 20:44:46
Message-ID: b36484f9-8632-8f92-7381-efa96e87882b () 2ndQuadrant ! com
[Download RAW message or body]
On 7/15/20 6:18 PM, Daniel Gustafsson wrote:
>> On 15 Jul 2020, at 20:35, Andrew Dunstan <andrew.dunstan@2ndquadrant.com> wrote:
>>
>> On 5/15/20 4:46 PM, Daniel Gustafsson wrote:
>>> My plan is to keep hacking at this to have it reviewable for the 14 cycle, so
>>> if anyone has an interest in NSS, then I would love to hear feedback on how it
>>> works (and doesn't work).
>> I'll be happy to help, particularly with Windows support and with some
>> of the callback stuff I've had a hand in.
> That would be fantastic, thanks! The password callback handling is still a
> TODO so feel free to take a stab at that since you have a lot of context on
> there.
>
> For Windows, I've include USE_NSS in Solution.pm as Thomas pointed out in this
> thread, but that was done blind as I've done no testing on Windows yet.
>
OK, here is an update of your patch that compiles and runs against NSS
under Windows (VS2019).
In addition to some work that was missing in src/tools/msvc, I had to
make a few adjustments, including:
* strtok_r() isn't available on Windows. We don't use it elsewhere in
the postgres code, and it seemed unnecessary to have reentrant calls
here, so I just replaced it with equivalent strtok() calls.
* We were missing an NSS implementation of
pgtls_verify_peer_name_matches_certificate_guts(). I supplied a
dummy that's enough to get it building cleanly, but that needs to be
filled in properly.
There is still plenty of work to go, but this seemed a sufficient
milestone to report progress on.
cheers
andrew
--
Andrew Dunstan https://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
["0001-WIP-Support-libnss-for-as-TLS-backend-v6.patch" (text/x-patch)]
From 4da394fb8cb7d65f3bfe8fa244e347a1323d14e5 Mon Sep 17 00:00:00 2001
From: Andrew Dunstan <andrew.dunstan@2ndquadrant.com>
Date: Fri, 31 Jul 2020 19:40:26 +0000
Subject: [PATCH] WIP Support libnss as TLS backend v6
---
configure | 211 +++++
configure.ac | 30 +
contrib/Makefile | 2 +-
contrib/sslinfo/sslinfo.c | 164 ++--
doc/src/sgml/sslinfo.sgml | 14 +-
src/Makefile.global.in | 10 +
src/backend/libpq/Makefile | 4 +
src/backend/libpq/auth.c | 7 +
src/backend/libpq/be-secure-nss.c | 1032 +++++++++++++++++++++++++
src/backend/libpq/be-secure-openssl.c | 16 +-
src/backend/libpq/be-secure.c | 3 +
src/backend/utils/misc/guc.c | 20 +-
src/include/common/pg_nss.h | 141 ++++
src/include/libpq/libpq-be.h | 9 +-
src/include/libpq/libpq.h | 3 +
src/include/pg_config.h.in | 3 +
src/include/pg_config_manual.h | 5 +-
src/interfaces/libpq/Makefile | 4 +
src/interfaces/libpq/fe-connect.c | 4 +
src/interfaces/libpq/fe-secure-nss.c | 975 +++++++++++++++++++++++
src/interfaces/libpq/fe-secure.c | 5 +-
src/interfaces/libpq/libpq-fe.h | 11 +
src/interfaces/libpq/libpq-int.h | 5 +
src/test/Makefile | 2 +-
src/test/ssl/Makefile | 172 +++++
src/test/ssl/t/001_ssltests.pl | 278 +++----
src/test/ssl/t/002_scram.pl | 4 +-
src/test/ssl/t/SSL/Backend/NSS.pm | 64 ++
src/test/ssl/t/SSL/Backend/OpenSSL.pm | 103 +++
src/test/ssl/t/SSLServer.pm | 64 +-
src/tools/msvc/Install.pm | 3 +-
src/tools/msvc/Mkvcbuild.pm | 29 +-
src/tools/msvc/Solution.pm | 20 +
src/tools/msvc/config_default.pl | 1 +
34 files changed, 3168 insertions(+), 250 deletions(-)
create mode 100644 src/backend/libpq/be-secure-nss.c
create mode 100644 src/include/common/pg_nss.h
create mode 100644 src/interfaces/libpq/fe-secure-nss.c
create mode 100644 src/test/ssl/t/SSL/Backend/NSS.pm
create mode 100644 src/test/ssl/t/SSL/Backend/OpenSSL.pm
diff --git a/configure b/configure
index cb8fbe1051..8b7d98c2ab 100755
--- a/configure
+++ b/configure
@@ -711,6 +711,7 @@ with_uuid
with_readline
with_systemd
with_selinux
+with_nss
with_openssl
with_ldap
with_krb_srvnam
@@ -856,6 +857,7 @@ with_bsd_auth
with_ldap
with_bonjour
with_openssl
+with_nss
with_selinux
with_systemd
with_readline
@@ -1558,6 +1560,7 @@ Optional Packages:
--with-ldap build with LDAP support
--with-bonjour build with Bonjour support
--with-openssl build with OpenSSL support
+ --with-nss build with NSS support
--with-selinux build with SELinux support
--with-systemd build with systemd support
--without-readline do not use GNU Readline nor BSD Libedit for editing
@@ -8100,6 +8103,41 @@ fi
$as_echo "$with_openssl" >&6; }
+#
+# LibNSS
+#
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to build with NSS support" \
>&5 +$as_echo_n "checking whether to build with NSS support... " >&6; }
+
+
+
+# Check whether --with-nss was given.
+if test "${with_nss+set}" = set; then :
+ withval=$with_nss;
+ case $withval in
+ yes)
+
+$as_echo "#define USE_NSS 1" >>confdefs.h
+
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-nss option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_nss=no
+
+fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $with_nss" >&5
+$as_echo "$with_nss" >&6; }
+
+
#
# SELinux
#
@@ -12174,6 +12212,9 @@ fi
fi
if test "$with_openssl" = yes ; then
+ if test x"$with_nss" = x"yes" ; then
+ as_fn_error $? "multiple SSL backends cannot be enabled simultaneously\"" \
"$LINENO" 5 + fi
# Minimum required OpenSSL version is 1.0.1
$as_echo "#define OPENSSL_API_COMPAT 0x10001000L" >>confdefs.h
@@ -12436,6 +12477,157 @@ done
fi
+if test "$with_nss" = yes ; then
+ if test x"$with_openssl" = x"yes" ; then
+ as_fn_error $? "multiple SSL backends cannot be enabled simultaneously\"" \
"$LINENO" 5 + fi
+ CLEANLDFLAGS="$LDFLAGS"
+ # TODO: document this set of LDFLAGS
+ LDFLAGS="-lssl3 -lsmime3 -lnss3 -lplds4 -lplc4 -lnspr4 $LDFLAGS"
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_VersionRangeSet in \
-lnss3" >&5 +$as_echo_n "checking for SSL_VersionRangeSet in -lnss3... " >&6; }
+if ${ac_cv_lib_nss3_SSL_VersionRangeSet+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnss3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_VersionRangeSet ();
+int
+main ()
+{
+return SSL_VersionRangeSet ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nss3_SSL_VersionRangeSet=yes
+else
+ ac_cv_lib_nss3_SSL_VersionRangeSet=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: \
$ac_cv_lib_nss3_SSL_VersionRangeSet" >&5 +$as_echo \
"$ac_cv_lib_nss3_SSL_VersionRangeSet" >&6; } +if test \
"x$ac_cv_lib_nss3_SSL_VersionRangeSet" = xyes; then : + cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBNSS3 1
+_ACEOF
+
+ LIBS="-lnss3 $LIBS"
+
+else
+ as_fn_error $? "library 'nss3' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PR_GetDefaultIOMethods in \
-lnspr4" >&5 +$as_echo_n "checking for PR_GetDefaultIOMethods in -lnspr4... " >&6; }
+if ${ac_cv_lib_nspr4_PR_GetDefaultIOMethods+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lnspr4 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char PR_GetDefaultIOMethods ();
+int
+main ()
+{
+return PR_GetDefaultIOMethods ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=yes
+else
+ ac_cv_lib_nspr4_PR_GetDefaultIOMethods=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: \
$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&5 +$as_echo \
"$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" >&6; } +if test \
"x$ac_cv_lib_nspr4_PR_GetDefaultIOMethods" = xyes; then : + cat >>confdefs.h \
<<_ACEOF +#define HAVE_LIBNSPR4 1
+_ACEOF
+
+ LIBS="-lnspr4 $LIBS"
+
+else
+ as_fn_error $? "library 'nspr4' is required for NSS" "$LINENO" 5
+fi
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_GetImplementedCiphers in \
-lssl3" >&5 +$as_echo_n "checking for SSL_GetImplementedCiphers in -lssl3... " >&6; }
+if ${ac_cv_lib_ssl3_SSL_GetImplementedCiphers+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-lssl3 $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char SSL_GetImplementedCiphers ();
+int
+main ()
+{
+return SSL_GetImplementedCiphers ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=yes
+else
+ ac_cv_lib_ssl3_SSL_GetImplementedCiphers=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: \
$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&5 +$as_echo \
"$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" >&6; } +if test \
"x$ac_cv_lib_ssl3_SSL_GetImplementedCiphers" = xyes; then : + cat >>confdefs.h \
<<_ACEOF +#define HAVE_LIBSSL3 1
+_ACEOF
+
+ LIBS="-lssl3 $LIBS"
+
+else
+ as_fn_error $? "library 'ssl3' is required for NSS" "$LINENO" 5
+fi
+
+ LDFLAGS="$CLEANLDFLAGS"
+fi
+
if test "$with_pam" = yes ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for pam_start in -lpam" >&5
$as_echo_n "checking for pam_start in -lpam... " >&6; }
@@ -13338,6 +13530,25 @@ else
fi
+fi
+
+if test "$with_nss" = yes ; then
+ ac_fn_c_check_header_mongrel "$LINENO" "ssl.h" "ac_cv_header_ssl_h" \
"$ac_includes_default" +if test "x$ac_cv_header_ssl_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <ssl.h> is required for NSS" "$LINENO" 5
+fi
+
+
+ ac_fn_c_check_header_mongrel "$LINENO" "nss.h" "ac_cv_header_nss_h" \
"$ac_includes_default" +if test "x$ac_cv_header_nss_h" = xyes; then :
+
+else
+ as_fn_error $? "header file <nss.h> is required for NSS" "$LINENO" 5
+fi
+
+
fi
if test "$with_pam" = yes ; then
diff --git a/configure.ac b/configure.ac
index eb2c731b58..23c07cabce 100644
--- a/configure.ac
+++ b/configure.ac
@@ -856,6 +856,15 @@ PGAC_ARG_BOOL(with, openssl, no, [build with OpenSSL support],
AC_MSG_RESULT([$with_openssl])
AC_SUBST(with_openssl)
+#
+# LibNSS
+#
+AC_MSG_CHECKING([whether to build with NSS support])
+PGAC_ARG_BOOL(with, nss, no, [build with NSS support],
+ [AC_DEFINE([USE_NSS], 1, [Define to build with NSS support. \
(--with-nss)])]) +AC_MSG_RESULT([$with_nss])
+AC_SUBST(with_nss)
+
#
# SELinux
#
@@ -1205,6 +1214,9 @@ if test "$with_gssapi" = yes ; then
fi
if test "$with_openssl" = yes ; then
+ if test x"$with_nss" = x"yes" ; then
+ AC_MSG_ERROR([multiple SSL backends cannot be enabled simultaneously"])
+ fi
dnl Order matters!
# Minimum required OpenSSL version is 1.0.1
AC_DEFINE(OPENSSL_API_COMPAT, [0x10001000L],
@@ -1230,6 +1242,19 @@ if test "$with_openssl" = yes ; then
AC_CHECK_FUNCS([CRYPTO_lock])
fi
+if test "$with_nss" = yes ; then
+ if test x"$with_openssl" = x"yes" ; then
+ AC_MSG_ERROR([multiple SSL backends cannot be enabled simultaneously"])
+ fi
+ CLEANLDFLAGS="$LDFLAGS"
+ # TODO: document this set of LDFLAGS
+ LDFLAGS="-lssl3 -lsmime3 -lnss3 -lplds4 -lplc4 -lnspr4 $LDFLAGS"
+ AC_CHECK_LIB(nss3, SSL_VersionRangeSet, [], [AC_MSG_ERROR([library 'nss3' is \
required for NSS])]) + AC_CHECK_LIB(nspr4, PR_GetDefaultIOMethods, [], \
[AC_MSG_ERROR([library 'nspr4' is required for NSS])]) + AC_CHECK_LIB(ssl3, \
SSL_GetImplementedCiphers, [], [AC_MSG_ERROR([library 'ssl3' is required for NSS])]) \
+ LDFLAGS="$CLEANLDFLAGS" +fi
+
if test "$with_pam" = yes ; then
AC_CHECK_LIB(pam, pam_start, [], [AC_MSG_ERROR([library 'pam' is required for \
PAM])]) fi
@@ -1405,6 +1430,11 @@ if test "$with_openssl" = yes ; then
AC_CHECK_HEADER(openssl/err.h, [], [AC_MSG_ERROR([header file <openssl/err.h> is \
required for OpenSSL])]) fi
+if test "$with_nss" = yes ; then
+ AC_CHECK_HEADER(ssl.h, [], [AC_MSG_ERROR([header file <ssl.h> is required for \
NSS])]) + AC_CHECK_HEADER(nss.h, [], [AC_MSG_ERROR([header file <nss.h> is required \
for NSS])]) +fi
+
if test "$with_pam" = yes ; then
AC_CHECK_HEADERS(security/pam_appl.h, [],
[AC_CHECK_HEADERS(pam/pam_appl.h, [],
diff --git a/contrib/Makefile b/contrib/Makefile
index 1846d415b6..cef7bf7f61 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -50,7 +50,7 @@ SUBDIRS = \
unaccent \
vacuumlo
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),yes)
SUBDIRS += sslinfo
else
ALWAYS_SUBDIRS += sslinfo
diff --git a/contrib/sslinfo/sslinfo.c b/contrib/sslinfo/sslinfo.c
index 5ba3988e27..84bb2c65b8 100644
--- a/contrib/sslinfo/sslinfo.c
+++ b/contrib/sslinfo/sslinfo.c
@@ -9,9 +9,11 @@
#include "postgres.h"
+#ifdef USE_OPENSSL
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/asn1.h>
+#endif
#include "access/htup_details.h"
#include "funcapi.h"
@@ -21,8 +23,8 @@
PG_MODULE_MAGIC;
+#ifdef USE_OPENSSL
static Datum X509_NAME_field_to_text(X509_NAME *name, text *fieldName);
-static Datum X509_NAME_to_text(X509_NAME *name);
static Datum ASN1_STRING_to_text(ASN1_STRING *str);
/*
@@ -32,6 +34,7 @@ typedef struct
{
TupleDesc tupdesc;
} SSLExtensionInfoContext;
+#endif
/*
* Indicates whether current session uses SSL
@@ -54,9 +57,16 @@ PG_FUNCTION_INFO_V1(ssl_version);
Datum
ssl_version(PG_FUNCTION_ARGS)
{
- if (MyProcPort->ssl == NULL)
+ const char *version;
+
+ if (!MyProcPort->ssl_in_use)
+ PG_RETURN_NULL();
+
+ version = be_tls_get_version(MyProcPort);
+ if (version == NULL)
PG_RETURN_NULL();
- PG_RETURN_TEXT_P(cstring_to_text(SSL_get_version(MyProcPort->ssl)));
+
+ PG_RETURN_TEXT_P(cstring_to_text(version));
}
@@ -67,9 +77,16 @@ PG_FUNCTION_INFO_V1(ssl_cipher);
Datum
ssl_cipher(PG_FUNCTION_ARGS)
{
- if (MyProcPort->ssl == NULL)
+ const char *cipher;
+
+ if (!MyProcPort->ssl_in_use)
PG_RETURN_NULL();
- PG_RETURN_TEXT_P(cstring_to_text(SSL_get_cipher(MyProcPort->ssl)));
+
+ cipher = be_tls_get_cipher(MyProcPort);
+ if (cipher == NULL)
+ PG_RETURN_NULL();
+
+ PG_RETURN_TEXT_P(cstring_to_text(cipher));
}
@@ -83,7 +100,7 @@ PG_FUNCTION_INFO_V1(ssl_client_cert_present);
Datum
ssl_client_cert_present(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(MyProcPort->peer != NULL);
+ PG_RETURN_BOOL(MyProcPort->peer_cert_valid);
}
@@ -99,29 +116,26 @@ PG_FUNCTION_INFO_V1(ssl_client_serial);
Datum
ssl_client_serial(PG_FUNCTION_ARGS)
{
+ char decimal[NAMEDATALEN];
Datum result;
- Port *port = MyProcPort;
- X509 *peer = port->peer;
- ASN1_INTEGER *serial = NULL;
- BIGNUM *b;
- char *decimal;
- if (!peer)
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+ PG_RETURN_NULL();
+
+ be_tls_get_peer_serial(MyProcPort, decimal, NAMEDATALEN);
+
+ if (!*decimal)
PG_RETURN_NULL();
- serial = X509_get_serialNumber(peer);
- b = ASN1_INTEGER_to_BN(serial, NULL);
- decimal = BN_bn2dec(b);
- BN_free(b);
result = DirectFunctionCall3(numeric_in,
CStringGetDatum(decimal),
ObjectIdGetDatum(0),
Int32GetDatum(-1));
- OPENSSL_free(decimal);
return result;
}
+#ifdef USE_OPENSSL
/*
* Converts OpenSSL ASN1_STRING structure into text
*
@@ -228,7 +242,7 @@ ssl_client_dn_field(PG_FUNCTION_ARGS)
text *fieldname = PG_GETARG_TEXT_PP(0);
Datum result;
- if (!(MyProcPort->peer))
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
PG_RETURN_NULL();
result = X509_NAME_field_to_text(X509_get_subject_name(MyProcPort->peer), \
fieldname); @@ -273,76 +287,23 @@ ssl_issuer_field(PG_FUNCTION_ARGS)
else
return result;
}
+#endif /* USE_OPENSSL */
-
-/*
- * Equivalent of X509_NAME_oneline that respects encoding
- *
- * This function converts X509_NAME structure to the text variable
- * converting all textual data into current database encoding.
- *
- * Parameter: X509_NAME *name X509_NAME structure to be converted
- *
- * Returns: text datum which contains string representation of
- * X509_NAME
- */
-static Datum
-X509_NAME_to_text(X509_NAME *name)
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_client_dn_field);
+Datum
+ssl_client_dn_field(PG_FUNCTION_ARGS)
{
- BIO *membuf = BIO_new(BIO_s_mem());
- int i,
- nid,
- count = X509_NAME_entry_count(name);
- X509_NAME_ENTRY *e;
- ASN1_STRING *v;
- const char *field_name;
- size_t size;
- char nullterm;
- char *sp;
- char *dp;
- text *result;
-
- if (membuf == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_OUT_OF_MEMORY),
- errmsg("could not create OpenSSL BIO structure")));
-
- (void) BIO_set_close(membuf, BIO_CLOSE);
- for (i = 0; i < count; i++)
- {
- e = X509_NAME_get_entry(name, i);
- nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(e));
- if (nid == NID_undef)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("could not get NID for ASN1_OBJECT object")));
- v = X509_NAME_ENTRY_get_data(e);
- field_name = OBJ_nid2sn(nid);
- if (field_name == NULL)
- field_name = OBJ_nid2ln(nid);
- if (field_name == NULL)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("could not convert NID %d to an ASN1_OBJECT structure", nid)));
- BIO_printf(membuf, "/%s=", field_name);
- ASN1_STRING_print_ex(membuf, v,
- ((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
- | ASN1_STRFLGS_UTF8_CONVERT));
- }
-
- /* ensure null termination of the BIO's content */
- nullterm = '\0';
- BIO_write(membuf, &nullterm, 1);
- size = BIO_get_mem_data(membuf, &sp);
- dp = pg_any_to_server(sp, size - 1, PG_UTF8);
- result = cstring_to_text(dp);
- if (dp != sp)
- pfree(dp);
- if (BIO_free(membuf) != 1)
- elog(ERROR, "could not free OpenSSL BIO structure");
+ PG_RETURN_NULL();
+}
- PG_RETURN_TEXT_P(result);
+PG_FUNCTION_INFO_V1(ssl_issuer_field);
+Datum
+ssl_issuer_field(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
}
+#endif /* USE_NSS */
/*
@@ -358,9 +319,17 @@ PG_FUNCTION_INFO_V1(ssl_client_dn);
Datum
ssl_client_dn(PG_FUNCTION_ARGS)
{
- if (!(MyProcPort->peer))
+ char subject[NAMEDATALEN];
+
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+ PG_RETURN_NULL();
+
+ be_tls_get_peer_subject_name(MyProcPort, subject, NAMEDATALEN);
+
+ if (!*subject)
PG_RETURN_NULL();
- return X509_NAME_to_text(X509_get_subject_name(MyProcPort->peer));
+
+ PG_RETURN_TEXT_P(cstring_to_text(subject));
}
@@ -377,12 +346,21 @@ PG_FUNCTION_INFO_V1(ssl_issuer_dn);
Datum
ssl_issuer_dn(PG_FUNCTION_ARGS)
{
- if (!(MyProcPort->peer))
+ char issuer[NAMEDATALEN];
+
+ if (!MyProcPort->ssl_in_use || !MyProcPort->peer_cert_valid)
+ PG_RETURN_NULL();
+
+ be_tls_get_peer_issuer_name(MyProcPort, issuer, NAMEDATALEN);
+
+ if (!*issuer)
PG_RETURN_NULL();
- return X509_NAME_to_text(X509_get_issuer_name(MyProcPort->peer));
+
+ PG_RETURN_TEXT_P(cstring_to_text(issuer));
}
+#ifdef USE_OPENSSL
/*
* Returns information about available SSL extensions.
*
@@ -516,3 +494,13 @@ ssl_extension_info(PG_FUNCTION_ARGS)
/* All done */
SRF_RETURN_DONE(funcctx);
}
+#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+PG_FUNCTION_INFO_V1(ssl_extension_info);
+Datum
+ssl_extension_info(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_NULL();
+}
+#endif /* USE_NSS */
diff --git a/doc/src/sgml/sslinfo.sgml b/doc/src/sgml/sslinfo.sgml
index c237d4ba95..d15a206d2d 100644
--- a/doc/src/sgml/sslinfo.sgml
+++ b/doc/src/sgml/sslinfo.sgml
@@ -22,7 +22,8 @@
<para>
This extension won't build at all unless the installation was
- configured with <literal>--with-openssl</literal>.
+ configured with SSL support, such as <literal>--with-openssl</literal>
+ or <literal>--with-nss</literal>.
</para>
<sect2>
@@ -54,7 +55,7 @@
<listitem>
<para>
Returns the name of the protocol used for the SSL connection (e.g. TLSv1.0
- TLSv1.1, or TLSv1.2).
+ TLSv1.1, TLSv1.2 or TLSv1.3).
</para>
</listitem>
</varlistentry>
@@ -208,6 +209,9 @@ emailAddress
the X.500 and X.509 standards, so you cannot just assign arbitrary
meaning to them.
</para>
+ <para>
+ This function is only available when using OpenSSL.
+ </para>
</listitem>
</varlistentry>
@@ -223,6 +227,9 @@ emailAddress
Same as <function>ssl_client_dn_field</function>, but for the certificate \
issuer rather than the certificate subject.
</para>
+ <para>
+ This function is only available when using OpenSSL.
+ </para>
</listitem>
</varlistentry>
@@ -238,6 +245,9 @@ emailAddress
Provide information about extensions of client certificate: extension name,
extension value, and if it is a critical extension.
</para>
+ <para>
+ This function is only available when using OpenSSL.
+ </para>
</listitem>
</varlistentry>
</variablelist>
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 9a6265b3a0..2f25c51c6c 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -184,6 +184,7 @@ with_perl = @with_perl@
with_python = @with_python@
with_tcl = @with_tcl@
with_openssl = @with_openssl@
+with_nss = @with_nss@
with_readline = @with_readline@
with_selinux = @with_selinux@
with_systemd = @with_systemd@
@@ -232,6 +233,15 @@ CLANG = @CLANG@
BITCODE_CFLAGS = @BITCODE_CFLAGS@
BITCODE_CXXFLAGS = @BITCODE_CXXFLAGS@
+ifeq ($(with_openssl),yes)
+with_ssl = yes
+else ifeq ($(with_nss),yes)
+with_ssl = yes
+else
+with_ssl = no
+endif
+
+
##########################################################################
#
# Programs and flags
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index efc5ef760a..191266a426 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -30,6 +30,10 @@ OBJS = \
ifeq ($(with_openssl),yes)
OBJS += be-secure-openssl.o
+else
+ifeq ($(with_nss),yes)
+OBJS += be-secure-nss.o
+endif
endif
ifeq ($(with_gssapi),yes)
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 02b6c3f127..8f4197d002 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -2870,7 +2870,14 @@ CheckCertAuth(Port *port)
{
int status_check_usermap = STATUS_ERROR;
+#if defined(USE_OPENSSL)
Assert(port->ssl);
+#elif defined(USE_NSS)
+ /* TODO: should we rename pr_fd to ssl, to keep consistency? */
+ Assert(port->pr_fd);
+#else
+ Assert(false);
+#endif
/* Make sure we have received a username in the certificate */
if (port->peer_cn == NULL ||
diff --git a/src/backend/libpq/be-secure-nss.c b/src/backend/libpq/be-secure-nss.c
new file mode 100644
index 0000000000..00ae054920
--- /dev/null
+++ b/src/backend/libpq/be-secure-nss.c
@@ -0,0 +1,1032 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-nss.c
+ * functions for supporting NSS as a TLS backend
+ *
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <sys/stat.h>
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header fils, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * The definitions are however not actually used in NSPR at all, and are only
+ * intended for what seems to be backwards compatibility for apps written
+ * against old versions of NSPR. The following comment is in the referenced
+ * file, and was added in 1998:
+ *
+ * This section typedefs the old 'native' types to the new PR<type>s.
+ * These definitions are scheduled to be eliminated at the earliest
+ * possible time. The NSPR API is implemented and documented using
+ * the new definitions.
+ *
+ * As there is no opt-out from pulling in these typedefs, we define the guard
+ * for the file to exclude it. This is incredibly ugly, but seems to be about
+ * the only way around it.
+ */
+#define PROTYPES_H
+#include <nspr.h>
+#undef PROTYPES_H
+#include <nss.h>
+#include <prio.h>
+#include <ssl.h>
+#include <sslerr.h>
+#include <secerr.h>
+#include <sslproto.h>
+#include <prtypes.h>
+#include <pk11pub.h>
+#include <secitem.h>
+#include <secport.h>
+#include <secder.h>
+#include <certdb.h>
+#include <base64.h>
+#include <cert.h>
+#include <prerror.h>
+#include <keyhi.h>
+
+typedef struct
+{
+ enum
+ {
+ PW_NONE = 0,
+ PW_FROMFILE = 1,
+ PW_PLAINTEXT = 2,
+ PW_EXTERNAL = 3
+ } source;
+ char *data;
+} secuPWData;
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR has removed the definition for some reasone, make sure to put ours
+ * back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and postgres"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+#include "common/pg_nss.h"
+#include "lib/stringinfo.h"
+#include "libpq/libpq.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/fd.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+static PRDescIdentity pr_id;
+
+static PRIOMethods pr_iomethods;
+static NSSInitContext * nss_context = NULL;
+static SSLVersionRange desired_sslver;
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+/* NSS IO layer callback overrides */
+static PRInt32 pg_ssl_read(PRFileDesc * fd, void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+static PRInt32 pg_ssl_write(PRFileDesc * fd, const void *buf, PRInt32 amount,
+ PRIntn flags, PRIntervalTime timeout);
+/* Utility functions */
+static PRFileDesc * init_iolayer(Port *port, int loglevel);
+static uint16 ssl_protocol_version_to_nss(int v, const char *guc_name);
+
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static char *ssl_protocol_version_to_string(int v);
+static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd,
+ PRBool checksig, PRBool isServer);
+static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+
+/* ------------------------------------------------------------ */
+/* Public interface */
+/* ------------------------------------------------------------ */
+
+static char *
+ssl_passphrase_callback(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ return pstrdup("");
+}
+
+/*
+ * be_tls_init
+ * Initialize the nss TLS library in the postmaster
+ *
+ * The majority of the setup needs to happen in be_tls_open_server since the
+ * NSPR initialization must happen after the forking of the backend. We could
+ * potentially move some parts in under !isServerStart, but so far this is the
+ * separation chosen.
+ */
+int
+be_tls_init(bool isServerStart)
+{
+ SECStatus status;
+ SSLVersionRange supported_sslver;
+
+ /*
+ * Set up the connection cache for multi-processing application behavior.
+ * If we are in ServerStart then we initialize the cache. If the server is
+ * already started, we inherit the cache such that it can be used for
+ * connections. Calling SSL_ConfigMPServerSIDCache sets an environment
+ * variable which contains enough information for the forked child to know
+ * how to access it. Passing NULL to SSL_InheritMPServerSIDCache will
+ * make the forked child look it up by the default name SSL_INHERITANCE,
+ * if env vars aren't inherited then the contents of the variable can be
+ * passed instead.
+ */
+ if (isServerStart)
+ {
+ /*
+ * SSLv2 and SSLv3 are disabled in this TLS backend, but when setting
+ * up the required session cache for NSS we still must supply timeout
+ * values for v2 and The minimum allowed value for both is 5 seconds,
+ * so opt for that in both cases (the defaults being 100 seconds and
+ * 24 hours).
+ *
+ * Passing NULL as the directory for the session cache will default to
+ * using /tmp on UNIX and \\temp on Windows. Deciding if we want to
+ * keep closer control on this directory is left as a TODO.
+ */
+ status = SSL_ConfigMPServerSIDCache(MaxConnections, 5, 5, NULL);
+ if (status != SECSuccess)
+ ereport(FATAL,
+ (errmsg("unable to set up TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ }
+ else
+ {
+ status = SSL_InheritMPServerSIDCache(NULL);
+ if (status != SECSuccess)
+ {
+ ereport(LOG,
+ (errmsg("unable to connect to TLS connection cache: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return -1;
+ }
+ }
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("no certificate database specified")));
+ goto error;
+ }
+
+ /*
+ * We check for the desired TLS version range here, even though we cannot
+ * set it until be_open_server such that we can be compatible with how the
+ * OpenSSL backend reports errors for incompatible range configurations.
+ * Set either the default supported TLS version range, or the configured
+ * range from ssl_min_protocol_version and ssl_max_protocol version. In
+ * case the user hasn't defined the maximum allowed version we fall back
+ * to the highest version TLS that the library supports.
+ */
+ if (SSL_VersionRangeGetSupported(ssl_variant_stream, &supported_sslver) != \
SECSuccess) + {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("unable to get default protocol support from NSS")));
+ goto error;
+ }
+
+ /*
+ * Set the fallback versions for the TLS protocol version range to a
+ * combination of our minimal requirement and the library maximum.
+ */
+ desired_sslver.min = SSL_LIBRARY_VERSION_TLS_1_0;
+ desired_sslver.max = supported_sslver.max;
+
+ if (ssl_min_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_min_protocol_version,
+ "ssl_min_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_min_protocol_version",
+ GetConfigOption("ssl_min_protocol_version",
+ false, false))));
+ goto error;
+ }
+
+ if (ver > 0)
+ desired_sslver.min = ver;
+ }
+
+ if (ssl_max_protocol_version)
+ {
+ int ver = ssl_protocol_version_to_nss(ssl_max_protocol_version,
+ "ssl_max_protocol_version");
+
+ if (ver == -1)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("\"%s\" setting \"%s\" not supported by this build",
+ "ssl_max_protocol_version",
+ GetConfigOption("ssl_max_protocol_version",
+ false, false))));
+ goto error;
+ }
+ if (ver > 0)
+ desired_sslver.max = ver;
+
+ if (ver < desired_sslver.min)
+ {
+ ereport(isServerStart ? FATAL : LOG,
+ (errmsg("could not set SSL protocol version range"),
+ errdetail("\"%s\" cannot be higher than \"%s\"",
+ "ssl_min_protocol_version",
+ "ssl_max_protocol_version")));
+ goto error;
+ }
+ }
+
+ return 0;
+error:
+ return -1;
+}
+
+int
+be_tls_open_server(Port *port)
+{
+ SECStatus status;
+ PRFileDesc *model;
+ PRFileDesc *pr_fd;
+ PRFileDesc *layer;
+ CERTCertificate *server_cert;
+ SECKEYPrivateKey *private_key;
+ CERTSignedCrl *crl;
+ SECItem crlname;
+ secuPWData pwdata = {PW_NONE, 0}; /* TODO: This is a bogus callback */
+ char *cert_database;
+ NSSInitParameters params;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. The documentation doesn't however clarify
+ * from which version this is holds true, so let's perform the potentially
+ * superfluous initialization anyways to avoid crashing on older versions
+ * of NSPR, as there is no difference in overhead. The NSS documentation
+ * still states that PR_Init must be called in some way (implicitly or
+ * explicitly).
+ *
+ * The below parameters are what the implicit initialization would've done
+ * for us, and should work even for older versions where it might not be
+ * done automatically. The last parameter, maxPTDs, is set to various
+ * values in other codebases, but has been unused since NSPR 2.1 which was
+ * released sometime in 1998.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0 /* maxPTDs */ );
+
+ /*
+ * The certificate path (configdir) must contain a valid NSS database. If
+ * the certificate path isn't a valid directory, NSS will fall back on the
+ * system certificate database. If the certificate path is a directory but
+ * is empty then the initialization will fail. On the client side this can
+ * be allowed for any sslmode but the verify-xxx ones.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=728562 For the server side
+ * we wont allow this to fail however, as we require the certificate and
+ * key to exist.
+ *
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, '\0', sizeof(params));
+ params.length = sizeof(params);
+
+ if (!ssl_database || strlen(ssl_database) == 0)
+ ereport(FATAL,
+ (errmsg("no certificate database specified")));
+
+ cert_database = psprintf("sql:%s", ssl_database);
+ nss_context = NSS_InitContext(cert_database, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ pfree(cert_database);
+
+ if (!nss_context)
+ ereport(FATAL,
+ (errmsg("unable to read certificate database \"%s\": %s",
+ ssl_database, pg_SSLerrmessage(PR_GetError()))));
+
+ /*
+ * Set the passphrase callback which will be used both to obtain the
+ * passphrase from the user, as well as by NSS to obtain the phrase
+ * repeatedly.
+ *
+ * TODO: Figure this out - do note that we are setting another password
+ * callback below for cert/key as well. Need to make sense of all these.
+ */
+ PK11_SetPasswordFunc(ssl_passphrase_callback);
+
+ /*
+ * Import the already opened socket as we don't want to use NSPR functions
+ * for opening the network socket due to how the PostgreSQL protocol works
+ * with TLS connections. This function is not part of the NSPR public API,
+ * see the comment at the top of the file for the rationale of still using
+ * it.
+ */
+ pr_fd = PR_ImportTCPSocket(port->sock);
+ if (!pr_fd)
+ ereport(ERROR,
+ (errmsg("unable to connect to socket")));
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = PR_OpenTCPSocket(port->laddr.addr.ss_family);
+ if (!model)
+ ereport(ERROR,
+ (errmsg("unable to open socket")));
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ model = SSL_ImportFD(NULL, model);
+ if (!model)
+ ereport(ERROR,
+ (errmsg("unable to enable TLS on socket")));
+
+ /*
+ * Configure basic settings for the connection over the SSL socket in
+ * order to set it up as a server.
+ */
+ if (SSL_OptionSet(model, SSL_SECURITY, PR_TRUE) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure TLS connection")));
+
+ if (SSL_OptionSet(model, SSL_HANDSHAKE_AS_SERVER, PR_TRUE) != SECSuccess ||
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_FALSE) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure TLS connection as server")));
+
+ /*
+ * SSLv2 is disabled by default, and SSLv3 will be excluded from the range
+ * of allowed protocols further down. Since we really don't want these to
+ * ever be enabled, let's use belts and suspenders and explicitly turn
+ * them off as well.
+ */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS server has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /*
+ * Configure the allowed cipher. If there are no user preferred suites,
+ * set the domestic policy. TODO: while this code works, the set of
+ * ciphers which can be set and still end up with a working socket is
+ * woefully underdocumented for anything more recent than SSLv3 (the code
+ * for TLS actually calls ssl3 functions under the hood for
+ * SSL_CipherPrefSet), so it's unclear if this is helpful or not. Using
+ * the policies works, but may be too coarsely grained.
+ *
+ * Another TODO: The SSL_ImplementedCiphers table returned with calling
+ * SSL_GetImplementedCiphers is sorted in server preference order. Sorting
+ * SSLCipherSuites according to the order of the ciphers therein could be
+ * a way to implement ssl_prefer_server_ciphers - if we at all want to use
+ * cipher selection for NSS like how we do it for OpenSSL that is.
+ */
+
+ /*
+ * If no ciphers are specified, we use the domestic policy
+ */
+ if (!SSLCipherSuites || strlen(SSLCipherSuites) == 0)
+ {
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to set cipher policy: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ }
+ else
+ {
+ char *ciphers,
+ *c;
+
+ char *sep = ":;, ";
+ PRUint16 ciphercode;
+ const PRUint16 *nss_ciphers;
+
+ /*
+ * If the user has specified a set of preferred cipher suites we start
+ * by turning off all the existing suites to avoid the risk of down-
+ * grades to a weaker cipher than expected.
+ */
+ nss_ciphers = SSL_GetImplementedCiphers();
+ for (int i = 0; i < SSL_GetNumImplementedCiphers(); i++)
+ SSL_CipherPrefSet(model, nss_ciphers[i], PR_FALSE);
+
+ ciphers = pstrdup(SSLCipherSuites);
+
+ for (c = strtok(ciphers, sep); c; c = strtok(NULL, sep))
+ {
+ ciphercode = pg_find_cipher(c);
+ if (ciphercode != INVALID_CIPHER)
+ {
+ status = SSL_CipherPrefSet(model, ciphercode, PR_TRUE);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("invalid cipher-suite specified: %s", c)));
+ }
+ }
+
+ pfree(ciphers);
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_sslver) != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to set requested SSL protocol version range")));
+
+ /*
+ * Set up the custom IO layer.
+ */
+ layer = init_iolayer(port, ERROR);
+ if (!layer)
+ goto error;
+
+ /* Store the Port as private data available in callbacks */
+ layer->secret = (void *) port;
+
+ if (PR_PushIOLayer(pr_fd, PR_TOP_IO_LAYER, layer) != PR_SUCCESS)
+ {
+ PR_Close(layer);
+ ereport(ERROR,
+ (errmsg("unable to push IO layer")));
+ }
+
+ /* TODO: set the postgres password callback param as callback function */
+ server_cert = PK11_FindCertFromNickname(ssl_cert_file, &pwdata /* password callback \
*/ ); + if (!server_cert)
+ ereport(ERROR,
+ (errmsg("unable to find certificate for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+
+ /* TODO: set the postgres password callback param as callback function */
+ private_key = PK11_FindKeyByAnyCert(server_cert, &pwdata /* password callback */ );
+ if (!private_key)
+ ereport(ERROR,
+ (errmsg("unable to find private key for \"%s\": %s",
+ ssl_cert_file, pg_SSLerrmessage(PR_GetError()))));
+
+ /*
+ * NSS doesn't use CRL files on disk, so we use the ssl_crl_file guc to
+ * contain the CRL nickname for the current server certificate in the NSS
+ * certificate database. The main difference from the OpenSSL backend is
+ * that NSS will use the CRL regardless, but being able to make sure the
+ * CRL is loaded seems like a good feature.
+ */
+ if (ssl_crl_file[0])
+ {
+ SECITEM_CopyItem(NULL, &crlname, &server_cert->derSubject);
+ crl = SEC_FindCrlByName(CERT_GetDefaultCertDB(), &crlname, SEC_CRL_TYPE);
+ if (!crl)
+ ereport(ERROR,
+ (errmsg("specified CRL not found in database")));
+ SEC_DestroyCrl(crl);
+ }
+
+ /*
+ * Finally we must configure the socket for being a server by setting the
+ * certificate and key.
+ */
+ status = SSL_ConfigSecureServer(model, server_cert, private_key, kt_rsa);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure secure server: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ status = SSL_ConfigServerCert(model, server_cert, private_key, NULL, 0);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to configure server for TLS server connections: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ ssl_loaded_verify_locations = true;
+
+ /*
+ * At this point, we no longer have use for the certificate and private
+ * key as they have been copied into the context by NSS. Destroy our
+ * copies explicitly to clean out the memory as best we can.
+ */
+ CERT_DestroyCertificate(server_cert);
+ SECKEY_DestroyPrivateKey(private_key);
+
+ status = SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) port);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to install authcert hook: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) port);
+ SSL_OptionSet(model, SSL_REQUEST_CERTIFICATE, PR_TRUE);
+ SSL_OptionSet(model, SSL_REQUIRE_CERTIFICATE, PR_FALSE);
+
+ port->pr_fd = SSL_ImportFD(model, pr_fd);
+ if (!port->pr_fd)
+ ereport(ERROR,
+ (errmsg("unable to initialize")));
+
+ PR_Close(model);
+
+ /*
+ * Force a handshake on the next I/O request, the second parameter means
+ * that we are a server, PR_FALSE would indicate being a client. NSPR
+ * requires us to call SSL_ResetHandshake since we imported an already
+ * established socket.
+ */
+ status = SSL_ResetHandshake(port->pr_fd, PR_TRUE);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to initiate handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ status = SSL_ForceHandshake(port->pr_fd);
+ if (status != SECSuccess)
+ ereport(ERROR,
+ (errmsg("unable to handshake: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+
+ port->ssl_in_use = true;
+ return 0;
+
+error:
+ return 1;
+}
+
+ssize_t
+be_tls_read(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_read;
+ PRErrorCode err;
+
+ n_read = PR_Read(port->pr_fd, ptr, len);
+
+ if (n_read < 0)
+ {
+ err = PR_GetError();
+
+ /* XXX: This logic seems potentially bogus? */
+ if (err == PR_WOULD_BLOCK_ERROR)
+ *waitfor = WL_SOCKET_READABLE;
+ else
+ *waitfor = WL_SOCKET_WRITEABLE;
+ }
+
+ return n_read;
+}
+
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
+{
+ ssize_t n_write;
+ PRErrorCode err;
+
+ n_write = PR_Send(port->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ if (n_write < 0)
+ {
+ err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR)
+ *waitfor = WL_SOCKET_WRITEABLE;
+ else
+ *waitfor = WL_SOCKET_READABLE;
+ }
+
+ return n_write;
+}
+
+void
+be_tls_close(Port *port)
+{
+ if (!port)
+ return;
+
+ if (port->peer_cn)
+ {
+ SSL_InvalidateSession(port->pr_fd);
+ pfree(port->peer_cn);
+ port->peer_cn = NULL;
+ }
+
+ PR_Close(port->pr_fd);
+ port->pr_fd = NULL;
+ port->ssl_in_use = false;
+
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+void
+be_tls_destroy(void)
+{
+ /*
+ * It reads a bit odd to clear a session cache when we are destroying the
+ * context altogether, but if the session cache isn't cleared before
+ * shutting down the context it will fail with SEC_ERROR_BUSY.
+ */
+ SSL_ClearSessionCache();
+}
+
+int
+be_tls_get_cipher_bits(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.effectiveKeyBits;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return 0;
+}
+
+/*
+ * be_tls_get_compression
+ *
+ * NSS disabled support for TLS compression in version 3.33 and removed the
+ * code in a subsequent release. The API for retrieving information about
+ * compression as well as enabling it is kept for backwards compatibility, but
+ * we don't need to consult it since it was only available for SSLv3 which we
+ * don't support.
+ *
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1409587
+ */
+bool
+be_tls_get_compression(Port *port)
+{
+ return false;
+}
+
+const char *
+be_tls_get_version(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ {
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+ }
+
+ return ssl_protocol_version_to_string(channel.protocolVersion);
+}
+
+const char *
+be_tls_get_cipher(Port *port)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ status = SSL_GetChannelInfo(port->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ goto error;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ goto error;
+
+ return suite.cipherSuiteName;
+
+error:
+ ereport(WARNING,
+ (errmsg("unable to extract TLS session information: %s",
+ pg_SSLerrmessage(PR_GetError()))));
+ return NULL;
+}
+
+void
+be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->subject), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ strlcpy(ptr, CERT_NameToAscii(&certificate->issuer), len);
+ else
+ ptr[0] = '\0';
+}
+
+void
+be_tls_get_peer_serial(Port *port, char *ptr, size_t len)
+{
+ CERTCertificate *certificate;
+
+ certificate = SSL_PeerCertificate(port->pr_fd);
+ if (certificate)
+ snprintf(ptr, len, "%li", DER_GetInteger(&(certificate->serialNumber)));
+ else
+ ptr[0] = '\0';
+}
+
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ Port *port = (Port *) arg;
+
+ port->peer_cert_valid = false;
+ return SECFailure;
+}
+
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ Port *port = (Port *) arg;
+ CERTCertificate *cert;
+ char *peer_cn;
+ int len;
+
+ status = SSL_AuthCertificate(CERT_GetDefaultCertDB(), port->pr_fd, checksig, \
PR_TRUE); + if (status == SECSuccess)
+ {
+ cert = SSL_PeerCertificate(port->pr_fd);
+ len = strlen(cert->subjectName);
+ peer_cn = MemoryContextAllocZero(TopMemoryContext, len + 1);
+ if (strncmp(cert->subjectName, "CN=", 3) == 0)
+ strlcpy(peer_cn, cert->subjectName + strlen("CN="), len + 1);
+ else
+ strlcpy(peer_cn, cert->subjectName, len + 1);
+ CERT_DestroyCertificate(cert);
+
+ port->peer_cn = peer_cn;
+ port->peer_cert_valid = true;
+ }
+
+ return status;
+}
+
+/* ------------------------------------------------------------ */
+/* Internal functions */
+/* ------------------------------------------------------------ */
+
+static PRInt32
+pg_ssl_read(PRFileDesc * fd, void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRRecvFN read_fn;
+ PRInt32 n_read;
+
+ read_fn = fd->lower->methods->recv;
+ n_read = read_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_read;
+}
+
+static PRInt32
+pg_ssl_write(PRFileDesc * fd, const void *buf, PRInt32 amount, PRIntn flags,
+ PRIntervalTime timeout)
+{
+ PRSendFN send_fn;
+ PRInt32 n_write;
+
+ send_fn = fd->lower->methods->send;
+ n_write = send_fn(fd->lower, buf, amount, flags, timeout);
+
+ return n_write;
+}
+
+static PRFileDesc *
+init_iolayer(Port *port, int loglevel)
+{
+ const PRIOMethods *default_methods;
+ PRFileDesc *layer;
+
+ /*
+ * Start by initializing our layer with all the default methods so that we
+ * can selectively override the ones we want while still ensuring that we
+ * have a complete layer specification.
+ */
+ default_methods = PR_GetDefaultIOMethods();
+ memcpy(&pr_iomethods, default_methods, sizeof(PRIOMethods));
+
+ pr_iomethods.recv = pg_ssl_read;
+ pr_iomethods.send = pg_ssl_write;
+
+ /*
+ * Each IO layer must be identified by a unique name, where uniqueness is
+ * per connection. Each connection in a postgres cluster can generate the
+ * identity from the same string as they will create their IO layers on
+ * different sockets. Only one layer per socket can have the same name.
+ */
+ pr_id = PR_GetUniqueIdentity("PostgreSQL");
+ if (pr_id == PR_INVALID_IO_LAYER)
+ {
+ ereport(loglevel,
+ (errmsg("out of memory when setting up TLS connection")));
+ return NULL;
+ }
+
+ /*
+ * Create the actual IO layer as a stub such that it can be pushed onto
+ * the layer stack. The step via a stub is required as we define custom
+ * callbacks.
+ */
+ layer = PR_CreateIOLayerStub(pr_id, &pr_iomethods);
+ if (!layer)
+ {
+ ereport(loglevel,
+ (errmsg("unable to create NSS I/O layer")));
+ return NULL;
+ }
+
+ return layer;
+}
+
+static char *
+ssl_protocol_version_to_string(int v)
+{
+ switch (v)
+ {
+ /* SSL v2 and v3 are not supported */
+ case SSL_LIBRARY_VERSION_2:
+ case SSL_LIBRARY_VERSION_3_0:
+ Assert(false);
+ break;
+
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return pstrdup("TLSv1.0");
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return pstrdup("TLSv1.1");
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return pstrdup("TLSv1.2");
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return pstrdup("TLSv1.3");
+ }
+
+ return pstrdup("unknown");
+}
+
+
+/*
+ * ssl_protocol_version_to_nss
+ * Translate PostgreSQL TLS version to NSS version
+ *
+ * Returns zero in case the requested TLS version is undefined (PG_ANY) and
+ * should be set by the caller, or -1 on failure.
+ */
+static uint16
+ssl_protocol_version_to_nss(int v, const char *guc_name)
+{
+ switch (v)
+ {
+ /*
+ * There is no SSL_LIBRARY_ macro defined in NSS with the value
+ * zero, so we use this to signal the caller that the highest
+ * useful version should be set on the connection.
+ */
+ case PG_TLS_ANY:
+ return 0;
+
+ /*
+ * No guard is required here as there are no versions of NSS
+ * without support for TLS1.
+ */
+ case PG_TLS1_VERSION:
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+ case PG_TLS1_1_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#else
+ break;
+#endif
+ case PG_TLS1_2_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#else
+ break;
+#endif
+ case PG_TLS1_3_VERSION:
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#else
+ break;
+#endif
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+/*
+ * pg_SSLerrmessage
+ * Create and return a human readable error message given
+ * the specified error code
+ *
+ * PR_ErrorToName only converts the enum identifier of the error to string,
+ * but that can be quite useful for debugging (and in case PR_ErrorToString is
+ * unable to render a message then we at least have something).
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ char error[128];
+ int ret;
+
+ /* TODO: this should perhaps use a StringInfo instead.. */
+ ret = pg_snprintf(error, sizeof(error), "%s (%s)",
+ PR_ErrorToString(errcode, PR_LANGUAGE_I_DEFAULT),
+ PR_ErrorToName(errcode));
+ if (ret)
+ return pstrdup(error);
+
+ return pstrdup(_("unknown TLS error"));
+}
diff --git a/src/backend/libpq/be-secure-openssl.c \
b/src/backend/libpq/be-secure-openssl.c index 8b21ff4065..5962cffc0c 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -1298,15 +1298,28 @@ X509_NAME_to_cstring(X509_NAME *name)
char *dp;
char *result;
+ if (membuf == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("failed to create BIO")));
+
(void) BIO_set_close(membuf, BIO_CLOSE);
for (i = 0; i < count; i++)
{
e = X509_NAME_get_entry(name, i);
nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(e));
+ if (nid == NID_undef)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not get NID for ASN1_OBJECT object")));
v = X509_NAME_ENTRY_get_data(e);
field_name = OBJ_nid2sn(nid);
if (!field_name)
field_name = OBJ_nid2ln(nid);
+ if (field_name == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not convert NID %d to an ASN1_OBJECT structure", nid)));
BIO_printf(membuf, "/%s=", field_name);
ASN1_STRING_print_ex(membuf, v,
((ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)
@@ -1322,7 +1335,8 @@ X509_NAME_to_cstring(X509_NAME *name)
result = pstrdup(dp);
if (dp != sp)
pfree(dp);
- BIO_free(membuf);
+ if (BIO_free(membuf) != 1)
+ elog(ERROR, "could not free OpenSSL BIO structure");
return result;
}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index 2ae507a902..f39977b80c 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -49,6 +49,9 @@ bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
bool ssl_loaded_verify_locations = false;
#endif
+#ifdef USE_NSS
+char *ssl_database;
+#endif
/* GUC variable controlling SSL cipher list */
char *SSLCipherSuites = NULL;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index de87ad6ef7..33c3eebf48 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4262,7 +4262,11 @@ static struct config_string ConfigureNamesString[] =
},
&ssl_library,
#ifdef USE_SSL
+#if defined(USE_OPENSSL)
"OpenSSL",
+#elif defined(USE_NSS)
+ "NSS",
+#endif
#else
"",
#endif
@@ -4320,6 +4324,18 @@ static struct config_string ConfigureNamesString[] =
check_canonical_path, assign_pgstat_temp_directory, NULL
},
+#ifdef USE_NSS
+ {
+ {"ssl_database", PGC_SIGHUP, CONN_AUTH_SSL,
+ gettext_noop("Location of the NSS certificate database."),
+ NULL
+ },
+ &ssl_database,
+ "",
+ NULL, NULL, NULL
+ },
+#endif
+
{
{"synchronous_standby_names", PGC_SIGHUP, REPLICATION_PRIMARY,
gettext_noop("Number of synchronous standbys and list of names of potential \
synchronous ones."), @@ -4348,8 +4364,10 @@ static struct config_string \
ConfigureNamesString[] = GUC_SUPERUSER_ONLY
},
&SSLCipherSuites,
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL)
"HIGH:MEDIUM:+3DES:!aNULL",
+#elif defined (USE_NSS)
+ "",
#else
"none",
#endif
diff --git a/src/include/common/pg_nss.h b/src/include/common/pg_nss.h
new file mode 100644
index 0000000000..74298c8bb1
--- /dev/null
+++ b/src/include/common/pg_nss.h
@@ -0,0 +1,141 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_nss.h
+ * Support for NSS as a TLS backend
+ *
+ * These definitions are used by both frontend and backend code.
+ *
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/include/common/pg_nss.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_NSS_H
+#define PG_NSS_H
+
+#ifdef USE_NSS
+
+#include <sslproto.h>
+
+PRUint16 pg_find_cipher(char *name);
+
+typedef struct
+{
+ const char *name;
+ PRUint16 number;
+} NSSCiphers;
+
+#define INVALID_CIPHER 0xFFFF
+
+/*
+ * This list is a partial copy of the ciphers in NSS files lib/ssl/sslproto.h
+ * in order to provide a human readable version of the ciphers. It would be
+ * nice to not have to have this, but NSS doesn't provide any API addressing
+ * the ciphers by name. TODO: do we want more of the ciphers, or perhaps less?
+ */
+static const NSSCiphers NSS_CipherList[] = {
+
+ {"TLS_NULL_WITH_NULL_NULL", TLS_NULL_WITH_NULL_NULL},
+
+ {"TLS_RSA_WITH_NULL_MD5", TLS_RSA_WITH_NULL_MD5},
+ {"TLS_RSA_WITH_NULL_SHA", TLS_RSA_WITH_NULL_SHA},
+ {"TLS_RSA_WITH_RC4_128_MD5", TLS_RSA_WITH_RC4_128_MD5},
+ {"TLS_RSA_WITH_RC4_128_SHA", TLS_RSA_WITH_RC4_128_SHA},
+ {"TLS_RSA_WITH_IDEA_CBC_SHA", TLS_RSA_WITH_IDEA_CBC_SHA},
+ {"TLS_RSA_WITH_DES_CBC_SHA", TLS_RSA_WITH_DES_CBC_SHA},
+ {"TLS_RSA_WITH_3DES_EDE_CBC_SHA", TLS_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_DSS_WITH_DES_CBC_SHA", TLS_DH_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DH_RSA_WITH_DES_CBC_SHA", TLS_DH_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_DES_CBC_SHA", TLS_DHE_DSS_WITH_DES_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_DES_CBC_SHA", TLS_DHE_RSA_WITH_DES_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_DH_anon_WITH_RC4_128_MD5", TLS_DH_anon_WITH_RC4_128_MD5},
+ {"TLS_DH_anon_WITH_DES_CBC_SHA", TLS_DH_anon_WITH_DES_CBC_SHA},
+ {"TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", TLS_DH_anon_WITH_3DES_EDE_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_CBC_SHA", TLS_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_128_CBC_SHA", TLS_DH_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_128_CBC_SHA", TLS_DH_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", TLS_DHE_DSS_WITH_AES_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", TLS_DHE_RSA_WITH_AES_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_128_CBC_SHA", TLS_DH_anon_WITH_AES_128_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_256_CBC_SHA", TLS_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_AES_256_CBC_SHA", TLS_DH_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_AES_256_CBC_SHA", TLS_DH_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", TLS_DHE_DSS_WITH_AES_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", TLS_DHE_RSA_WITH_AES_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_AES_256_CBC_SHA", TLS_DH_anon_WITH_AES_256_CBC_SHA},
+ {"TLS_RSA_WITH_NULL_SHA256", TLS_RSA_WITH_NULL_SHA256},
+ {"TLS_RSA_WITH_AES_128_CBC_SHA256", TLS_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_AES_256_CBC_SHA256", TLS_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", TLS_DHE_DSS_WITH_AES_128_CBC_SHA256},
+ {"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA},
+
+ {"TLS_DHE_DSS_WITH_RC4_128_SHA", TLS_DHE_DSS_WITH_RC4_128_SHA},
+ {"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", TLS_DHE_RSA_WITH_AES_128_CBC_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", TLS_DHE_DSS_WITH_AES_256_CBC_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", TLS_DHE_RSA_WITH_AES_256_CBC_SHA256},
+
+ {"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA},
+ {"TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA},
+
+ {"TLS_RSA_WITH_SEED_CBC_SHA", TLS_RSA_WITH_SEED_CBC_SHA},
+
+ {"TLS_RSA_WITH_AES_128_GCM_SHA256", TLS_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_RSA_WITH_AES_256_GCM_SHA384", TLS_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", TLS_DHE_RSA_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", TLS_DHE_RSA_WITH_AES_256_GCM_SHA384},
+ {"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", TLS_DHE_DSS_WITH_AES_128_GCM_SHA256},
+ {"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", TLS_DHE_DSS_WITH_AES_256_GCM_SHA384},
+
+ {"TLS_AES_128_GCM_SHA256", TLS_AES_128_GCM_SHA256},
+ {"TLS_AES_256_GCM_SHA384", TLS_AES_256_GCM_SHA384},
+ {"TLS_CHACHA20_POLY1305_SHA256", TLS_CHACHA20_POLY1305_SHA256},
+ {NULL, 0}
+};
+
+/*
+ * pg_find_cipher
+ * Translate an NSS ciphername to the cipher code
+ *
+ * Searches the configured ciphers for the corresponding cipher code to the
+ * name. Search is performed case insensitive.
+ */
+PRUint16
+pg_find_cipher(char *name)
+{
+ const NSSCiphers *cipher_list = NSS_CipherList;
+
+ while (cipher_list->name)
+ {
+ if (pg_strcasecmp(cipher_list->name, name) == 0)
+ return cipher_list->number;
+
+ cipher_list++;
+ }
+
+ return 0xFFFF;
+}
+
+#endif /* USE_NSS */
+
+#endif /* PG_NSS_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 179ebaa104..6211510fab 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -192,13 +192,18 @@ typedef struct Port
bool peer_cert_valid;
/*
- * OpenSSL structures. (Keep these last so that the locations of other
- * fields are the same whether or not you build with OpenSSL.)
+ * SSL backend specific structures. (Keep these last so that the locations
+ * of other fields are the same whether or not you build with SSL
+ * enabled.)
*/
#ifdef USE_OPENSSL
SSL *ssl;
X509 *peer;
#endif
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif
} Port;
#ifdef USE_SSL
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index b1152475ac..298d87ecae 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -88,6 +88,9 @@ extern PGDLLIMPORT bool ssl_passphrase_command_supports_reload;
#ifdef USE_SSL
extern bool ssl_loaded_verify_locations;
#endif
+#ifdef USE_NSS
+extern char *ssl_database;
+#endif
extern int secure_initialize(bool isServerStart);
extern bool secure_loaded_verify_locations(void);
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index fb270df678..31f808398c 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -893,6 +893,9 @@
/* Define to 1 to build with PAM support. (--with-pam) */
#undef USE_PAM
+/* Define to build with NSS support (--with-nss) */
+#undef USE_NSS
+
/* Define to 1 to use software CRC-32C implementation (slicing-by-8). */
#undef USE_SLICING_BY_8_CRC32C
diff --git a/src/include/pg_config_manual.h b/src/include/pg_config_manual.h
index 705dc69c06..c28b84126d 100644
--- a/src/include/pg_config_manual.h
+++ b/src/include/pg_config_manual.h
@@ -176,10 +176,9 @@
/*
* USE_SSL code should be compiled only when compiling with an SSL
- * implementation. (Currently, only OpenSSL is supported, but we might add
- * more implementations in the future.)
+ * implementation.
*/
-#ifdef USE_OPENSSL
+#if defined(USE_OPENSSL) || defined(USE_NSS)
#define USE_SSL
#endif
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index d4919970f8..97821fb39b 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -57,6 +57,10 @@ OBJS += \
fe-secure-gssapi.o
endif
+ifeq ($(with_nss), yes)
+OBJS += fe-secure-nss.o
+endif
+
ifeq ($(PORTNAME), cygwin)
override shlib = cyg$(NAME)$(DLSUFFIX)
endif
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 7bee9dd201..2814eb8ddd 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -354,6 +354,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
offsetof(struct pg_conn, target_session_attrs)},
+ {"cert_database", NULL, NULL, NULL,
+ "CertificateDatabase", "", 64,
+ offsetof(struct pg_conn, cert_database)},
+
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
diff --git a/src/interfaces/libpq/fe-secure-nss.c \
b/src/interfaces/libpq/fe-secure-nss.c new file mode 100644
index 0000000000..6401949136
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-nss.c
@@ -0,0 +1,975 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-nss.c
+ * functions for supporting NSS as a TLS backend for frontend libpq
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq/fe-secure-nss.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include "libpq-fe.h"
+#include "fe-auth.h"
+#include "libpq-int.h"
+
+/*
+ * BITS_PER_BYTE is also defined in the NSPR header fils, so we need to undef
+ * our version to avoid compiler warnings on redefinition.
+ */
+#define pg_BITS_PER_BYTE BITS_PER_BYTE
+#undef BITS_PER_BYTE
+
+/*
+ * The nspr/obsolete/protypes.h NSPR header typedefs uint64 and int64 with
+ * colliding definitions from ours, causing a much expected compiler error.
+ * The definitions are however not actually used in NSPR at all, and are only
+ * intended for what seems to be backwards compatibility for apps written
+ * against old versions of NSPR. The following comment is in the referenced
+ * file, and was added in 1998:
+ *
+ * This section typedefs the old 'native' types to the new PR<type>s.
+ * These definitions are scheduled to be eliminated at the earliest
+ * possible time. The NSPR API is implemented and documented using
+ * the new definitions.
+ *
+ * As there is no opt-out from pulling in these typedefs, we define the guard
+ * for the file to exclude it. This is incredibly ugly, but seems to be about
+ * the only way around it.
+ */
+#define PROTYPES_H
+#include <nspr.h>
+#undef PROTYPES_H
+#include <nss.h>
+#include <ssl.h>
+#include <sslproto.h>
+#include <pk11func.h>
+#include <prerror.h>
+#include <prinit.h>
+#include <prio.h>
+#include <secerr.h>
+#include <secmod.h>
+
+/*
+ * Ensure that the colliding definitions match, else throw an error. In case
+ * NSPR remove the definition in a future version (however unlikely that may
+ * be, make sure to put ours back again.
+ */
+#if defined(BITS_PER_BYTE)
+#if BITS_PER_BYTE != pg_BITS_PER_BYTE
+#error "incompatible byte widths between NSPR and PostgreSQL"
+#endif
+#else
+#define BITS_PER_BYTE pg_BITS_PER_BYTE
+#endif
+#undef pg_BITS_PER_BYTE
+
+static SECStatus pg_load_nss_module(SECMODModule * *module, const char *library, \
const char *name); +static SECStatus pg_bad_cert_handler(void *arg, PRFileDesc * fd);
+static char *pg_SSLerrmessage(PRErrorCode errcode);
+static SECStatus pg_client_auth_handler(void *arg, PRFileDesc * socket, \
CERTDistNames * caNames, + CERTCertificate * *pRetCert, SECKEYPrivateKey * \
*pRetKey); +static SECStatus pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool \
checksig, PRBool isServer); +static int ssl_protocol_version_to_nss(const char \
*protocol); +static bool cert_database_has_CA(PGconn *conn);
+
+static char *PQssl_passwd_cb(PK11SlotInfo * slot, PRBool retry, void *arg);
+
+/*
+ * PR_ImportTCPSocket() is a private API, but very widely used, as it's the
+ * only way to make NSS use an already set up POSIX file descriptor rather
+ * than opening one itself. To quote the NSS documentation:
+ *
+ * "In theory, code that uses PR_ImportTCPSocket may break when NSPR's
+ * implementation changes. In practice, this is unlikely to happen because
+ * NSPR's implementation has been stable for years and because of NSPR's
+ * strong commitment to backward compatibility."
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_ImportTCPSocket
+ *
+ * The function is declared in <private/pprio.h>, but as it is a header marked
+ * private we declare it here rather than including it.
+ */
+NSPR_API(PRFileDesc *) PR_ImportTCPSocket(int);
+
+static SECMODModule * ca_trust = NULL;
+static NSSInitContext * nss_context = NULL;
+
+/*
+ * Track whether the NSS database has a password set or not. There is no API
+ * function for retrieving password status, so we simply flip this to true in
+ * case NSS invoked the password callback - as that will only happen in case
+ * there is a password. The reason for tracking this is that there are calls
+ * which require a password parameter, but doesn't use the callbacks provided,
+ * so we must call the callback on behalf of these.
+ */
+static bool has_password = false;
+
+#if defined(WIN32)
+static const char *ca_trust_name = "nssckbi.dll";
+#elif defined(__darwin__)
+static const char *ca_trust_name = "libnssckbi.dylib";
+#else
+static const char *ca_trust_name = "libnssckbi.so";
+#endif
+
+static PQsslKeyPassHook_nss_type PQsslKeyPassHook = NULL;
+
+/* ------------------------------------------------------------ */
+/* Procedures common to all secure sessions */
+/* ------------------------------------------------------------ */
+
+void
+pgtls_init_library(bool do_ssl, int do_crypto)
+{
+ /* TODO: implement me .. */
+}
+
+int
+pgtls_init(PGconn *conn)
+{
+ conn->ssl_in_use = false;
+
+ return 0;
+}
+
+void
+pgtls_close(PGconn *conn)
+{
+ if (nss_context)
+ {
+ NSS_ShutdownContext(nss_context);
+ nss_context = NULL;
+ }
+}
+
+PostgresPollingStatusType
+pgtls_open_client(PGconn *conn)
+{
+ SECStatus status;
+ PRFileDesc *pr_fd;
+ PRFileDesc *model;
+ NSSInitParameters params;
+ SSLVersionRange desired_range;
+
+ /*
+ * The NSPR documentation states that runtime initialization via PR_Init
+ * is no longer required, as the first caller into NSPR will perform the
+ * initialization implicitly. The documentation doesn't however clarify
+ * from which version this is holds true, so let's perform the potentially
+ * superfluous initialization anyways to avoid crashing on older versions
+ * of NSPR, as there is no difference in overhead. The NSS documentation
+ * still states that PR_Init must be called in some way (implicitly or
+ * explicitly).
+ *
+ * The below parameters are what the implicit initialization would've done
+ * for us, and should work even for older versions where it might not be
+ * done automatically. The last parameter, maxPTDs, is set to various
+ * values in other codebases, but has been unused since NSPR 2.1 which was
+ * released sometime in 1998.
+ */
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
+
+ /*
+ * The original design of NSS was for a single application to use a single
+ * copy of it, initialized with NSS_Initialize() which isn't returning any
+ * handle with which to refer to NSS. NSS initialization and shutdown are
+ * global for the application, so a shutdown in another NSS enabled
+ * library would cause NSS to be stopped for libpq as well. The fix has
+ * been to introduce NSS_InitContext which returns a context handle to
+ * pass to NSS_ShutdownContext. NSS_InitContext was introduced in NSS
+ * 3.12, but the use of it is not very well documented.
+ * https://bugzilla.redhat.com/show_bug.cgi?id=738456
+ *
+ * The InitParameters struct passed can be used to override internal
+ * values in NSS, but the usage is not documented at all. When using
+ * NSS_Init initializations, the values are instead set via PK11_Configure
+ * calls so the PK11_Configure documentation can be used to glean some
+ * details on these.
+ *
+ * https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/PKCS11/Module_Specs
+ */
+ memset(¶ms, 0, sizeof(params));
+ params.length = sizeof(params);
+
+ if (conn->cert_database && strlen(conn->cert_database) > 0)
+ {
+ char *cert_database_path = psprintf("sql:%s", conn->cert_database);
+
+ nss_context = NSS_InitContext(cert_database_path, "", "", "",
+ ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_PK11RELOAD);
+ pfree(cert_database_path);
+ }
+ else
+ nss_context = NSS_InitContext("", "", "", "", ¶ms,
+ NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
+ NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
+ NSS_INIT_NOROOTINIT | NSS_INIT_PK11RELOAD);
+
+ if (!nss_context)
+ {
+ char *err = pg_SSLerrmessage(PR_GetError());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to %s certificate database: %s"),
+ conn->cert_database ? "open" : "create",
+ err);
+ free(err);
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Configure cipher policy.
+ */
+ status = NSS_SetDomesticPolicy();
+ if (status != SECSuccess)
+ {
+ char *err = pg_SSLerrmessage(PR_GetError());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure cipher policy: %s"),
+ err);
+ free(err);
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * If we don't have a certificate database, the system trust store is the
+ * fallback we can use. If we fail to initialize that as well, we can
+ * still attempt a connection as long as the sslmode isn't verify*.
+ */
+ if (!conn->cert_database && conn->sslmode[0] == 'v')
+ {
+ status = pg_load_nss_module(&ca_trust, ca_trust_name, "\"Root Certificates\"");
+ /* status = pg_load_nss_module(&ca_trust, ca_trust_name, "trust"); */
+ if (status != SECSuccess)
+ {
+ char *err = pg_SSLerrmessage(PR_GetError());
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("WARNING: unable to load NSS trust module \"%s\" : %s"), \
ca_trust_name, err); + return PGRES_POLLING_FAILED;
+ }
+ }
+
+
+ PK11_SetPasswordFunc(PQssl_passwd_cb);
+
+ /*
+ * Import the already opened socket as we don't want to use NSPR functions
+ * for opening the network socket due to how the PostgreSQL protocol works
+ * with TLS connections. This function is not part of the NSPR public API,
+ * see the comment at the top of the file for the rationale of still using
+ * it.
+ */
+ pr_fd = PR_ImportTCPSocket(conn->sock);
+ if (!pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to attach to socket: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Most of the documentation available, and implementations of, NSS/NSPR
+ * use the PR_NewTCPSocket() function here, which has the drawback that it
+ * can only create IPv4 sockets. Instead use PR_OpenTCPSocket() which
+ * copes with IPv6 as well.
+ */
+ model = SSL_ImportFD(NULL, PR_OpenTCPSocket(conn->laddr.addr.ss_family));
+ if (!model)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to enable TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Disable old protocol versions (SSLv2 and SSLv3) */
+ SSL_OptionSet(model, SSL_ENABLE_SSL2, PR_FALSE);
+ SSL_OptionSet(model, SSL_V2_COMPATIBLE_HELLO, PR_FALSE);
+ SSL_OptionSet(model, SSL_ENABLE_SSL3, PR_FALSE);
+
+#ifdef SSL_CBC_RANDOM_IV
+
+ /*
+ * Enable protection against the BEAST attack in case the NSS library has
+ * support for that. While SSLv3 is disabled, we may still allow TLSv1
+ * which is affected. The option isn't documented as an SSL option, but as
+ * an NSS environment variable.
+ */
+ SSL_OptionSet(model, SSL_CBC_RANDOM_IV, PR_TRUE);
+#endif
+
+ /* Set us up as a TLS client for the handshake */
+ SSL_OptionSet(model, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE);
+
+ /*
+ * When setting the available protocols, we either use the user defined
+ * configuration values, and if missing we accept whatever is the highest
+ * version supported by the library as the max and only limit the range in
+ * the other end at TLSv1.0. ssl_variant_stream is a ProtocolVariant enum
+ * for Stream protocols, rather than datagram.
+ */
+ SSL_VersionRangeGetSupported(ssl_variant_stream, &desired_range);
+ desired_range.min = SSL_LIBRARY_VERSION_TLS_1_0;
+
+ if (conn->ssl_min_protocol_version && strlen(conn->ssl_min_protocol_version) > 0)
+ {
+ int ssl_min_ver = ssl_protocol_version_to_nss(conn->ssl_min_protocol_version);
+
+ if (ssl_min_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for minimum version of SSL \
protocol\n"), + conn->ssl_min_protocol_version);
+ return -1;
+ }
+
+ desired_range.min = ssl_min_ver;
+ }
+
+ if (conn->ssl_max_protocol_version && strlen(conn->ssl_max_protocol_version) > 0)
+ {
+ int ssl_max_ver = ssl_protocol_version_to_nss(conn->ssl_max_protocol_version);
+
+ if (ssl_max_ver == -1)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid value \"%s\" for maximum version of SSL \
protocol\n"), + conn->ssl_max_protocol_version);
+ return -1;
+ }
+
+ desired_range.max = ssl_max_ver;
+ }
+
+ if (SSL_VersionRangeSet(model, &desired_range) != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to set allowed SSL protocol version range: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set up callback for verifying server certificates, as well as for how
+ * to handle failed verifications.
+ */
+ SSL_AuthCertificateHook(model, pg_cert_auth_handler, (void *) conn);
+ SSL_BadCertHook(model, pg_bad_cert_handler, (void *) conn);
+
+ /*
+ * Convert the NSPR socket to an SSL socket. Ensuring the success of this
+ * operation is critical as NSS SSL_* functions may return SECSuccess on
+ * the socket even though SSL hasn't been enabled, which introduce a risk
+ * of silent downgrades.
+ */
+ conn->pr_fd = SSL_ImportFD(model, pr_fd);
+ if (!conn->pr_fd)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to configure client for TLS: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * The model can now we closed as we've applied the settings of the model
+ * onto the real socket. From hereon we should only use conn->pr_fd.
+ */
+ PR_Close(model);
+
+ /* Set the private data to be passed to the password callback */
+ SSL_SetPKCS11PinArg(conn->pr_fd, (void *) conn);
+
+ /*
+ * If a CRL file has been specified, verify if it exists in the database
+ * but don't fail in case it doesn't.
+ */
+ if (conn->sslcrl && strlen(conn->sslcrl) > 0)
+ {
+ /* XXX: Implement me.. */
+ }
+
+ status = SSL_ResetHandshake(conn->pr_fd, PR_FALSE);
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to initiate handshake: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Set callback for client authentication when requested by the server.
+ */
+ SSL_GetClientAuthDataHook(conn->pr_fd, pg_client_auth_handler, (void *) conn);
+
+ /*
+ * Specify which hostname we are expecting to talk to. This is required,
+ * albeit mostly applies to when opening a connection to a traditional
+ * http server it seems.
+ */
+ SSL_SetURL(conn->pr_fd, (conn->connhost[conn->whichhost]).host);
+
+ do
+ {
+ status = SSL_ForceHandshake(conn->pr_fd);
+ }
+ while (status != SECSuccess && PR_GetError() == PR_WOULD_BLOCK_ERROR);
+
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("SSL error: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ return PGRES_POLLING_FAILED;
+ }
+
+ conn->ssl_in_use = true;
+ return PGRES_POLLING_OK;
+}
+
+ssize_t
+pgtls_read(PGconn *conn, void *ptr, size_t len)
+{
+ PRInt32 nread;
+ PRErrorCode status;
+ int read_errno = 0;
+
+ nread = PR_Recv(conn->pr_fd, ptr, len, 0, PR_INTERVAL_NO_WAIT);
+
+ /*
+ * PR_Recv blocks until there is data to read or the timeout expires. Zero
+ * is returned for closed connections, while -1 indicates an error within
+ * the ongoing connection.
+ */
+ if (nread == 0)
+ {
+ read_errno = ECONNRESET;
+ return -1;
+ }
+
+ if (nread == -1)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+ read_errno = EINTR;
+ break;
+
+ case PR_IO_TIMEOUT_ERROR:
+ break;
+
+ /*
+ * The error cases for PR_Recv are not documented, but can be
+ * reverse engineered from _MD_unix_map_default_error() in the
+ * NSPR code, defined in pr/src/md/unix/unix_errors.c.
+ */
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS read error: %s"),
+ pg_SSLerrmessage(status));
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(read_errno);
+ return (ssize_t) nread;
+}
+
+/*
+ * pgtls_read_pending
+ * Check for the existence of data to be read.
+ *
+ * This is part of the PostgreSQL TLS backend API.
+ */
+bool
+pgtls_read_pending(PGconn *conn)
+{
+ unsigned char c;
+ int n;
+
+ /*
+ * PR_Recv peeks into the stream with the timeount turned off, to see if
+ * there is another byte to read off the wire. There is an NSS function
+ * SSL_DataPending() which might seem like a better fit, but it will only
+ * check already encrypted data in the SSL buffer, not still unencrypted
+ * data, thus it doesn't guarantee that a subsequent call to
+ * PR_Read/PR_Recv wont block.
+ */
+ n = PR_Recv(conn->pr_fd, &c, 1, PR_MSG_PEEK, PR_INTERVAL_NO_WAIT);
+ return (n > 0);
+}
+
+ssize_t
+pgtls_write(PGconn *conn, const void *ptr, size_t len)
+{
+ PRInt32 n;
+ PRErrorCode status;
+ int write_errno = 0;
+
+ n = PR_Write(conn->pr_fd, ptr, len);
+
+ if (n < 0)
+ {
+ status = PR_GetError();
+
+ switch (status)
+ {
+ case PR_WOULD_BLOCK_ERROR:
+#ifdef EAGAIN
+ write_errno = EAGAIN;
+#else
+ write_errno = EINTR;
+#endif
+ break;
+
+ default:
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("TLS write error: %s"),
+ pg_SSLerrmessage(status));
+ write_errno = ECONNRESET;
+ break;
+ }
+ }
+
+ SOCK_ERRNO_SET(write_errno);
+ return (ssize_t) n;
+}
+
+/*
+ * Verify that the server certificate matches the hostname we connected to.
+ *
+ * The certificate's Common Name and Subject Alternative Names are considered.
+ */
+int
+pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
+ int *names_examined,
+ char **first_name)
+{
+ return 1;
+}
+
+/* ------------------------------------------------------------ */
+/* PostgreSQL specific TLS support functions */
+/* ------------------------------------------------------------ */
+
+/*
+ * TODO: this a 99% copy of the same function in the backend, make these share
+ * a single implementation instead.
+ */
+static char *
+pg_SSLerrmessage(PRErrorCode errcode)
+{
+ const char *error;
+
+ error = PR_ErrorToName(errcode);
+ if (error)
+ return strdup(error);
+
+ return strdup("unknown TLS error");
+}
+
+static SECStatus
+pg_load_nss_module(SECMODModule * *module, const char *library, const char *name)
+{
+ SECMODModule *mod;
+ char *modulespec;
+
+ modulespec = psprintf("library=\"%s\", name=\"%s\"", library, name);
+
+ /*
+ * Attempt to load the specified module. The second parameter is "parent"
+ * which should always be NULL for application code. The third parameter
+ * defines if loading should recurse which is only applicable when loading
+ * a module from within another module. This hierarchy would have to be
+ * defined in the modulespec, and since we don't support anything but
+ * directly addressed modules we should pass PR_FALSE.
+ */
+ mod = SECMOD_LoadUserModule(modulespec, NULL, PR_FALSE);
+ pfree(modulespec);
+
+ if (mod && mod->loaded)
+ {
+ *module = mod;
+ return SECSuccess;
+ }
+
+ SECMOD_DestroyModule(mod);
+ return SECFailure;
+}
+
+/* ------------------------------------------------------------ */
+/* NSS Callbacks */
+/* ------------------------------------------------------------ */
+
+/*
+ * pg_cert_auth_handler
+ * Callback for authenticating server certificate
+ *
+ * This is pretty much the same procedure as the SSL_AuthCertificate function
+ * provided by NSS, with the difference being server hostname validation. With
+ * SSL_AuthCertificate there is no way to do verify-ca, it only does the -full
+ * flavor of our sslmodes, so we need our own implementation.
+ */
+static SECStatus
+pg_cert_auth_handler(void *arg, PRFileDesc * fd, PRBool checksig, PRBool isServer)
+{
+ SECStatus status;
+ PGconn *conn = (PGconn *) arg;
+ char *server_hostname = NULL;
+ CERTCertificate *server_cert;
+ void *pin;
+
+ Assert(!isServer);
+
+ pin = SSL_RevealPinArg(conn->pr_fd);
+ server_cert = SSL_PeerCertificate(conn->pr_fd);
+
+ status = CERT_VerifyCertificateNow((CERTCertDBHandle *) CERT_GetDefaultCertDB(), \
server_cert, + checksig, certificateUsageSSLServer,
+ pin, NULL);
+
+ /*
+ * If we've already failed validation then there is no point in also
+ * performing the hostname check for verify-full.
+ */
+ if (status != SECSuccess)
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify certificate: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+ goto done;
+ }
+
+ if (strcmp(conn->sslmode, "verify-full") == 0)
+ {
+ server_hostname = SSL_RevealURL(conn->pr_fd);
+ if (!server_hostname || server_hostname[0] == '\0')
+ goto done;
+
+ /*
+ * CERT_VerifyCertName will internally perform RFC 2818 SubjectAltName
+ * verification.
+ */
+ status = CERT_VerifyCertName(server_cert, server_hostname);
+ if (status != SECSuccess)
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unable to verify server hostname: %s"),
+ pg_SSLerrmessage(PR_GetError()));
+
+ }
+
+done:
+ if (server_hostname)
+ PR_Free(server_hostname);
+
+ CERT_DestroyCertificate(server_cert);
+ return status;
+}
+
+/*
+ * pg_client_auth_handler
+ * Callback for client certificate validation
+ *
+ * The client auth callback is not on by default in NSS, so we need to invoke
+ * it ourselves to ensure we can do cert authentication. A TODO is to support
+ * running without a specified sslcert parameter. By retrieving all the certs
+ * via nickname from the cert database and see if we find one which apply with
+ * NSS_CmpCertChainWCANames() and PK11_FindKeyByAnyCert() we could support
+ * just running with a ssl database specified.
+ *
+ * For now, we use the default client certificate validation which requires a
+ * defined nickname to identify the cert in the database.
+ */
+static SECStatus
+pg_client_auth_handler(void *arg, PRFileDesc * socket, CERTDistNames * caNames,
+ CERTCertificate * *pRetCert, SECKEYPrivateKey * *pRetKey)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ return NSS_GetClientAuthData(conn->sslcert, socket, caNames, pRetCert, pRetKey);
+}
+
+/*
+ * pg_bad_cert_handler
+ * Callback for failed certificate validation
+ *
+ * The TLS handshake will call this function iff the server certificate failed
+ * validation. Depending on the sslmode, we allow the connection anyways.
+ */
+static SECStatus
+pg_bad_cert_handler(void *arg, PRFileDesc * fd)
+{
+ PGconn *conn = (PGconn *) arg;
+ PRErrorCode err;
+
+ /*
+ * This really shouldn't happen, as we've the the PGconn object as our
+ * callback data, and at the callsite we know it will be populated. That
+ * being said, the NSS code itself performs this check even when it should
+ * not be required so let's use the same belts with our suspenders.
+ */
+ if (!arg)
+ return SECFailure;
+
+ /*
+ * For sslmodes other than verify-full and verify-ca we don't perform peer
+ * validation, so return immediately. sslmode require with a database
+ * specified which contains a CA certificate will work like verify-ca to
+ * be compatible with the OpenSSL implementation.
+ */
+ if (strcmp(conn->sslmode, "require") == 0)
+ {
+ if (conn->cert_database && strlen(conn->cert_database) > 0 && \
cert_database_has_CA(conn)) + return SECFailure;
+ }
+ if (conn->sslmode[0] == 'v')
+ return SECFailure;
+
+ err = PORT_GetError();
+
+ /*
+ * TODO: these are relevant error codes that can occur in certificate
+ * validation, figure out which we dont want for require/prefer etc.
+ */
+ switch (err)
+ {
+ case SEC_ERROR_INVALID_AVA:
+ case SEC_ERROR_INVALID_TIME:
+ case SEC_ERROR_BAD_SIGNATURE:
+ case SEC_ERROR_EXPIRED_CERTIFICATE:
+ case SEC_ERROR_UNKNOWN_ISSUER:
+ case SEC_ERROR_UNTRUSTED_ISSUER:
+ case SEC_ERROR_UNTRUSTED_CERT:
+ case SEC_ERROR_CERT_VALID:
+ case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
+ case SEC_ERROR_CRL_EXPIRED:
+ case SEC_ERROR_CRL_BAD_SIGNATURE:
+ case SEC_ERROR_EXTENSION_VALUE_INVALID:
+ case SEC_ERROR_CA_CERT_INVALID:
+ case SEC_ERROR_CERT_USAGES_INVALID:
+ case SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION:
+ return SECSuccess;
+ break;
+ default:
+ return SECFailure;
+ break;
+ }
+
+ /* Unreachable */
+ return SECSuccess;
+}
+
+/* ------------------------------------------------------------ */
+/* SSL information functions */
+/* ------------------------------------------------------------ */
+
+void *
+PQgetssl(PGconn *conn)
+{
+ /*
+ * Always return NULL as this is legacy and defined to be equal to
+ * PQsslStruct(conn, "OpenSSL"); This should ideally trigger a logged
+ * warning somewhere as it's nonsensical to run in a non-OpenSSL build,
+ * but the color of said bikeshed hasn't yet been determined.
+ */
+ return NULL;
+}
+
+void *
+PQsslStruct(PGconn *conn, const char *struct_name)
+{
+ if (!conn)
+ return NULL;
+
+ /*
+ * Return the underlying PRFileDesc which can be used to access
+ * information on the connection details. There is no SSL context per se.
+ */
+ if (strcmp(struct_name, "NSS") == 0)
+ return conn->pr_fd;
+ return NULL;
+}
+
+const char *const *
+PQsslAttributeNames(PGconn *conn)
+{
+ static const char *const result[] = {
+ "library",
+ "cipher",
+ "protocol",
+ "key_bits",
+ "compression",
+ NULL
+ };
+
+ return result;
+}
+
+const char *
+PQsslAttribute(PGconn *conn, const char *attribute_name)
+{
+ SECStatus status;
+ SSLChannelInfo channel;
+ SSLCipherSuiteInfo suite;
+
+ if (!conn || !conn->pr_fd)
+ return NULL;
+
+ if (strcmp(attribute_name, "library") == 0)
+ return "NSS";
+
+ status = SSL_GetChannelInfo(conn->pr_fd, &channel, sizeof(channel));
+ if (status != SECSuccess)
+ return NULL;
+
+ status = SSL_GetCipherSuiteInfo(channel.cipherSuite, &suite, sizeof(suite));
+ if (status != SECSuccess)
+ return NULL;
+
+ if (strcmp(attribute_name, "cipher") == 0)
+ return suite.cipherSuiteName;
+
+ if (strcmp(attribute_name, "key_bits") == 0)
+ {
+ static char key_bits_str[8];
+
+ snprintf(key_bits_str, sizeof(key_bits_str), "%i", suite.effectiveKeyBits);
+ return key_bits_str;
+ }
+
+ if (strcmp(attribute_name, "protocol") == 0)
+ {
+ switch (channel.protocolVersion)
+ {
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ return "TLSv1.3";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ return "TLSv1.2";
+#endif
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ return "TLSv1.1";
+#endif
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ return "TLSv1.0";
+ default:
+ return "unknown";
+ }
+ }
+
+ /*
+ * NSS disabled support for compression in version 3.33, and it was only
+ * available for SSLv3 at that point anyways, so we can safely return off
+ * here without checking.
+ */
+ if (strcmp(attribute_name, "compression") == 0)
+ return "off";
+
+ return NULL;
+}
+
+static int
+ssl_protocol_version_to_nss(const char *protocol)
+{
+ if (pg_strcasecmp("TLSv1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_0;
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_1
+ if (pg_strcasecmp("TLSv1.1", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_1;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_2
+ if (pg_strcasecmp("TLSv1.2", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_2;
+#endif
+
+#ifdef SSL_LIBRARY_VERSION_TLS_1_3
+ if (pg_strcasecmp("TLSv1.3", protocol) == 0)
+ return SSL_LIBRARY_VERSION_TLS_1_3;
+#endif
+
+ return -1;
+}
+
+static bool
+cert_database_has_CA(PGconn *conn)
+{
+ CERTCertList *certificates;
+ bool hasCA;
+
+ /*
+ * If the certificate database has a password we must provide it, since
+ * this API doesn't invoke the standard password callback.
+ */
+ if (has_password)
+ certificates = PK11_ListCerts(PK11CertListCA, PQssl_passwd_cb(NULL, PR_FALSE, \
(void *) conn)); + else
+ certificates = PK11_ListCerts(PK11CertListCA, NULL);
+ hasCA = !CERT_LIST_EMPTY(certificates);
+ CERT_DestroyCertList(certificates);
+
+ return hasCA;
+}
+
+PQsslKeyPassHook_nss_type
+PQgetSSLKeyPassHook_nss(void)
+{
+ return PQsslKeyPassHook;
+}
+
+void
+PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook)
+{
+ PQsslKeyPassHook = hook;
+}
+
+/*
+ * Supply a password to decrypt a client certificate.
+ *
+ * This must match NSS type PK11PasswordFunc.
+ */
+static char *
+PQssl_passwd_cb(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ has_password = true;
+
+ if (PQsslKeyPassHook)
+ return PQsslKeyPassHook(slot, (PRBool) retry, arg);
+ else
+ return PQdefaultSSLKeyPassHook_nss(slot, retry, arg);
+}
+
+/*
+ * The default password handler callback.
+ */
+char *
+PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void *arg)
+{
+ PGconn *conn = (PGconn *) arg;
+
+ /*
+ * If the password didn't work the first time there is no point in
+ * retrying as it hasn't changed.
+ */
+ if (retry != PR_TRUE && conn->sslpassword && strlen(conn->sslpassword) > 0)
+ return PORT_Strdup(conn->sslpassword);
+
+ return NULL;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index 3311fd7a5b..b6c92ece11 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -430,6 +430,9 @@ PQsslAttributeNames(PGconn *conn)
return result;
}
+#endif /* USE_SSL */
+
+#ifndef USE_OPENSSL
PQsslKeyPassHook_OpenSSL_type
PQgetSSLKeyPassHook_OpenSSL(void)
@@ -448,7 +451,7 @@ PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn \
*conn) {
return 0;
}
-#endif /* USE_SSL */
+#endif /* USE_OPENSSL */
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 3b6a9fbce3..27c16e187f 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -625,6 +625,17 @@ extern PQsslKeyPassHook_OpenSSL_type \
PQgetSSLKeyPassHook_OpenSSL(void); extern void \
PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook); extern \
int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn);
+/* == in fe-secure-nss.c === */
+typedef struct PK11SlotInfoStr PK11SlotInfo;
+typedef int PRIntn;
+typedef PRIntn PRBool;
+
+/* Support for overriding sslpassword handling with a callback. */
+typedef char *(*PQsslKeyPassHook_nss_type) (PK11SlotInfo * slot, PRBool retry, void \
*arg); +extern PQsslKeyPassHook_nss_type PQgetSSLKeyPassHook_nss(void);
+extern void PQsetSSLKeyPassHook_nss(PQsslKeyPassHook_nss_type hook);
+extern char *PQdefaultSSLKeyPassHook_nss(PK11SlotInfo * slot, PRBool retry, void \
*arg); +
#ifdef __cplusplus
}
#endif
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 1de91ae295..12717ca720 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -362,6 +362,7 @@ struct pg_conn
char *sslpassword; /* client key file password */
char *sslrootcert; /* root certificate filename */
char *sslcrl; /* certificate revocation list filename */
+ char *cert_database;
char *requirepeer; /* required peer credentials for local sockets */
char *gssencmode; /* GSS mode (require,prefer,disable) */
char *krbsrvname; /* Kerberos service name */
@@ -485,6 +486,10 @@ struct pg_conn
* OpenSSL version changes */
#endif
#endif /* USE_OPENSSL */
+
+#ifdef USE_NSS
+ void *pr_fd;
+#endif /* USE_NSS */
#endif /* USE_SSL */
#ifdef ENABLE_GSS
diff --git a/src/test/Makefile b/src/test/Makefile
index efb206aa75..d18f5a083b 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -27,7 +27,7 @@ ifneq (,$(filter ldap,$(PG_TEST_EXTRA)))
SUBDIRS += ldap
endif
endif
-ifeq ($(with_openssl),yes)
+ifeq ($(with_ssl),yes)
ifneq (,$(filter ssl,$(PG_TEST_EXTRA)))
SUBDIRS += ssl
endif
diff --git a/src/test/ssl/Makefile b/src/test/ssl/Makefile
index 777ee39413..fe265e2dbd 100644
--- a/src/test/ssl/Makefile
+++ b/src/test/ssl/Makefile
@@ -14,6 +14,7 @@ top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
export with_openssl
+export with_nss
CERTIFICATES := server_ca server-cn-and-alt-names \
server-cn-only server-single-alt-name server-multiple-alt-names \
@@ -30,6 +31,32 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) \
$(CERTIFICATES:%=ssl/%.crt) \ ssl/client+client_ca.crt ssl/client-der.key \
ssl/client-encrypted-pem.key ssl/client-encrypted-der.key
+# Even though we in practice could get away with far fewer NSS databases, they
+# are generated to mimick the setup for the OpenSSL tests in order to ensure
+# we isolate the same behavior between the backends. The database name should
+# contain the files included for easier test suite code reading.
+NSSFILES := ssl/nss/client_ca.crt.db \
+ ssl/nss/server_ca.crt.db \
+ ssl/nss/root+server_ca.crt.db \
+ ssl/nss/root+client_ca.crt.db \
+ ssl/nss/client.crt__client.key.db \
+ ssl/nss/client-revoked.crt__client-revoked.key.db \
+ ssl/nss/server-cn-only.crt__server-password.key.db \
+ ssl/nss/server-cn-only.crt__server-cn-only.key.db \
+ ssl/nss/root.crl \
+ ssl/nss/server.crl \
+ ssl/nss/client.crl \
+ ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db \
+ ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db \
+ ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db \
+ ssl/nss/server-no-names.crt__server-no-names.key.db \
+ ssl/nss/server-revoked.crt__server-revoked.key.db \
+ ssl/nss/root+client.crl \
+ ssl/nss/client+client_ca.crt__client.key.db \
+ ssl/nss/client.crt__client-encrypted-pem.key.db \
+ ssl/nss/root+server_ca.crt__server.crl.db \
+ ssl/nss/root+server_ca.crt__root+server.crl.db
+
# 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.
#
@@ -37,6 +64,10 @@ SSLFILES := $(CERTIFICATES:%=ssl/%.key) \
$(CERTIFICATES:%=ssl/%.crt) \ #
sslfiles: $(SSLFILES)
+# Generate NSS certificate databases corresponding to the OpenSSL certificates.
+# This target will fail unless preceded by nssfiles-clean.
+nssfiles: $(NSSFILES)
+
# OpenSSL requires a directory to put all generated certificates in. We don't
# use this for anything, but we need a location.
ssl/new_certs_dir:
@@ -64,6 +95,24 @@ ssl/%_ca.crt: ssl/%_ca.key %_ca.config ssl/root_ca.crt \
ssl/new_certs_dir rm ssl/temp_ca.crt ssl/temp_ca_signed.crt
echo "01" > ssl/$*_ca.srl
+ssl/nss/%_ca.crt.db: ssl/%_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n $*_ca.crt -i ssl/$*_ca.crt -t "CT,C,C"
+
+ssl/nss/root+server_ca.crt__server.crl.db: ssl/root+server_ca.crt ssl/nss/server.crl
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t \
"CT,C,C" + crlutil -I -i ssl/nss/server.crl -d $@ -B
+
+ssl/nss/root+server_ca.crt__root+server.crl.db: ssl/root+server_ca.crt \
ssl/nss/root.crl ssl/nss/server.crl + $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i ssl/root+server_ca.crt -t \
"CT,C,C" + crlutil -I -i ssl/nss/root.crl -d $@ -B
+ crlutil -I -i ssl/nss/server.crl -d $@ -B
+
# Server certificates, signed by server CA:
ssl/server-%.crt: ssl/server-%.key ssl/server_ca.crt server-%.config
openssl req -new -key ssl/server-$*.key -out ssl/server-$*.csr -config \
server-$*.config @@ -77,6 +126,74 @@ ssl/server-ss.crt: ssl/server-cn-only.key \
ssl/server-cn-only.crt server-cn-only. 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 rm ssl/server-ss.csr
+ssl/nss/server-cn-only.crt__server-password.key.db: ssl/server-cn-only.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t \
"CT,C,C" + certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-password.pfx -inkey \
ssl/server-password.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt \
-passin 'pass:secret1' -passout pass: + pk12util -i ssl/nss/server-password.pfx -d $@ \
-W '' +
+ssl/nss/server-cn-only.crt__server-cn-only.key.db: ssl/server-cn-only.crt \
ssl/server-cn-only.key + $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-only.crt -i ssl/server-cn-only.crt -t \
"CT,C,C" + certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-cn-only.pfx -inkey \
ssl/server-cn-only.key -in ssl/server-cn-only.crt -certfile ssl/server_ca.crt \
-passout pass: + pk12util -i ssl/nss/server-cn-only.pfx -d $@ -W ''
+
+ssl/nss/server-multiple-alt-names.crt__server-multiple-alt-names.key.db: \
ssl/server-multiple-alt-names.crt + $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-multiple-alt-names.crt -i \
ssl/server-multiple-alt-names.crt -t "CT,C,C" + certutil -d "sql:$@" -A -n \
server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" + certutil -d "sql:$@" -A -n \
root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" + certutil -d "sql:$@" -A -n client_ca.crt \
-i ssl/client_ca.crt -t "CT,C,C" + openssl pkcs12 -export -out \
ssl/nss/server-multiple-alt-names.pfx -inkey ssl/server-multiple-alt-names.key -in \
ssl/server-multiple-alt-names.crt -certfile ssl/server-multiple-alt-names.crt \
-passout pass: + pk12util -i ssl/nss/server-multiple-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-single-alt-name.crt__server-single-alt-name.key.db: \
ssl/server-single-alt-name.crt + $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-single-alt-name.crt -i \
ssl/server-single-alt-name.crt -t "CT,C,C" + certutil -d "sql:$@" -A -n server_ca.crt \
-i ssl/server_ca.crt -t "CT,C,C" + certutil -d "sql:$@" -A -n root_ca.crt -i \
ssl/root_ca.crt -t "CT,C,C" + certutil -d "sql:$@" -A -n client_ca.crt -i \
ssl/client_ca.crt -t "CT,C,C" + openssl pkcs12 -export -out \
ssl/nss/server-single-alt-name.pfx -inkey ssl/server-single-alt-name.key -in \
ssl/server-single-alt-name.crt -certfile ssl/server-single-alt-name.crt -passout \
pass: + pk12util -i ssl/nss/server-single-alt-name.pfx -d $@ -W ''
+
+ssl/nss/server-cn-and-alt-names.crt__server-cn-and-alt-names.key.db: \
ssl/server-cn-and-alt-names.crt + $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-cn-and-alt-names.crt -i \
ssl/server-cn-and-alt-names.crt -t "CT,C,C" + certutil -d "sql:$@" -A -n \
server_ca.crt -i ssl/server_ca.crt -t "CT,C,C" + certutil -d "sql:$@" -A -n \
root_ca.crt -i ssl/root_ca.crt -t "CT,C,C" + certutil -d "sql:$@" -A -n client_ca.crt \
-i ssl/client_ca.crt -t "CT,C,C" + openssl pkcs12 -export -out \
ssl/nss/server-cn-and-alt-names.pfx -inkey ssl/server-cn-and-alt-names.key -in \
ssl/server-cn-and-alt-names.crt -certfile ssl/server-cn-and-alt-names.crt -passout \
pass: + pk12util -i ssl/nss/server-cn-and-alt-names.pfx -d $@ -W ''
+
+ssl/nss/server-no-names.crt__server-no-names.key.db: ssl/server-no-names.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-no-names.crt -i ssl/server-no-names.crt -t \
"CT,C,C" + certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-no-names.pfx -inkey \
ssl/server-no-names.key -in ssl/server-no-names.crt -certfile ssl/server-no-names.crt \
-passout pass: + pk12util -i ssl/nss/server-no-names.pfx -d $@ -W ''
+
+ssl/nss/server-revoked.crt__server-revoked.key.db: ssl/server-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/server-revoked.crt -i ssl/server-revoked.crt -t \
"CT,C,C" + certutil -d "sql:$@" -A -n server_ca.crt -i ssl/server_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root_ca.crt -i ssl/root_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/server-revoked.pfx -inkey \
ssl/server-revoked.key -in ssl/server-revoked.crt -certfile ssl/server-revoked.crt \
-passout pass: + pk12util -i ssl/nss/server-revoked.pfx -d $@ -W ''
+
# Password-protected version of server-cn-only.key
ssl/server-password.key: ssl/server-cn-only.key
openssl rsa -aes256 -in $< -out $@ -passout 'pass:secret1'
@@ -88,6 +205,27 @@ ssl/client.crt: ssl/client.key ssl/client_ca.crt
openssl x509 -in ssl/temp.crt -out ssl/client.crt # to keep just the PEM cert
rm ssl/client.csr ssl/temp.crt
+# Client certificate, signed by client CA
+ssl/nss/client.crt__client.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -n root+client_ca.crt -i ssl/root+client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client.pfx -inkey ssl/client.key -in \
ssl/client.crt -certfile ssl/client_ca.crt -passout pass: + pk12util -i \
ssl/nss/client.pfx -d $@ -W '' +
+# Client certificate with encrypted key, signed by client CA
+ssl/nss/client.crt__client-encrypted-pem.key.db: ssl/client.crt
+ $(MKDIR_P) $@
+ echo 'dUmmyP^#+' > $@.pass
+ certutil -d "sql:$@" -N -f $@.pass
+ certutil -d "sql:$@" -A -f $@.pass -n ssl/client.crt -i ssl/client.crt -t "CT,C,C"
+ certutil -d "sql:$@" -A -f $@.pass -n client_ca.crt -i ssl/client_ca.crt -t \
"CT,C,C" + certutil -d "sql:$@" -A -f $@.pass -n root+server_ca.crt -i \
ssl/root+server_ca.crt -t "CT,C,C" + openssl pkcs12 -export -out \
ssl/nss/client-encrypted-pem.pfx -inkey ssl/client-encrypted-pem.key -in \
ssl/client.crt -certfile ssl/client_ca.crt -passin pass:'dUmmyP^#+' -passout \
pass:'dUmmyP^#+' + pk12util -i ssl/nss/client-encrypted-pem.pfx -d $@ -W 'dUmmyP^#+' \
-k $@.pass +
# Another client certificate, signed by the client CA. This one is revoked.
ssl/client-revoked.crt: ssl/client-revoked.key ssl/client_ca.crt client.config
openssl req -new -key ssl/client-revoked.key -out ssl/client-revoked.csr -config \
client.config @@ -95,6 +233,14 @@ 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
+ssl/nss/client-revoked.crt__client-revoked.key.db: ssl/client-revoked.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client-revoked.crt -i ssl/client-revoked.crt -t \
"CT,C,C" + certutil -d "sql:$@" -A -n client_ca.crt -i ssl/client_ca.crt -t "CT,C,C"
+ openssl pkcs12 -export -out ssl/nss/client-revoked.pfx -inkey \
ssl/client-revoked.key -in ssl/client-revoked.crt -certfile ssl/client_ca.crt \
-passout pass: + pk12util -i ssl/nss/client-revoked.pfx -d $@ -W ''
+
# 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
@@ -127,19 +273,40 @@ ssl/root+client_ca.crt: ssl/root_ca.crt ssl/client_ca.crt
ssl/client+client_ca.crt: ssl/client.crt ssl/client_ca.crt
cat $^ > $@
+# Client certificate, signed by client CA
+ssl/nss/client+client_ca.crt__client.key.db: ssl/client+client_ca.crt
+ $(MKDIR_P) $@
+ certutil -d "sql:$@" -N --empty-password
+ certutil -d "sql:$@" -A -n ssl/client+client_ca.crt -i ssl/client+client_ca.crt -t \
"CT,C,C" + certutil -d "sql:$@" -A -n ssl/root+server_ca.crt -i \
ssl/root+server_ca.crt -t "CT,C,C" + openssl pkcs12 -export -out ssl/nss/client.pfx \
-inkey ssl/client.key -in ssl/client.crt -certfile ssl/client_ca.crt -passout pass: \
+ pk12util -i ssl/nss/client.pfx -d $@ -W '' +
#### CRLs
ssl/client.crl: ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -revoke ssl/client-revoked.crt
openssl ca -config cas.config -name client_ca -gencrl -out ssl/client.crl
+ssl/nss/client.crl: ssl/client.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/server.crl: ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -revoke ssl/server-revoked.crt
openssl ca -config cas.config -name server_ca -gencrl -out ssl/server.crl
+ssl/nss/server.crl: ssl/server.crl
+ openssl crl -in $^ -outform der -out $@
+
ssl/root.crl: ssl/root_ca.crt
openssl ca -config cas.config -name root_ca -gencrl -out ssl/root.crl
+ssl/nss/root.crl: ssl/root.crl
+ openssl crl -in $^ -outform der -out $@
+
+ssl/nss/root+client.crl: ssl/root+client.crl
+ openssl crl -in $^ -outform der -out $@
+
# If a CRL is used, OpenSSL requires a CRL file for *all* the CAs in the
# chain, even if some of them are empty.
ssl/root+server.crl: ssl/root.crl ssl/server.crl
@@ -151,9 +318,14 @@ ssl/root+client.crl: ssl/root.crl ssl/client.crl
sslfiles-clean:
rm -f $(SSLFILES) ssl/client_ca.srl ssl/server_ca.srl ssl/client_ca-certindex* \
ssl/server_ca-certindex* ssl/root_ca-certindex* ssl/root_ca.srl ssl/temp_ca.crt \
ssl/temp_ca_signed.crt
+.PHONY: nssfiles-clean
+nssfiles-clean:
+ rm -rf ssl/nss
+
clean distclean maintainer-clean:
rm -rf tmp_check
rm -rf ssl/*.old ssl/new_certs_dir ssl/client*_tmp.key
+ rm -rf ssl/nss
# Doesn't depend on $(SSLFILES) because we don't rebuild them by default
check:
diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl
index fd2727b568..c27313f86d 100644
--- a/src/test/ssl/t/001_ssltests.pl
+++ b/src/test/ssl/t/001_ssltests.pl
@@ -4,15 +4,22 @@ use PostgresNode;
use TestLib;
use Test::More;
-use File::Copy;
-
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
+
+my $openssl;
+my $nss;
if ($ENV{with_openssl} eq 'yes')
{
+ $openssl = 1;
+ plan tests => 93;
+}
+elsif ($ENV{with_nss} eq 'yes')
+{
+ $nss = 1;
plan tests => 93;
}
else
@@ -32,32 +39,6 @@ my $SERVERHOSTCIDR = '127.0.0.1/32';
# Allocation of base connection string shared among multiple tests.
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.
-#
-# 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
-# permissions. Add it to @keys to include it in the final clean
-# up phase.
-copy("ssl/client.key", "ssl/client_wrongperms_tmp.key");
-chmod 0644, "ssl/client_wrongperms_tmp.key";
-push @keys, 'client_wrongperms';
-
#### Set up the server.
note "setting up data directory";
@@ -72,32 +53,28 @@ $node->start;
# Run this before we lock down access below.
my $result = $node->safe_psql('postgres', "SHOW ssl_library");
-is($result, 'OpenSSL', 'ssl_library parameter');
+is($result, SSL::Server::ssl_library(), 'ssl_library parameter');
configure_test_server_for_ssl($node, $SERVERHOSTADDR, $SERVERHOSTCIDR,
'trust');
note "testing password-protected keys";
-open my $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo wrongpassword'\n";
-close $sslconf;
-
-command_fails(
- [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
- 'restart fails with password-protected key file with wrong password');
-$node->_update_pid(0);
-
-open $sslconf, '>', $node->data_dir . "/sslconfig.conf";
-print $sslconf "ssl=on\n";
-print $sslconf "ssl_cert_file='server-cn-only.crt'\n";
-print $sslconf "ssl_key_file='server-password.key'\n";
-print $sslconf "ssl_passphrase_command='echo secret1'\n";
-close $sslconf;
+SKIP:
+{
+ skip "Certificate passphrases aren't checked on server restart in NSS", 1
+ if ($nss);
+
+ switch_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo wrongpassword');
+ command_fails(
+ [ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
+ 'restart fails with password-protected key file with wrong password');
+ $node->_update_pid(0);
+}
+switch_server_cert($node, 'server-cn-only', 'root+client_ca',
+ 'server-password', 'echo secret1');
command_ok(
[ 'pg_ctl', '-D', $node->data_dir, '-l', $node->logfile, 'restart' ],
'restart succeeds with password-protected key file');
@@ -149,82 +126,105 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-ca",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|could not connect to server/,
"connect without server root cert sslmode=verify-ca");
test_connect_fails(
$common_connstr,
"sslrootcert=invalid sslmode=verify-full",
- qr/root certificate file "invalid" does not exist/,
+ qr/root certificate file "invalid" does not exist|could not connect to server/,
"connect without server root cert sslmode=verify-full");
# Try with wrong root cert, should fail. (We're using the client CA as the
# root, but the server's key is signed by the server CA.)
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=require",
- qr/SSL error/, "connect with wrong server root cert sslmode=require");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-ca");
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/client_ca.crt sslmode=verify-full",
- qr/SSL error/, "connect with wrong server root cert sslmode=verify-full");
-
-# Try with just the server CA's cert. This fails because the root file
-# must contain the whole chain up to the root CA.
-test_connect_fails($common_connstr,
- "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
- qr/SSL error/, "connect with server CA cert, without root CA");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=require \
cert_database=ssl/nss/client_ca.crt.db", + qr/SSL error/,
+ "connect with wrong server root cert sslmode=require");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-ca \
cert_database=ssl/nss/client_ca.crt.db", + qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-ca");
+test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/client_ca.crt sslmode=verify-full \
cert_database=ssl/nss/client_ca.crt.db", + qr/SSL error/,
+ "connect with wrong server root cert sslmode=verify-full");
+
+SKIP:
+{
+ # NSS supports partial chain validation, so this test doesnt work there.
+ # This is similar to the OpenSSL option X509_V_FLAG_PARTIAL_CHAIN which
+ # we don't allow.
+ skip "NSS support partial chain validation", 2 if ($nss);
+ # Try with just the server CA's cert. This fails because the root file
+ # must contain the whole chain up to the root CA.
+ test_connect_fails($common_connstr,
+ "sslrootcert=ssl/server_ca.crt sslmode=verify-ca",
+ qr/SSL error/, "connect with server CA cert, without root CA");
+}
# And finally, with the correct root cert.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require \
cert_database=ssl/nss/root+server_ca.crt.db", "connect with correct server CA cert \
file sslmode=require"); test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca \
cert_database=ssl/nss/root+server_ca.crt.db", "connect with correct server CA cert \
file sslmode=verify-ca"); test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-full \
cert_database=ssl/nss/root+server_ca.crt.db", "connect with correct server CA cert \
file sslmode=verify-full");
-# Test with cert root file that contains two certificates. The client should
-# be able to pick the right one, regardless of the order in the file.
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 1");
-test_connect_ok(
- $common_connstr,
- "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
- "cert root file that contains two certificates, order 2");
+SKIP:
+{
+ skip "CA ordering is irrelevant in NSS databases", 2 if ($nss);
+
+ # Test with cert root file that contains two certificates. The client should
+ # be able to pick the right one, regardless of the order in the file.
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-1.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 1");
+ # How about import the both-file into a database?
+ test_connect_ok(
+ $common_connstr,
+ "sslrootcert=ssl/both-cas-2.crt sslmode=verify-ca",
+ "cert root file that contains two certificates, order 2");
+}
# CRL tests
# Invalid CRL filename is the same as no CRL, succeeds
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=invalid \
cert_database=ssl/nss/root+server_ca.crt.db", "sslcrl option with invalid file \
name");
-# A CRL belonging to a different CA is not accepted, fails
-test_connect_fails(
- $common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
- qr/SSL error/,
- "CRL belonging to a different CA");
+SKIP:
+{
+ skip "CRL's are verified when adding to NSS database", 2 if ($nss);
+ # A CRL belonging to a different CA is not accepted, fails
+ test_connect_fails(
+ $common_connstr,
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/client.crl",
+ qr/SSL error/,
+ "CRL belonging to a different CA");
+}
# With the correct CRL, succeeds (this cert is not revoked)
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl \
cert_database=ssl/nss/root+server_ca.crt__root+server.crl.db", "CRL with a \
non-revoked cert");
# Check that connecting with verify-full fails, when the hostname doesn't
# match the hostname in the server's certificate.
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid \
sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR"; + "user=ssltestuser \
dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt \
hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -237,14 +237,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=wronghost.test",
- qr/\Qserver certificate for "common-name.pg-ssltest.test" does not match host name \
"wronghost.test"\E/, + qr/\Qserver certificate for "common-name.pg-ssltest.test" does \
not match host name "wronghost.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/, "mismatch between \
host name and server certificate sslmode=verify-full");
# Test Subject Alternative Names.
switch_server_cert($node, 'server-multiple-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid \
sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full"; + \
"user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt \
hostaddr=$SERVERHOSTADDR sslmode=verify-full \
cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -262,12 +262,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) \
does not match host name "wronghost.alt-name.pg-ssltest.test"\E/, + qr/\Qserver \
certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match \
host name "wronghost.alt-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/, "host \
name not matching with X.509 Subject Alternative Names"); test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) \
does not match host name "deep.subdomain.wildcard.pg-ssltest.test"\E/, + qr/\Qserver \
certificate for "dns1.alt-name.pg-ssltest.test" (and 2 other names) does not match \
host name "deep.subdomain.wildcard.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/, \
"host name not matching with X.509 Subject Alternative Names wildcard");
# Test certificate with a single Subject Alternative Name. (this gives a
@@ -275,7 +275,7 @@ test_connect_fails(
switch_server_cert($node, 'server-single-alt-name');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid \
sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full"; + \
"user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt \
hostaddr=$SERVERHOSTADDR sslmode=verify-full \
cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -285,12 +285,12 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=wronghost.alt-name.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host \
name "wronghost.alt-name.pg-ssltest.test"\E/, + qr/\Qserver certificate for \
"single.alt-name.pg-ssltest.test" does not match host name \
"wronghost.alt-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/, "host name not \
matching with a single X.509 Subject Alternative Name"); test_connect_fails(
$common_connstr,
"host=deep.subdomain.wildcard.pg-ssltest.test",
- qr/\Qserver certificate for "single.alt-name.pg-ssltest.test" does not match host \
name "deep.subdomain.wildcard.pg-ssltest.test"\E/, + qr/\Qserver certificate for \
"single.alt-name.pg-ssltest.test" does not match host name \
"deep.subdomain.wildcard.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/, "host name \
not matching with a single X.509 Subject Alternative Name wildcard" );
@@ -299,7 +299,7 @@ test_connect_fails(
switch_server_cert($node, 'server-cn-and-alt-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid \
sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR sslmode=verify-full"; + \
"user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt \
hostaddr=$SERVERHOSTADDR sslmode=verify-full \
cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -312,14 +312,14 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"host=common-name.pg-ssltest.test",
- qr/\Qserver certificate for "dns1.alt-name.pg-ssltest.test" (and 1 other name) does \
not match host name "common-name.pg-ssltest.test"\E/, + qr/\Qserver certificate for \
"dns1.alt-name.pg-ssltest.test" (and 1 other name) does not match host name \
"common-name.pg-ssltest.test"\E|SSL_ERROR_BAD_CERT_DOMAIN/, "certificate with both a \
CN and SANs ignores CN");
# Finally, test a server certificate that has no CN or SANs. Of course, that's
# not a very sensible certificate, but libpq should handle it gracefully.
switch_server_cert($node, 'server-no-names');
$common_connstr =
- "user=ssltestuser dbname=trustdb sslcert=invalid \
sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR"; + "user=ssltestuser \
dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt \
hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/root+server_ca.crt.db";
test_connect_ok(
$common_connstr,
@@ -328,7 +328,7 @@ test_connect_ok(
test_connect_fails(
$common_connstr,
"sslmode=verify-full host=common-name.pg-ssltest.test",
- qr/could not get server's host name from server certificate/,
+ qr/could not get server's host name from server \
certificate|SSL_ERROR_BAD_CERT_DOMAIN/, "server certificate without CN or SANs \
sslmode=verify-full");
# Test that the CRL works
@@ -340,11 +340,11 @@ $common_connstr =
# Without the CRL, succeeds. With it, fails.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca \
cert_database=ssl/nss/root+server_ca.crt.db", "connects without client-side CRL");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/root+server.crl",
+ "sslrootcert=ssl/root+server_ca.crt sslmode=verify-ca sslcrl=ssl/server.crl \
cert_database=ssl/nss/root+server_ca.crt__server.crl.db", qr/SSL error/,
"does not connect with client-side CRL");
@@ -365,21 +365,21 @@ command_like(
# Test min/max SSL protocol versions.
test_connect_ok(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require \
ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2", \
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require \
ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.2 \
cert_database=ssl/nss/root+server_ca.crt.db", "connection success with correct range \
of TLS protocol versions"); test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require \
ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1", \
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require \
ssl_min_protocol_version=TLSv1.2 ssl_max_protocol_version=TLSv1.1 \
cert_database=ssl/nss/root+server_ca.crt.db", qr/invalid SSL protocol version \
range/, "connection failure with incorrect range of TLS protocol versions");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require \
ssl_min_protocol_version=incorrect_tls", + "sslrootcert=ssl/root+server_ca.crt \
sslmode=require ssl_min_protocol_version=incorrect_tls \
cert_database=ssl/nss/root+server_ca.crt.db", qr/invalid ssl_min_protocol_version \
value/, "connection failure with an incorrect SSL protocol minimum bound");
test_connect_fails(
$common_connstr,
- "sslrootcert=ssl/root+server_ca.crt sslmode=require \
ssl_max_protocol_version=incorrect_tls", + "sslrootcert=ssl/root+server_ca.crt \
sslmode=require ssl_max_protocol_version=incorrect_tls \
cert_database=ssl/nss/root+server_ca.crt.db", qr/invalid ssl_max_protocol_version \
value/, "connection failure with an incorrect SSL protocol maximum bound");
@@ -390,7 +390,7 @@ test_connect_fails(
note "running server tests";
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb \
hostaddr=$SERVERHOSTADDR"; + "sslrootcert=ssl/root+server_ca.crt sslmode=require \
dbname=certdb hostaddr=$SERVERHOSTADDR \
cert_database=ssl/nss/client.crt__client.key.db";
# no client cert
test_connect_fails(
@@ -406,32 +406,43 @@ test_connect_ok(
"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"
-);
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb \
hostaddr=$SERVERHOSTADDR"; +
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # 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^#+'", + "user=ssltestuser sslcert=ssl/client.crt \
sslkey=ssl/client-encrypted-pem_tmp.key sslpassword='dUmmyP^#+' \
cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db", "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"
-);
+SKIP:
+{
+ skip "NSS database not implemented in the Makefile", 1 if ($nss);
+ # 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!,
+ "user=ssltestuser sslcert=ssl/client.crt sslkey=ssl/client-encrypted-pem_tmp.key \
sslpassword='wrong' cert_database=ssl/nss/client.crt__client-encrypted-pem.key.db", \
+ qr!connection requires a valid client certificate|\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" );
@@ -471,18 +482,19 @@ command_like(
'-P',
'null=_null_',
'-d',
- "$common_connstr user=ssltestuser sslcert=ssl/client.crt \
sslkey=ssl/client_tmp.key", + "$common_connstr user=ssltestuser \
sslcert=ssl/client.crt sslkey=ssl/client_tmp.key \
cert_database=ssl/nss/client.crt__client.key.db", '-c',
"SELECT * FROM pg_stat_ssl WHERE pid = pg_backend_pid()"
],
qr{^pid,ssl,version,cipher,bits,compression,client_dn,client_serial,issuer_dn\r?\n
- ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/CN=ssltestuser,1,\Q/CN=Test CA for PostgreSQL \
SSL regression test client certs\E\r?$}mx, \
+ ^\d+,t,TLSv[\d.]+,[\w-]+,\d+,f,/?CN=ssltestuser,1,/?\QCN=Test CA for PostgreSQL \
SSL regression test client certs\E\r?$}mx, 'pg_stat_ssl with client certificate');
# client key with wrong permissions
SKIP:
{
skip "Permissions check not enforced on Windows", 2 if ($windows_os);
+ skip "Key not on filesystem with NSS", 2 if ($nss);
test_connect_fails(
$common_connstr,
@@ -495,10 +507,13 @@ SKIP:
test_connect_fails(
$common_connstr,
"user=anotheruser sslcert=ssl/client.crt sslkey=ssl/client_tmp.key",
- qr/certificate authentication failed for user "anotheruser"/,
+ qr/unable to verify certificate|certificate authentication failed for user \
"anotheruser"/, "certificate authorization fails with client cert belonging to \
another user" );
+$common_connstr =
+ "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=certdb \
hostaddr=$SERVERHOSTADDR \
cert_database=ssl/nss/client-revoked.crt__client-revoked.key.db"; +
# revoked client cert
test_connect_fails(
$common_connstr,
@@ -510,7 +525,7 @@ test_connect_fails(
# works, iff username matches Common Name
# fails, iff username doesn't match Common Name.
$common_connstr =
- "sslrootcert=ssl/root+server_ca.crt sslmode=require dbname=verifydb \
hostaddr=$SERVERHOSTADDR"; + "sslrootcert=ssl/root+server_ca.crt sslmode=require \
dbname=verifydb hostaddr=$SERVERHOSTADDR \
cert_database=ssl/nss/client.crt__client.key.db";
test_connect_ok(
$common_connstr,
@@ -536,17 +551,18 @@ test_connect_ok(
# intermediate client_ca.crt is provided by client, and isn't in server's \
ssl_ca_file switch_server_cert($node, 'server-cn-only', 'root_ca');
$common_connstr =
- "user=ssltestuser dbname=certdb sslkey=ssl/client_tmp.key \
sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR"; + "user=ssltestuser \
dbname=certdb sslkey=ssl/client_tmp.key sslrootcert=ssl/root+server_ca.crt \
hostaddr=$SERVERHOSTADDR cert_database=ssl/nss/client+client_ca.crt__client.key.db"; \
test_connect_ok(
$common_connstr,
"sslmode=require sslcert=ssl/client+client_ca.crt",
"intermediate client certificate is provided by client");
-test_connect_fails($common_connstr, "sslmode=require sslcert=ssl/client.crt",
- qr/SSL error/, "intermediate client certificate is missing");
+test_connect_fails(
+ $common_connstr,
+ "sslmode=require sslcert=ssl/client.crt",
+ qr/connection requires a valid client certificate|SSL error/,
+ "intermediate client certificate is missing");
# clean up
-foreach my $key (@keys)
-{
- unlink("ssl/${key}_tmp.key");
-}
+
+SSL::Server::cleanup();
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 01231f8ba0..4ea81fdbcf 100644
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -11,11 +11,11 @@ use File::Copy;
use FindBin;
use lib $FindBin::RealBin;
-use SSLServer;
+use SSL::Server;
if ($ENV{with_openssl} ne 'yes')
{
- plan skip_all => 'SSL not supported by this build';
+ plan skip_all => 'OpenSSL not supported by this build';
}
# This is the hostname used to connect to the server.
diff --git a/src/test/ssl/t/SSL/Backend/NSS.pm b/src/test/ssl/t/SSL/Backend/NSS.pm
new file mode 100644
index 0000000000..837f0d9891
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/NSS.pm
@@ -0,0 +1,64 @@
+package SSL::Backend::NSS;
+
+use strict;
+use warnings;
+use Exporter;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_nss_backend);
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'NSS' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_nss_backend
+{
+ my $class = 'SSL::Backend::NSS';
+
+ return $class->new();
+}
+
+sub init
+{
+ # Make sure the certificate databases are in place?
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2];
+ my $keyfile = $_[3];
+
+ my $cert_nickname = $certfile . '.crt__' . $keyfile . '.key';
+ my $cert_database = $cert_nickname . '.db';
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='ssl/$certfile.crt'\n"
+ . "ssl_crl_file=''\n"
+ . "ssl_database='nss/$cert_database'\n";
+
+ return $sslconf;
+}
+
+sub cleanup
+{
+ # Something?
+}
+
+1;
diff --git a/src/test/ssl/t/SSL/Backend/OpenSSL.pm \
b/src/test/ssl/t/SSL/Backend/OpenSSL.pm new file mode 100644
index 0000000000..62b11b7632
--- /dev/null
+++ b/src/test/ssl/t/SSL/Backend/OpenSSL.pm
@@ -0,0 +1,103 @@
+package SSL::Backend::OpenSSL;
+
+use strict;
+use warnings;
+use Exporter;
+use File::Copy;
+
+our @ISA = qw(Exporter);
+our @EXPORT_OK = qw(get_new_openssl_backend);
+
+our (@keys);
+
+INIT
+{
+ @keys = (
+ "client", "client-revoked",
+ "client-der", "client-encrypted-pem",
+ "client-encrypted-der");
+}
+
+sub new
+{
+ my ($class) = @_;
+
+ my $self = { _library => 'OpenSSL' };
+
+ bless $self, $class;
+
+ return $self;
+}
+
+sub get_new_openssl_backend
+{
+ my $class = 'SSL::Backend::OpenSSL';
+
+ my $backend = $class->new();
+
+ return $backend;
+}
+
+sub init
+{
+ # 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.
+ #
+ # This changes ssl/client.key to ssl/client_tmp.key etc for the rest
+ # of the tests.
+ 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
+ # permissions. Add it to @keys to include it in the final clean
+ # up phase.
+ copy("ssl/client.key", "ssl/client_wrongperms_tmp.key")
+ or die
+ "couldn't copy ssl/client.key to ssl/client_wrongperms_tmp.key: $!";
+ chmod 0644, "ssl/client_wrongperms_tmp.key"
+ or die
+ "failed to change permissions on ssl/client_wrongperms_tmp.key: $!";
+ push @keys, 'client_wrongperms';
+}
+
+# Change the configuration to use given server cert file, and reload
+# the server so that the configuration takes effect.
+sub set_server_cert
+{
+ my $self = $_[0];
+ my $certfile = $_[1];
+ my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || $certfile;
+
+ my $sslconf =
+ "ssl_ca_file='$cafile.crt'\n"
+ . "ssl_cert_file='$certfile.crt'\n"
+ . "ssl_key_file='$keyfile.key'\n"
+ . "ssl_crl_file='root+client.crl'\n";
+
+ return $sslconf;
+}
+
+sub get_library
+{
+ my ($self) = @_;
+
+ return $self->{_library};
+}
+
+sub cleanup
+{
+ foreach my $key (@keys)
+ {
+ unlink("ssl/${key}_tmp.key");
+ }
+}
+
+1;
diff --git a/src/test/ssl/t/SSLServer.pm b/src/test/ssl/t/SSLServer.pm
index 1e392b8fbf..f6b9711bcd 100644
--- a/src/test/ssl/t/SSLServer.pm
+++ b/src/test/ssl/t/SSLServer.pm
@@ -24,15 +24,34 @@
# explicitly because an invalid sslcert or sslrootcert, respectively,
# causes those to be ignored.)
-package SSLServer;
+package SSL::Server;
use strict;
use warnings;
use PostgresNode;
+use RecursiveCopy;
use TestLib;
use File::Basename;
use File::Copy;
use Test::More;
+use SSL::Backend::OpenSSL qw(get_new_openssl_backend);
+use SSL::Backend::NSS qw(get_new_nss_backend);
+
+our ($openssl, $nss, $backend);
+
+# The TLS backend which the server is using should be mostly transparent for
+# the user, apart from individual configuration settings, so keep the backend
+# specific things abstracted behind SSL::Server.
+if ($ENV{with_openssl} eq 'yes')
+{
+ $backend = get_new_openssl_backend();
+ $openssl = 1;
+}
+elsif ($ENV{with_nss} eq 'yes')
+{
+ $backend = get_new_nss_backend();
+ $nss = 1;
+}
use Exporter 'import';
our @EXPORT = qw(
@@ -145,12 +164,19 @@ sub configure_test_server_for_ssl
close $sslconf;
# Copy all server certificates and keys, and client root cert, to the data dir
- copy_files("ssl/server-*.crt", $pgdata);
- copy_files("ssl/server-*.key", $pgdata);
- chmod(0600, glob "$pgdata/server-*.key") or die $!;
- copy_files("ssl/root+client_ca.crt", $pgdata);
- copy_files("ssl/root_ca.crt", $pgdata);
- copy_files("ssl/root+client.crl", $pgdata);
+ if (defined($openssl))
+ {
+ copy_files("ssl/server-*.crt", $pgdata);
+ copy_files("ssl/server-*.key", $pgdata);
+ chmod(0600, glob "$pgdata/server-*.key") or die $!;
+ copy_files("ssl/root+client_ca.crt", $pgdata);
+ copy_files("ssl/root_ca.crt", $pgdata);
+ copy_files("ssl/root+client.crl", $pgdata);
+ }
+ elsif (defined($nss))
+ {
+ RecursiveCopy::copypath("ssl/nss", $pgdata . "/nss") if -e "ssl/nss";
+ }
# Stop and restart server to load new listen_addresses.
$node->restart;
@@ -158,9 +184,22 @@ sub configure_test_server_for_ssl
# Change pg_hba after restart because hostssl requires ssl=on
configure_hba_for_ssl($node, $servercidr, $authmethod);
+ # Finally, perform backend specific configuration
+ $backend->init();
+
return;
}
+sub ssl_library
+{
+ return $backend->get_library();
+}
+
+sub cleanup
+{
+ $backend->cleanup();
+}
+
# Change the configuration to use given server cert file, and reload
# the server so that the configuration takes effect.
sub switch_server_cert
@@ -168,14 +207,17 @@ sub switch_server_cert
my $node = $_[0];
my $certfile = $_[1];
my $cafile = $_[2] || "root+client_ca";
+ my $keyfile = $_[3] || '';
+ my $pwcmd = $_[4] || '';
my $pgdata = $node->data_dir;
+ $keyfile = $certfile if $keyfile eq '';
+
open my $sslconf, '>', "$pgdata/sslconfig.conf";
print $sslconf "ssl=on\n";
- print $sslconf "ssl_ca_file='$cafile.crt'\n";
- print $sslconf "ssl_cert_file='$certfile.crt'\n";
- print $sslconf "ssl_key_file='$certfile.key'\n";
- print $sslconf "ssl_crl_file='root+client.crl'\n";
+ print $sslconf $backend->set_server_cert($certfile, $cafile, $keyfile);
+ print $sslconf "ssl_passphrase_command='$pwcmd'\n"
+ unless $pwcmd eq '';
close $sslconf;
$node->restart;
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index b6d0cfd39b..c53c59229e 100644
--- a/src/tools/msvc/Install.pm
+++ b/src/tools/msvc/Install.pm
@@ -438,7 +438,8 @@ sub CopyContribFiles
{
# These configuration-based exclusions must match vcregress.pl
next if ($d eq "uuid-ossp" && !defined($config->{uuid}));
- next if ($d eq "sslinfo" && !defined($config->{openssl}));
+ next if ($d eq "sslinfo" && !defined($config->{openssl})
+ && !defined($config->{nss}));
next if ($d eq "xml2" && !defined($config->{xml}));
next if ($d =~ /_plperl$/ && !defined($config->{perl}));
next if ($d =~ /_plpython$/ && !defined($config->{python}));
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 20da7985c1..818a1922f3 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -192,12 +192,19 @@ sub mkvcbuild
$postgres->FullExportDLL('postgres.lib');
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options.
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
@@ -255,12 +262,19 @@ sub mkvcbuild
$libpq->AddReference($libpgcommon, $libpgport);
# The OBJS scraper doesn't know about ifdefs, so remove appropriate files
- # if building without OpenSSL.
- if (!$solution->{options}->{openssl})
+ # if building without various options
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
+ }
+ if (!$solution->{options}->{openssl})
+ {
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{nss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-nss.c');
+ }
if (!$solution->{options}->{gss})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
@@ -428,9 +442,14 @@ sub mkvcbuild
push @contrib_excludes, 'xml2';
}
+ if (!$solution->{options}->{openssl} && !$solution->{options}->{nss})
+ {
+ push @contrib_excludes, 'sslinfo';
+ }
+
if (!$solution->{options}->{openssl})
{
- push @contrib_excludes, 'sslinfo', 'ssl_passphrase_callback';
+ push @contrib_excludes, 'ssl_passphrase_callback';
}
if (!$solution->{options}->{uuid})
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index bc8904732f..ac11d9ab26 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -484,6 +484,7 @@ sub GenerateFiles
USE_NAMED_POSIX_SEMAPHORES => undef,
USE_OPENSSL => undef,
USE_OPENSSL_RANDOM => undef,
+ USE_NSS => undef,
USE_PAM => undef,
USE_SLICING_BY_8_CRC32C => undef,
USE_SSE42_CRC32C => undef,
@@ -537,6 +538,10 @@ sub GenerateFiles
$define{HAVE_OPENSSL_INIT_SSL} = 1;
}
}
+ if ($self->{options}->{nss})
+ {
+ $define{USE_NSS} = 1;
+ }
$self->GenerateConfigHeader('src/include/pg_config.h', \%define, 1);
$self->GenerateConfigHeader('src/include/pg_config_ext.h', \%define, 0);
@@ -1004,6 +1009,21 @@ sub AddProject
}
}
}
+ if ($self->{options}->{nss})
+ {
+ $proj->AddIncludeDir($self->{options}->{nss} . '\..\public\nss');
+ $proj->AddIncludeDir($self->{options}->{nss} . '\include\nspr');
+ foreach my $lib (qw(plds4 plc4 nspr4))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib\lib' . "$lib.lib", 0);
+ }
+ foreach my $lib (qw(ssl3 smime3 nss3))
+ {
+ $proj->AddLibrary($self->{options}->{nss} .
+ '\lib' . "\\$lib.dll.lib", 0);
+ }
+ }
if ($self->{options}->{nls})
{
$proj->AddIncludeDir($self->{options}->{nls} . '\include');
diff --git a/src/tools/msvc/config_default.pl b/src/tools/msvc/config_default.pl
index 2ef2cfc4e9..49dc4d5864 100644
--- a/src/tools/msvc/config_default.pl
+++ b/src/tools/msvc/config_default.pl
@@ -17,6 +17,7 @@ our $config = {
perl => undef, # --with-perl=<path>
python => undef, # --with-python=<path>
openssl => undef, # --with-openssl=<path>
+ nss => undef, # --with-nss=<path>
uuid => undef, # --with-uuid=<path>
xml => undef, # --with-libxml=<path>
xslt => undef, # --with-libxslt=<path>
--
2.27.0.windows.1
[prev in list] [next in list] [prev in thread] [next in thread]
Configure |
About |
News |
Add a list |
Sponsored by KoreLogic